歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Java中synchronized關鍵字實現線程同步互斥

Java多線程程序現在很常見,和數據庫操作系統一樣,多個線程會共享一個堆內存,如果不加以控制,不進行線程之間的同步,會造成數據混亂等。

先看看下面這個程序:

public class TestSynchronized implements Runnable {
 Timer timer = new Timer();

 public static void main(String args[]) {
  TestSynchronized test = new TestSynchronized();
  Thread t1 = new Thread(test);
  Thread t2 = new Thread(test);
  t1.setName("t1");
  t2.setName("t2");
  t1.start();
  t2.start();
 }

 public void run() {
  timer.add(Thread.currentThread().getName());
 }
}

class Timer {
 private static int num = 0;

 public void add(String name) {
  // synchronized(this){ //沒有同步,將導致num的數目不正常
  num++;
  try {
   Thread.sleep(1);
  } catch (Exception e) {
  }
  System.out.println(name + ", 你是第" + num + "個使用timer的線程");
  // }
 }
}輸出的結果:

t1, 你是第2個使用timer的線程
t2, 你是第2個使用timer的線程

num兩次都是2。這裡很明顯是兩個線程在訪問同一個Timer對象的num變量時交替進行,所以最後打印的時候都是2;

如何實現不同線程之間互斥訪問這個num呢,使用synchronized同步代碼塊即可解決,用synchronized(this){}將需要同步代碼塊圈起來。

輸出正常結果:

t2, 你是第1個使用timer的線程
t1, 你是第2個使用timer的線程

 


synchronized的另一個用法是同步方法,就是在方法之前加上修飾符synchronized。那麼不同線程在訪問同一個對象的synchronized方法時就會互斥訪問,從而達到同步

下面是一個經典的生產消費者模型代碼:


public class TestSynchronized {
 public static void main(String[] args) {
  SyncStack ss = new SyncStack();
  Producer p = new Producer(ss);
  Consumer c = new Consumer(ss);
  Thread tp = new Thread(p);
  Thread tc = new Thread(c);
  tp.start();
  tc.start();
 }
}

class Bread {
 int id;

 Bread(int id) {
  this.id = id;
 }

 public String toString() {
  return "Bread: " + id;
 }
}

class SyncStack {
 int index = 0;
 Bread[] breads = new Bread[6];

 public synchronized void push(Bread b) {
  if (index == breads.length) {// 注意只能用while不能用if;因為wait會異常
   try {
    this.wait();// 訪問當前對象的線程wait()
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
  this.notify();
  breads[index] = b;
  index++;
  System.out.println("生產了:" + b);
 }

 public synchronized Bread pop() {
  if (index == 0) {
   try {
    this.wait();// wait的時候鎖已經不歸該線程所有,但是sleep還有鎖
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
  this.notify();// 叫醒一個正在該對象上wait的一個線程
  index--;
  System.out.println("消費了:" + breads[index]);
  return breads[index];
 }
}

class Producer implements Runnable {
 SyncStack ss = null;

 Producer(SyncStack ss) {
  this.ss = ss;
 }

 public void run() {
  for (int i = 0; i < 20; i++) {
   Bread b = new Bread(i);
   ss.push(b);
   // System.out.println("生產了:"+b);
   try {
    Thread.sleep((int) Math.random() * 1000);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
 }
}

class Consumer implements Runnable {
 SyncStack ss = null;

 Consumer(SyncStack ss) {
  this.ss = ss;
 }

 public void run() {
  for (int i = 0; i < 20; i++) {
   Bread b = null;
   b = ss.pop();
   // System.out.println("消費了:"+breads[index]);//放到這裡用if的話會出問題,先打出消費0,再打出生產0
   try {
    Thread.sleep((int) Math.random() * 1000);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
 }
}

同步棧對象SyncStack的push和pop方法就是同步方法。生產者線程和消費者線程將會互斥訪問pop和push方法。

下面說一下這種同步機制的實現。java為每個類對象分配一個獨一無二的對象鎖,每個線程要訪問這個對象的成員就需要獲得這個鎖(當然這裡是指需要同步的線程)。因為一個對象只有一個對象鎖,所以一個線程在拿到鎖之後,另一個訪問相同對象的線程必須等待前者執行完成後釋放掉對象鎖,以此實現互斥。也就是說synchronized是針對對象的,不是針對類的。看看上面的圖形。ObjectA和ObjectB是同一個類的不同實例,所以ThreadAn和ThreadBn沒有關系。只有ThreadA1234同步,ThreadB1234亦是如此。並且對於非synchronized的方法和代碼塊,並沒有影響,沒有互斥

那有沒有實現類級別上的同步呢?有的。在 Java 中,不光是類實例,每一個類也對應一把鎖,這樣我們也可將類的靜態成員函數聲明為 synchronized ,以控制其對類的靜態成員變量的訪問。  使用同步代碼塊是synchronized(classname.class){}

最後幾點總結:

1)是某個對象實例內,synchronized aMethod(){}可以防止多個線程同時訪問這個對象的synchronized方法(如果一個對象有多個synchronized方法,只要一個線程訪問了其中的一個synchronized方法,其它線程不能同時訪問這個對象中任何一個synchronized方法)。這時,不同的對象實例的synchronized方法是不相干擾的。也就是說,其它線程照樣可以同時訪問相同類的另一個對象實例中的synchronized方法;
2)是某個類的范圍,synchronized static aStaticMethod{}防止多個線程同時訪問這個類中的synchronized static 方法。它可以對類的所有對象實例起作用。
2、除了方法前用synchronized關鍵字,synchronized關鍵字還可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。用法是: synchronized(this){/*區塊*/},它的作用域是當前對象;
3、synchronized關鍵字是不能繼承的,也就是說,基類的方法synchronized f(){} 在繼承類中並不自動是synchronized f(){},而是變成了f(){}。繼承類需要你顯式的指定它的某個方法為synchronized方法;


對synchronized(this)的一些理解

一、當兩個並發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執      行完這個代碼塊以後才能執行該代碼塊。 

二、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。 

三、尤其關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被      阻塞。 

四、第三個例子同樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結          果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。 

五、以上規則對其它對象鎖同樣適用

更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2016-11/137565p2.htm

Copyright © Linux教程網 All Rights Reserved