歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Java中synchronized關鍵字實現線程同步互斥

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

日期:2017/3/1 9:08:58   编辑:Linux編程

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