多線程同步代碼塊詳解

      網友投稿 797 2022-05-30

      線程是程序執行的一條路徑, 一個進程中可以包含多條線程。多線程并發執行可以提高程序的效率,可以同時完成多項工作,多線程并發執行的實質就是CPU在做著高速的切換。多線程的應用場景:紅蜘蛛同時共享屏幕給多個電腦;迅雷開啟多條線程一起下載;QQ同時和多個人一起視頻;服務器同時處理多個客戶端請求。

      并行和并發的區別

      并行就是兩個任務同時運行,就是甲任務進行的同時,乙任務也在進行。(需要多核CPU)

      并發是指兩個任務都請求運行,而處理器只能按受一個任務,就把這兩個任務安排輪流進行,由于時間間隔較短,使人感覺兩個任務都在運行。

      比如我跟兩個網友聊天,左手操作一個電腦跟甲聊,同時右手用另一臺電腦跟乙聊天,這就叫并行。如果用一臺電腦我先給甲發個消息,然后立刻再給乙發消息,然后再跟甲聊,再跟乙聊。這就叫并發。

      Java程序運行原理

      Java命令會啟動java虛擬機,啟動JVM,等于啟動了一個應用程序,也就是啟動了一個進程。該進程會自動啟動一個 “主線程” ,然后主線程去調用某個類的 main 方法。JVM啟動至少啟動了垃圾回收線程和主線程,所以是多線程的。

      代碼驗證:

      public class Demo1_Thread { //此程序的運行結果是 "我是主線程的執行代碼"和"垃圾被清掃了"交替執行 /** 證明jvm是多線程的 */ public static void main(String[] args) { for(int i = 0; i < 100000; i++) { new Demo(); } for(int i = 0; i < 10000; i++) { System.out.println("我是主線程的執行代碼"); } } } class Demo { @Override public void finalize() { System.out.println("垃圾被清掃了"); } }

      方式一:定義類繼承Thread,重寫run方法,把新線程要做的事寫在run方法中;創建線程對象,開啟(調用start())新線程, 內部會自動執行run方法。

      代碼演示:

      public class Demo2_Thread { public static void main(String[] args) { MyThread mt = new MyThread(); //4,創建Thread類的子類對象 mt.start(); //5,開啟線程 MyThread mt1 = new MyThread(); mt1.start(); for(int i = 0; i < 1000; i++) { System.out.println("bb"); } } } class MyThread extends Thread { //1,繼承Thread public void run() { //2,重寫run方法 for(int i = 0; i < 1000; i++) { //3,將要執行的代碼寫在run方法中 System.out.println("aaaaaaaaaaaa"); } } }

      方式二:定義類實現Runnable接口,實現run方法,把新線程要做的事寫在run方法中,創建自定義的Runnable的子類對象,創建Thread對象,傳入Runnable,調用start()開啟新線程,內部會自動調用Runnable的run()方法。

      代碼演示:

      public class Demo3_Thread { public static void main(String[] args) { MyRunnable mr = new MyRunnable(); //4,創建Runnable的子類對象 Thread t = new Thread(mr); //5,將其當作參數傳遞給Thread的構造函數 t.start(); //6,開啟線程 for(int i = 0; i < 1000; i++) { System.out.println("bb"); } } } class MyRunnable implements Runnable { //1,定義一個類實現Runnable @Override public void run() { //2,重寫run方法 for(int i = 0; i < 1000; i++) { //3,將要執行的代碼寫在run方法中 System.out.println("aaaaaaaaaaaa"); } } }

      剖析實現Runnable接口的源碼

      源碼解釋:構造函數中傳入了Runnable的引用,成員變量記住了它,start()調用run()方法時內部判斷成員變量Runnable的引用是否為空,不為空編譯時看的是Runnable的run(),運行時執行的是子類的run()方法。

      源碼解析:

      public class Thread implements Runnable { //Thread類本身實現了Runnable接口 /*省略代碼*/ private Runnable target; //一個Runnable類型的成員變量 public Thread(Runnable target) {//Thread的有參構造方法,傳入一個Runnable接口的子類對象target //調用init方法(下面這個) 把target傳入 init(null, target, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize) { //init方法里面又調用另一個重載的init方法(下面這個)把target傳入 init(g, target, name, stackSize, null); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { /*省略代碼*/ this.target = target;//在init方法里面把成員變量target 進行賦值 /*省略代碼*/ } public synchronized void start() { //當調用start方法的時候 內部調用的是start0()方法 /*省略代碼*/ start0(); /*省略代碼*/ } private native void start0(); //start0()是native修飾的方法 底層是其他語言 我們無法查看,但是我們知道底層會調用run方法 @Override public void run() { //start0()的底層是會調用run()方法 if (target != null) { //判斷成員變量target是否為null target.run(); //如果target被賦值了,就要調用target的run方法 } } /*省略代碼*/ }

      多線程兩種實現方式的區別

      繼承Thread : 由于子類重寫了Thread類的run(),當調用start()時, 直接找子類的run()方法;好處是:可以直接使用Thread類中的方法,代碼簡單;弊端是:如果已經有了父類,就不能用這種方法。

      實現Runnable : 構造函數中傳入了Runnable的引用, 成員變量記住了它, start()調用run()方法時內部判斷成員變量Runnable的引用是否為空, 不為空編譯時看的是Runnable的run(),運行時執行的是子類的run()方法。好處是:即使自己定義的線程類有了父類也沒關系,因為有了父類也可以實現接口,而且接口是可以多實現的;弊端是:不能直接使用Thread中的方法需要先獲取到線程對象后,才能得到Thread的方法,代碼復雜。

      匿名內部類實現線程的兩種方式

      public static void main(String[] args) { new Thread() { //1,繼承Thread類 public void run() { //2,重寫run方法 for(int i = 0; i < 1000; i++) { //3,將要執行的代碼寫在run方法中 System.out.println("aaaaaaaaaaaaaa"); } } }.start(); //4,開啟線程 new Thread(new Runnable() { //1,將Runnable的子類對象傳遞給Thread的構造方法 public void run() { //2,重寫run方法 for(int i = 0; i < 1000; i++) { //3,將要執行的代碼寫在run方法中 System.out.println("bb"); } } }).start(); //4,開啟線程 }

      public final String getName():獲取線程對象的名稱。默認情況下,名字的組成 Thread-編號(編號從0開始)

      public final void setName(String name):設置線程名稱。

      代碼演示:

      //通過構造方法給name賦值 public static void demo1() { new Thread("芙蓉姐姐") { //通過構造方法給name賦值 public void run() { System.out.println(this.getName() + "....aaaaaaaaa"); } }.start(); } //通過setName()來設置線程的名字 public static void main(String[] args) { Thread t = new Thread() { public void run() { //this.setName("張三"); //在這兒設置也行 在外面設置名稱也行 System.out.println(this.getName() + "....aaaaaaaaaaaaa"); } }; t.setName("張三"); //通過setName()來設置線程的名字 t.start(); }

      public static Thread currentThread():返回當前正在執行的線程對象引用

      public static void sleep(long millis):讓當前線程休眠millis毫秒

      public final void setDaemon(boolean on):設置線程為守護線程,一旦前臺(主線程)結束,守護線程就結束了

      public final void join():當前線程暫停,等待指定的線程執行結束后,當前線程再繼續

      public final void join(long millis):當前線程暫停, 等待指定的線程執行millis毫秒結束后, 當前線程再繼續

      public static void yield():暫停當前正在執行的線程對象,并執行其他線程。

      public final int getPriority():獲取線程優先級

      public final void setPriority(int newPriority):更改線程的優先級。線程默認優先級是5。范圍是1-10。

      注意:優先級可以在一定的程度上,讓線程獲較多的執行機會。

      多線程與同步代碼塊詳解

      代碼演示:

      public static void main(String[] args) { final Thread t1 = new Thread() { public void run() { for(int i = 0; i < 10; i++) { System.out.println(getName() + "...aaaaaaaaaaaaa"); } } }; Thread t2 = new Thread() { public void run() { for(int i = 0; i < 10; i++) { if(i == 2) { try { t1.join(); //t1插隊后,t1執行結束后 t2才能接著執行 //t1.join(1); //插隊指定的時間,過了指定時間后,兩條線程交替執行 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(getName() + "...bb"); } } }; t1.start(); t2.start(); }

      當多線程并發, 有多段代碼同時執行時, 我們希望某一段代碼執行的過程中CPU不要切換到其他線程工作,這時就需要同步。如果兩段代碼是同步的,那么同一時間只能執行一段, 在一段代碼沒執行結束之前, 不會執行另外一段代碼。所謂同步代碼塊就是使用synchronized關鍵字加上一個鎖對象來定義一段代碼,這就叫同步代碼塊,多個同步代碼塊如果使用相同的鎖對象,那么他們就是同步的。

      代碼演示

      public class Demo_Synchronized { public static void main(String[] args) { //匿名內部類使用局部變量,局部變量前面必須加final修飾,為了延長局部變量的聲明周期 final Printer p = new Printer(); new Thread() { public void run() { while(true) { p.print1(); //調用print1() } } }.start(); new Thread() { public void run() { while(true) { p.print2(); //調用print2() } } }.start(); } } class Printer { Demo d = new Demo(); public void print1() { synchronized(d) { //同步代碼塊,鎖機制,鎖對象可以是任意的 //當多線程并發, 有多段代碼同時執行時 //我們希望下面代碼執行的過程中CPU不要切換到其他線程工作 System.out.print("黑"); System.out.print("馬"); System.out.print("程"); System.out.print("序"); System.out.print("員"); System.out.print("\r\n"); } } public void print2() { synchronized(d) {//如果說兩塊代碼想同步的話,那么這兩個同步代碼塊的鎖對象必須是同一個鎖對象,所以說上面的鎖對象是d,這一個鎖對象也是d //我們希望下面代碼執行的過程中CPU不要切換到其他線程工作 System.out.print("傳"); System.out.print("智"); System.out.print("播"); System.out.print("客"); System.out.print("\r\n"); } } } class Demo{}

      同步方法

      使用synchronized關鍵字修飾一個方法,該方法中所有的代碼都是同步的,非靜態同步函數的鎖是當前對象this,靜態的同步函數的鎖是當前類的字節碼對象。

      代碼演示:

      public class Demo_Synchronized { public static void main(String[] args) { //匿名內部類使用局部變量,局部變量前面必須加final修飾,為了延長局部變量的聲明周期 final Printer2 p = new Printer2(); new Thread() { public void run() { while(true) { p.print1(); } } }.start(); new Thread() { public void run() { while(true) { p.print2(); } } }.start(); } } class Printer2 { Demo d = new Demo(); //非靜態的同步方法的鎖對象是神馬? //答:非靜態的同步方法的鎖對象是this //靜態的同步方法的鎖對象是什么? //是該類的字節碼對象 public static synchronized void print1() {//同步方法只需要在方法上加synchronized關鍵字即可 System.out.print("黑"); System.out.print("馬"); System.out.print("程"); System.out.print("序"); System.out.print("員"); System.out.print("\r\n"); } public static void print2() { synchronized(Printer2.class) { System.out.print("傳"); System.out.print("智"); System.out.print("播"); System.out.print("客"); System.out.print("\r\n"); } } }

      死鎖

      同步代碼塊的嵌套就容易出現死鎖。所以開發中盡量避免同步代碼塊的嵌套。

      代碼演示:

      public class Demo5_DeadLock { /** @param args */ private static String s1 = "筷子左"; private static String s2 = "筷子右"; public static void main(String[] args) { new Thread() { public void run() { while(true) { synchronized(s1) { System.out.println(getName() + "...獲取" + s1 + "等待" + s2); synchronized(s2) { System.out.println(getName() + "...拿到" + s2 + "開吃"); } } } } }.start(); new Thread() { public void run() { while(true) { synchronized(s2) { System.out.println(getName() + "...獲取" + s2 + "等待" + s1); synchronized(s1) { System.out.println(getName() + "...拿到" + s1 + "開吃"); } } } } }.start(); } }

      Vector,StringBuffer,Hashtable這些類之所以說是同步的,是因為他們里面的方法上都加了synchronized

      Collections.synchroinzedXxx(xxx):可以返回一個線程安全的集合

      public static Collection synchronizedCollection(Collection c)

      public static Set synchronizedSet(Set s)

      public static List synchronizedList(List list)

      public static Map synchronizedMap(Map m)

      注:Vector是線程安全的,ArrayList是線程不安全的

      StringBuffer是線程安全的,StringBuilder是線程不安全的

      Hashtable是線程安全的,HashMap是線程不安全的

      任務調度 多線程

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:關于 Kubernetes中kube-controller-managerr的一些筆記
      下一篇:如何做好軟件開發工作的思考
      相關文章
      亚洲国产综合无码一区二区二三区| 亚洲不卡1卡2卡三卡2021麻豆| 亚洲欧洲日韩极速播放| 亚洲综合激情九月婷婷| 亚洲国产三级在线观看| 亚洲黄片手机免费观看| 亚洲av无码天堂一区二区三区| 自拍偷自拍亚洲精品偷一| 国产亚洲精品2021自在线| 国产精品亚洲专区一区| 国产偷国产偷亚洲高清人| 日产国产精品亚洲系列| 亚洲av无码专区在线观看素人| 天天综合亚洲色在线精品| 国产成人亚洲综合无| 亚洲人妻av伦理| 亚洲综合精品网站在线观看| 亚洲乱码国产一区网址| 久久久久亚洲精品男人的天堂| 久久亚洲高清综合| 亚洲精品无码久久一线| 亚洲精品无码久久一线| 亚洲国产综合91精品麻豆| 久久精品a亚洲国产v高清不卡| 亚洲va在线va天堂va888www| 亚洲第一精品在线视频| 亚洲视频在线不卡| 亚洲乱码中文论理电影| 亚洲fuli在线观看| 亚洲精品无码成人片久久不卡| 亚洲AV无码一区二区三区网址| 国产亚洲精品成人久久网站| 亚洲AV无码资源在线观看| 亚洲AV无码专区亚洲AV桃| 伊在人亚洲香蕉精品区麻豆| 国产精品亚洲精品久久精品| 亚洲精品动漫人成3d在线| 亚洲精品国产美女久久久| 亚洲午夜久久久久久久久久| 亚洲国产精品线在线观看| 亚洲午夜国产精品无卡|