溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java中怎么實現(xiàn)線程間共享

發(fā)布時間:2021-08-06 17:04:52 來源:億速云 閱讀:118 作者:Leah 欄目:編程語言

這篇文章給大家介紹Java中怎么實現(xiàn)線程間共享,內(nèi)容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

一、synchronize對象鎖和類鎖

synchronize為多線程關鍵字是一種同步鎖,它可以修飾以下幾種對象:

代碼塊:被修飾的代碼塊被稱為同步代碼塊,作用的范圍是{}里面的代碼,作用的對象是調(diào)用這個代碼塊的對象

方法:被修飾的方法稱為同步方法,作用的范圍是整個方法,作用的對象是調(diào)用這個方法的對象

類:作用的范圍是synchronize后面括號里的部分,作用的對象是當前這個類

1、對象鎖

下面由一個栗子引入:

public class TestSynchronize {  //加了對象鎖的方法  private synchronized void syn(){    //自定義sleep工具類    SleepTools.second(2);    System.out.println("syn is going..."+this.toString());    SleepTools.second(2);    System.out.println("syn ended..."+this.toString());  }  //調(diào)用了對象鎖方法的線程1  private static class thread implements Runnable{    private TestSynchronize testSynchronize;    public thread(TestSynchronize testSynchronize){      this.testSynchronize = testSynchronize;    }    @Override    public void run() {      System.out.println("thread is running...");      testSynchronize.syn();    }  }  //調(diào)用了對象鎖方法的線程2  private static class thread2 implements Runnable{    private TestSynchronize testSynchronize;    public thread2(TestSynchronize testSynchronize){      this.testSynchronize = testSynchronize;    }    @Override    public void run() {      System.out.println("thread2 is running...");      testSynchronize.syn();    }  }  public static void main(String[] args) {    TestSynchronize testSynchronize = new TestSynchronize();    thread thread = new thread(testSynchronize);    TestSynchronize testSynchronize2 = new TestSynchronize();    thread2 thread2 = new thread2(testSynchronize);    //thread2 thread2 = new thread2(testSynchronize2);    new Thread(thread).start();    new Thread(thread2).start();  }}/**當兩個線程都將testSynchronize傳入時(即使用同一個對象調(diào)用加了對象鎖的方法)運行結果如下:thread is running...thread2 is running...syn is going...com.zl.synchronize.TestSynchronize@6b52350csyn ended...com.zl.synchronize.TestSynchronize@6b52350csyn is going...com.zl.synchronize.TestSynchronize@6b52350csyn ended...com.zl.synchronize.TestSynchronize@6b52350c*//**當一個傳入testSynchronize,另一個傳入testSynchronize2時 運行結果如下:thread is running...thread2 is running...syn is going...com.zl.synchronize.TestSynchronize@28835f5fsyn is going...com.zl.synchronize.TestSynchronize@47c48106syn ended...com.zl.synchronize.TestSynchronize@28835f5fsyn ended...com.zl.synchronize.TestSynchronize@47c48106*/

結論:多個線程調(diào)用同一個對象的同步方法會阻塞,調(diào)用不同對象的同步方法不會阻塞

2、類鎖

1) synchronized修飾的靜態(tài)方法

public static synchronized void obj3() {  int i = 5;  while (i-- > 0) {    System.out.println(Thread.currentThread().getName() + " : " + i);    try {      Thread.sleep(500);    } catch (InterruptedException ie) {    }  }}

2) synchronized (test.class) ,鎖的對象是test.class,即test類的鎖。

public void obj1() {  synchronized (test.class) {    int i = 5;    while (i-- > 0) {      System.out.println(Thread.currentThread().getName() + " : " + i);      try {        Thread.sleep(500);      } catch (InterruptedException ie) {      }    }  }}

那么問題來了:在一個類中有兩方法,分別用synchronized 修飾的靜態(tài)方法(類鎖)和非靜態(tài)方法(對象鎖)。多線程訪問兩個方法的時候,線程會不會阻塞?

答案是當類鎖和對象鎖同時存在時,多線程訪問時不會阻塞,因為他們不是一個鎖。

二、volatile

volatile 是一個類型修飾符。volatile 的作用是作為指令關鍵字,確保本條指令不會因編譯器的優(yōu)化而省略。

volatile的特性

保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。(實現(xiàn)可見性)  禁止進行指令重排序。(實現(xiàn)有序性)  volatile 只能保證對單次讀/寫的原子性。i++ 這種操作不能保證原子性。

三、ThreadLocal

ThreadLocal從字面意思來理解,是一個線程本地變量,也可以叫線程本地變量存儲。有時候一個對象的變量會被多個線程所訪問,這個時候就會有線程安全問題,當然可以使用synchronized關鍵字來為該變量加鎖,進行同步處理來限制只能有一個線程來使用該變量,但是這樣會影響程序執(zhí)行的效率,這時ThreadLocal就派上了用場;    使用ThreadLocal維護變量的時候,會為每一個使用該變量的線程提供一個獨立的變量副本,即每個線程內(nèi)部都會有一個當前變量。這樣同時有多個線程訪問該變量并不會相互影響,因為他們都是使用各自線程存儲的變量,所以不會存在線程安全的問題。    同步機制采用了“以時間換空間”的方式,而ThreadLocal采用了“以空間換時間”的方式,前者僅提供一份變量,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問且互不影響。

下面給出測試程序:

public class ThreadLocalDemo {  private static ThreadLocal<Integer> number = new ThreadLocal<Integer>(){    @Override    protected Integer initialValue() {      return 1;    }  };  private static class thread extends Thread{    @Override    public void run() {      Integer number = ThreadLocalDemo.number.get();      for (int i = 0; i < this.getId(); i++) {        number++;      }      System.out.println(this.getName()+"---"+this.getId()+"===="+number);    }  }  private static class thread2 extends Thread{    @Override    public void run() {      Integer number = ThreadLocalDemo.number.get();      for (int i = 0; i < this.getId(); i++) {        number++;      }      System.out.println(this.getName()+"---"+this.getId()+"===="+number);    }  }  public static void main(String[] args) {    new Thread(new thread()).start();    new Thread(new thread2()).start();  }}/**Thread-0---12====13Thread-2---14====15*/

四、等待(Wait)和通知(notify)

為了支撐多線程之間的協(xié)作,JDK提供了兩個非常重要的線程接口:等待wait()方法和通知notify()方法。 這兩個方法并不是在Thread類中的,而是輸出在Object類。這意味著任何對象都可以調(diào)用這兩個方法。

等待/通知的經(jīng)典范式

wait()方法和notify()方法究竟是如何工作的呢?

  如果一個線程調(diào)用了object.wait()方法,那么它就會進入object對象的等待隊列,這個隊列中,可能會有多個線程,因為系統(tǒng)運行多個線程同時等待某一個對象,

  當object.notify()方法被調(diào)用的時候,它就會從這個等待隊列中隨機選擇一個線程,并進行喚醒。

  除notity()方法外,Object對象還有一個類似的notifyAll()方法,它和notity方法的功能基本一致,不同的是,它會喚醒在這個等待隊列中所有等待的線程,而不是隨機一個。

  object.wait()方法并不能隨便調(diào)用。它必須包含在對象的synchronzied語句中,無論是wait()方法或者notity()方法都需要首先獲得目標對象的一個監(jiān)視器。

假設有T1和T2表示兩個線程,T1在正確執(zhí)行wait()方法前,必須獲得object對象的監(jiān)視器,而wait()方法執(zhí)行之后會釋放這個監(jiān)視器。

這樣做的目的是使其他等待在object對象上的線程不至于因為T1的休眠而全部無法正常執(zhí)行。

  線程T2在notity()方法調(diào)用前,也必須獲得object對象的監(jiān)視器。此時T1已經(jīng)釋放了這個監(jiān)視器。所以T2可以順利獲得object對象的監(jiān)視器。

接著,T2執(zhí)行了notify()方法嘗試喚醒一個等待線程,這里假設喚醒了T1,T1被喚醒后,要做的第一件事并不是執(zhí)行后續(xù)代碼,而是要嘗試重新

獲得object對象的監(jiān)視器,而這個監(jiān)視器也正是T1在wait()方法執(zhí)行前所持有的那個。

如果暫時無法獲得,則T1還必須等待這個監(jiān)視器。當監(jiān)視器順利獲得后,T1才可以在真正意義上繼續(xù)執(zhí)行。

這里要注意,只有當wait()和notify()被包含的synchronized語句執(zhí)行完,才會釋放監(jiān)視器。

為了方便理解,簡單的案例:

public class testWaitAndNotify {  final static Object object = new Object();  public static class T1 extends Thread {    public void run() {      synchronized (object) {        try {          System.out.println(System.currentTimeMillis() + ":T1 start! ");          System.out.println(System.currentTimeMillis() + ":T1 wait for object");          object.wait();        } catch (InterruptedException e) {          e.printStackTrace();        }        System.out.println(System.currentTimeMillis() + ":T1 end! ");      }    }  }  public static class T2 extends Thread {    public void run() {      synchronized (object) {        try {          System.out.println(System.currentTimeMillis() + ":T2 start! notify one thread");          object.notify();          sleep(5000);          System.out.println(System.currentTimeMillis() + ":T2 end! ");        } catch (InterruptedException e) {          e.printStackTrace();        }      }    }  }  public static void main(String[] args) {    Thread t1 = new T1();    Thread t2 = new T2();    t1.start();    t2.start();  }}/**1571039516250:T1 start! 1571039516250:T1 wait for object1571039516251:T2 start! notify one thread1571039521251:T2 end! 1571039521251:T1 end! */

五、等待超時模式

由于經(jīng)典的等待/通知范式無法做到超時等待,也就是說,當消費者在獲得鎖后,如果條件不滿足,等待生產(chǎn)者改變條件之前會一直處于等待狀態(tài),在一些實際應用中,會浪費資源,降低運行效率。

偽代碼如下所示:

//假設超時時間是mills,則等待持續(xù)時間是remaining,超時時間是futurelong future = System.currentTimeMillis() + mills;long remaining = mills;synchronized (lock) {  while (!condition && remaining > 0) {    wait(remaining);    remaining = future - System.currentTimeMillis();  }  //處理代碼}

六、join()

join在線程里面意味著“插隊”,哪個線程調(diào)用join代表哪個線程插隊先執(zhí)行——但是插誰的隊是有講究了,不是說你可以插到隊頭去做第一個吃螃蟹的人,而是插到在當前運行線程的前面,比如系統(tǒng)目前運行線程A,在線程A里面調(diào)用了線程B.join方法,則接下來線程B會搶先在線程A面前執(zhí)行,等到線程B全部執(zhí)行完后才繼續(xù)執(zhí)行線程A。

話不多說上代碼

public class TestJoin {  private static class thread extends Thread{    private Thread t;    //接收一個插隊線程    public thread(Thread t){      this.t = t;    }    @Override    public void run() {      try {        //調(diào)用插隊線程的join方法        t.join();      } catch (InterruptedException e) {        e.printStackTrace();      }      System.out.println(getName()+"---執(zhí)行完畢!");    }  }  public static void main(String[] args) throws InterruptedException {    //獲取當前線程作為前一個線程    Thread pre = Thread.currentThread();    //創(chuàng)建五個線程    for (int i = 0; i < 5; i++) {      Thread thread = new Thread(new thread(pre),String.valueOf(i));      //啟動線程      thread.start();      //重置前一個線程      pre = thread;    }    System.out.println(System.currentTimeMillis());    //讓主線程睡眠2s    Thread.currentThread().sleep(2000);    System.out.println(System.currentTimeMillis());    System.out.println(Thread.currentThread().getName()+"---執(zhí)行完畢");  }}/**15710611680641571061170065main---執(zhí)行完畢Thread-0---執(zhí)行完畢!Thread-1---執(zhí)行完畢!Thread-2---執(zhí)行完畢!Thread-3---執(zhí)行完畢!Thread-4---執(zhí)行完畢!*/

關于Java中怎么實現(xiàn)線程間共享就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI