首頁  >  文章  >  Java  >  詳解Java多執行緒程式設計中的執行緒同步方法

詳解Java多執行緒程式設計中的執行緒同步方法

高洛峰
高洛峰原創
2017-01-05 16:32:071455瀏覽

1、多執行緒的同步:
1.1、同步機制:
在多執行緒中,可能有多個執行緒試圖存取一個有限的資源,必須預防這種情況的發生。所以引入了同步機制:在執行緒使用一個資源時為其加鎖,這樣其他的執行緒便不能存取那個資源了,直到解鎖後才可以存取。

1.2、共享成員變數的例子:
成員變數與局部變數:
成員變數:

如果一個變數是成員變量,那麼多執行緒對同一個物件的成員變數進行操作,這多個執行緒是共享一個成員變數的。

局部變數:

如果一個變數是局部變量,那麼多個執行緒對同一個物件進行操作,每個執行緒都會有一個該局部變數的拷貝。他們之間的局部變數互不影響。

下面舉例說明:
實作了Runnable的執行緒類別:

class MyThread3 implements Runnable{
 
 //两个线程操作同一个对象,共享成员变量
 //int i;
 @Override
 public void run() {
  //两个线程操作同一个对象,各自保存局部变量的拷贝
  int i = 0;
  while(i<100){
   System.out.println(i);
   i++;
   try {
    Thread.sleep(100);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
 }
}

在main方法中用兩個執行緒操作同一個物件:

public static void main(String[] args) {
 
 MyThread3 myThread = new MyThread3();
 //下面两个线程对同一个对象(Runnable的实现类对象)进行操作
 Thread thread = new Thread(myThread);
 Thread thread2 = new Thread(myThread);
 //各自保存局部变量的拷贝,互不影响,输出200个数字
 thread.start();
 thread2.start();
}

這裡如果把i變成成員變量,則輸出100個數字。

1.3、共享資源導致的讀取錯誤
下面舉個例子,兩個線程共用一個Number對象,透過Number類的getNumber方法獲取數據,讀取數據並改寫時,發現了重複讀取操作:

首先建立一個Number類:

class Number{
 private int number = 10;
 public String getNumber(int i){
  if(number > 0){
   try {
    Thread.sleep(100);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   number -= i;
   return "取出"+i+"成功,剩余数量:"+number;
  }
  return "取出"+i+"失败,剩余数量:"+number;
 }
}

執行緒類,在執行緒類別中的私有屬性包含了Number類的參考:

class MyThread4 extends Thread{
 
 //两个线程操作同一个对象,共享成员变量
 Number number;
 public MyThread4(Number number){
  this.number = number;
 }
 @Override
 public void run() {
  System.out.println(number.getNumber(8));
 }
}

在main函數中建立兩個執行緒類,包含了同一個Number類實例的參考:

public static void main(String[] args) {
 
 Number number = new Number();
 //两个线程操作同一个对象,共享对象number的成员变量number
 MyThread4 myThread = new MyThread4(number);
 MyThread4 myThread2 = new MyThread4(number);
 myThread.start();
 myThread2.start();
}

這樣,當第一個執行緒讀取Number中的number變數時先儲存下來再休眠0.1秒,然後第二個執行緒再讀取number變數並儲存,此時兩個執行緒保存了同樣的數字,在修改時,也就導致修改了同一個數字兩次。

2、同步機制的實現:

在多線程並發編程中Synchronized一直是元老級角色,很多人都會稱呼它為重量級鎖,但是隨著Java SE1.6對Synchronized進行了各種優化之後,有些情況下它不那麼重了”
Java中的每一個物件都可以作為鎖。

對於同步方法,鎖是當前實例物件。
對於靜態同步方法,鎖是當前物件的Class物件。
對於同步方法區塊,鎖是Synchonized括號裡配置的物件。 :
使用synchronized關鍵字,該關鍵字修飾的方法叫做同步方法。 ,而不僅僅是為該方法上鎖。 synchronized方法執行完。執行緒就不能呼叫該類別的其他靜態synchronized方法了,但是可以呼叫非靜態的synchronized方法。
下面是多執行緒呼叫靜態的方法的例子,由於鎖定了方法所在物件對應的Class對象,其他執行緒無法呼叫該方法所在物件其他的靜態synchronized方法:

/**
 * 定义一个类,包含了线程类需要调用的方法
 */
class Compute1{
 //这时如果某个线程调用该方法,
 //将锁定synchronized方法所在对象对应的class对象,
 //而不是锁定synchronized方法所在对象
 public synchronized static void execute(){
  for(int i = 0; i<100; i++){
   try {
    Thread.sleep(100);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   System.out.println("compute1:execute1 " + i++);
  }
 }
 public synchronized static void execute2(){
  for(int i = 0; i<100; i++){
   try {
    Thread.sleep(100);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   System.out.println("compute1:execute2 " + i++);
  }
 }
}

main方法中兩個執行緒分別呼叫同一個物件的兩個static synchronized方法:

public static void main(String[] args) {
 Compute1 com = new Compute1();
 Thread thread1 = new Thread1(com);
 Thread thread2 = new Thread2(com);
 thread1.start();
 thread2.start();
}

一次只能呼叫一個靜態方法,直到執行完成。執行的標誌從而達到同步的效果:

/**
 * 定义一个类,包含了线程类需要调用的方法
 */
class Compute1{
 //通过同步代码块锁定object1对象进行锁定了其他同样的synchronized代码块
 private Object object1 = new Object();
 public void execute(){
  synchronized(object1){
   for(int i = 0; i<100; i++){
    try {
     Thread.sleep(100);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    System.out.println("compute1:execute1 " + i++);
   }
  }
 
 }
 public synchronized void execute2(){
  synchronized(object1){
   for(int i = 0; i<100; i++){
    try {
     Thread.sleep(100);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    System.out.println("compute1:execute2 " + i++);
   }
  }
 }
}

如果想要使用synchronized同步代碼塊達到和使用synchronized方法同樣的效果,可以鎖定this引用:

synchronized(this){
 …
}

2.3、synchronized方法和synchronized同步代碼

塊的區別: synchronized同步程式碼區塊只是鎖定了該程式碼區塊,程式碼區塊外面的程式碼還是可以存取的。

synchronized方法是粗粒度的並發控制,某一個時刻只能有一個執行緒來執行該synchronized方法。

synchronized同步程式碼區塊是細粒度的並發控制,只會將區塊中的程式碼同步,程式碼區塊以外的程式碼可以被其他執行緒同時存取。

更多詳解Java多執行緒程式設計中的執行緒同步方法相關文章請關注PHP中文網!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn