線程
1 線程概念
進程:進程指正在內存中運行的程序。進程具有一定的獨立性。
線程:線程是進程中的一個執行單元。負責當前進程中程序的執行。一個進程中至少有一個線程。如果一個進程中有多個線程,稱之為多線程程序。
java中的線程采用的是搶占式調度,如果線程的優先級相同,那么會隨機選擇一個線程執行。理論上,優先級高的線程搶到CPU的概率更大。
CPU使用搶占式調度在多個線程間進行著高速的切換。對于CPU的一個核而言,某個時刻,只能執行一個線程。CPU在多個線程間切換的速度相對我們的感受要快得多,看上去就是在同時執行。
多線程程序并不能提高程序的運行速度,但是能夠提高程序的運行效率,讓CPU的使用率更高。
為什么要使用多線程?
提高用戶體驗
提高CPU的利用率
線程在執行的過程中會和計算機硬件進行交互,線程和硬件交互的時候會暫時空置CPU,從而多線程可以提高CPU利用率
2 線程的創建
2.1 線程的創建方式一
定義一個類繼承Thread
重寫run方法
創建子類對象
調用start方法,開啟線程并讓線程執行
public class MyThread extends Thread{ ?@Overridepublic void run() {// 把希望其他線程執行的代碼放在run方法中for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "正在執行了。。。");}} }
2.2 線程的創建方式二[推薦]
定義類實現Runnable接口
實現接口中的run方法
創建Thread類的對象
將Runnable接口的實現類對象作為參數傳遞給Thread類的構造方法
調用Thread類的start方法開啟線程
public class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "正在執行");}} } ? ? public static void main(String[] args) { // ? ? ? MyRunnable myRunnable = new MyRunnable(); // ? ? ? Thread thread = new Thread(myRunnable,"子線程"); // ? ? ? thread.start();// 匿名內部類 // ? ? ? new Thread(new Runnable() { // ? ? ? ? ? @Override // ? ? ? ? ? public void run() { // ? ? ? ? ? ? ? for (int i = 0; i < 100; i++) { // ? ? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "執行了"); // ? ? ? ? ? ? ? } // ? ? ? ? ? } // ? ? ? }).start();// lambda表達式new Thread(()-> {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "執行了");}},"小線程").start(); ?for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName());}}
課堂練習:售票案例:模擬電影院賣票,實現3個窗口同時賣"哪吒2"的100張票。
方式一:
public class Ticket implements Runnable{int ticket = 100;@Overridepublic void run() {while (true){if (ticket <= 0) {break;} ?System.out.println(Thread.currentThread().getName() + "您的票是" + ticket--);}} } ? ? public static void main(String[] args) {Ticket ticket = new Ticket();// 創建線程Thread thread1 = new Thread(ticket,"窗口1");Thread thread2 = new Thread(ticket,"窗口2");Thread thread3 = new Thread(ticket,"窗口3");thread1.start();thread2.start();thread3.start();}
方式二:
public class TicketDemo { ?static int ticket = 100; ?public static void saleTicket(){while (true){if (ticket <= 0) {break;}System.out.println(Thread.currentThread().getName() + "您的票是" + ticket--);}} } ? ? ?public static void main(String[] args) { // ? ? ? new Thread(()->{ // ? ? ? ? ? TicketDemo.saleTicket(); // ? ? ? },"窗口1").start();new Thread(TicketDemo::saleTicket,"窗口1").start();new Thread(TicketDemo::saleTicket,"窗口2").start();new Thread(TicketDemo::saleTicket,"窗口3").start();}
經過多次運行會發現以上的內容可能會出現以下結果:
有重復的票
有0的票
有-1的票
線程安全隱患:如果有多個線程在同時運行,而這些線程可能會同時運行某段代碼。程序每次運行的結果和單線程運行的結果一樣,而且其他的變量值也和預期的一樣,就是線程安全。否則就是有線程安全隱患。
線程安全隱患出現的條件:
多線程
有共享資源
有修改操作
以上三個條件破壞一個就可以避免線程安全隱患
3 線程安全隱患解決方案
3.1 同步代碼塊
synchronized(鎖對象){可能會產生線程安全問題的代碼; }
在同一個時刻,同步代碼塊中只能有一個線程執行,執行完畢之后會釋放鎖,所有線程再去搶鎖對象,搶到之后的線程可以執行同步代碼塊中的代碼,其他線程等待同步代碼塊執行完畢后釋放鎖。
同步代碼塊中的鎖對象可以是任意的對象,但是多個線程時,要使用同一個鎖對象才能夠保證線程安全。
3.2 同步方法
在方法上添加synchronized
public synchronized 返回值類型 ?方法名(參數){}
在同一個時刻,同步方法中只能有一個線程執行,執行完畢之后會釋放鎖,所有線程再去搶鎖對象,搶到之后的線程可以執行同步方法中的代碼,其他線程等待同步方法執行完畢后釋放鎖。
// 靜態同步方法// 鎖對象是 類名.classprivate synchronized void method2(){} ?// 同步方法 // 鎖對象是thisprivate synchronized boolean method() {if (ticket <= 0) {return true;} ?System.out.println(Thread.currentThread().getName() + "您的票是" + ticket--);return false;}
3.3 Lock
// 創建鎖Lock lock = new ReentrantLock(); ?@Overridepublic void run() {while (true) {// 線程休眠 單位:毫秒try {Thread.sleep(1L);} catch (InterruptedException e) {throw new RuntimeException(e);}// 加鎖lock.lock();try {if (ticket <= 0) {break;}System.out.println(Thread.currentThread().getName() + "您的票是" + ticket--);} finally {// 釋放鎖lock.unlock();} ?}}
同步:一段代碼只允許一個線程執行
異步:一段代碼允許多個線程執行
同步一定是線程安全的
線程安全不一定同步
異步不一定線程不安全
線程不安全一定是異步
3.4 ThreadLocal
從程序上游向下游傳遞數據
static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) {String str = "你好";threadLocal.set(str);a();}public static void a(){b();}private static void b() {c();}private static void c() {d();}private static void d() {String s = threadLocal.get();System.out.println(s);}
可以解決線程安全隱患
public static ThreadLocal<Printer> tl = new ThreadLocal<>() {// 匿名內部類// 重寫初始化方法,返回一個打印機@Overrideprotected Printer initialValue() {return new Printer();}};public static void main(String[] args) {new Thread(new GFS()).start();new Thread(new BFM()).start();}}class Printer {public void print(String str) {System.out.println("打印機在打印" + str);} }class BFM implements Runnable {@Overridepublic void run() {Printer printer = TestDemo4.tl.get();printer.print(printer + "我是白富美");printer.print(printer + "我很美");printer.print(printer + "很有錢");} }class GFS implements Runnable {@Overridepublic void run() {Printer printer = TestDemo4.tl.get();printer.print(printer + "我是高富帥");printer.print(printer + "我很高");printer.print(printer + "也很帥");} }
4 死鎖
死鎖:由于鎖的嵌套導致鎖之間相互鎖死的現象
public class TestDemo5 {static Print p = new Print();static Scan s = new Scan();public static void main(String[] args) {new Thread(()->{synchronized (p){p.print();try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (s){s.scan();}}}).start();new Thread(()->{synchronized (s){s.scan();synchronized (p){p.print();}}}).start();} }class Print{public void print(){System.out.println("打印機在打印");} }class Scan{public void scan(){System.out.println("掃描儀在掃描");} }
5 等待喚醒機制
package cn.javasm.demo;public class TestDemo6 {public static void main(String[] args) {Student student = new Student();new Thread(new Ask(student)).start();new Thread(new Change(student)).start();} }class Change implements Runnable{private Student student;public Change(Student student) {this.student = student;}@Overridepublic void run() {while (true){synchronized (student){if (student.flag){try {// 讓當前線程陷入等待student.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}if ("周杰倫".equals(student.getName())){student.setName("林志玲");student.setGender("女生");}else {student.setName("周杰倫");student.setGender("男生");}student.flag = true;// 隨機喚醒一個正在等待的線程student.notify();}}} }class Ask implements Runnable{private Student student;public Ask(Student student) {this.student = student;}@Overridepublic void run() {while (true){synchronized (student) {if (!student.flag){try {// 讓當前線程陷入等待student.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("老師,我是" + student.getName() + ",我是" + student.getGender() + ",我要問問題");student.flag = false;// 喚醒一個線程student.notify();}}} }class Student{private String name;private String gender;// true讓Ask執行// false讓Change執行public boolean flag;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;} }
作業:完成生產者消費者模式
一個線程作為消費者,一個線程作為生產者。生產者每生產一次,消費者就消費一次。生產者每次生產的商品數量以及消費者消費的數量使用隨機數產生即可。每一次的生產的商品數量和上一次剩余的商品數量之和不能超過1000。