對于多線程共享資源的情況須要進行同步,以避免一個線程的修改被還有一個線程的修改所覆蓋。
最普遍的同步方式就是synchronized。
把代碼聲明為synchronized。有兩個重要后果,一般是指該代碼具有 原子性(atomicity)和 可見性(visibility)。
1、原子性強調的是運行。意味著個時刻,僅僅有一個線程可以運行一段代碼,這段代碼通過一個monitor object保護。從而防止多個線程在更新共享狀態時相互沖突。
2、可見性強調的是結果。它要對付內存緩存和編譯器優化的各種反常行為。它必須確保釋放鎖之前對共享數據做出的更改對于隨后獲得該鎖的還有一個線程是可見的。
同步方法
看一個樣例就明確了:
import java.util.Random;
public class TestSyncClass {
?private int num=0;
?
?private Random random=new Random();
?
?public synchronized void testAdd1(){
??System.out.println("testAdd1--->>");
??num++;
??try {
???Thread.sleep(1000);
??} catch (InterruptedException e) {
???e.printStackTrace();
??}
??System.out.println("1-result-->>"+num);
?}
?
?public synchronized void testAdd2(){
??System.out.println("testAdd2--->>");
??num++;
??try {
???Thread.sleep(1000);
??} catch (InterruptedException e) {
???e.printStackTrace();
??}
??System.out.println("2-result-->>"+num);
?}
?
?public? void testAdd3(){
??System.out.println("testAdd3--->>");
??num++;
??try {
???Thread.sleep(1000);
??} catch (InterruptedException e) {
???e.printStackTrace();
??}
??System.out.println("3-result-->>"+num);
?}
?
?public static void main(String[] args) {
??final TestSyncClass syncClass=new TestSyncClass();
??Thread thread1=new Thread(){
???@Override
???public void run() {
????syncClass.testAdd1();
????super.run();
???}
??};
??
??Thread thread2=new Thread(){
???@Override
???public void run() {
????syncClass.testAdd2();
????super.run();
???}
??};
??
??Thread thread3=new Thread(){
???@Override
???public void run() {
????syncClass.testAdd3();
????super.run();
???}
??};
??
??thread1.start();
??thread2.start();
??thread3.start();
?}
}
代碼執行結果:
testAdd1--->>
testAdd3--->>
1-result-->>2
3-result-->>2
testAdd2--->>
2-result-->>3
代碼中testAdd1、testAdd2是被synchronized聲明的方法。testAdd3沒有聲明。在執行的時候因為testAdd3沒有被聲明,所以在緊跟著開始執行testAdd1的時候也執行了testAdd3。結果testAdd1執行的結果被testAdd3的結果覆蓋了,打印了同樣的值3。這個主要是可見性的問題。因為testAdd2也是被聲明過的,所以testAdd2并沒有馬上執行。而是等testAdd1執行完之后才開始執行。
全部對象都自己主動含有單一的鎖(也稱為監視器monitor object)。
當在對象上調用其隨意synchronized方法的時候,此對象都被加鎖。這時該對象上的其它synchronized方法僅僅有等到前一個方法調用完成并釋放了鎖之后才干被調用。
針對每一個類,也有一個鎖(作為類的Class對象的一部分)。所以synchronized static 方法能夠在類的范圍內防止對static數據的并發訪問。
同步塊
不管何種情況,要想鎖住代碼,必須使用同樣的鎖。比如把testAdd2改成
??????? private Object object=new Object();
?public void testAdd2(){
??synchronized(object){
???System.out.println("testAdd2--->>");
???num++;
???try {
????Thread.sleep(1000);
???} catch (InterruptedException e) {
????e.printStackTrace();
???}
???System.out.println("2-result-->>"+num);
??}
?}
則testAdd2和testAdd1就不會相互等待了。結果例如以下:
testAdd2--->>
testAdd3--->>
testAdd1--->>
3-result-->>3
2-result-->>3
1-result-->>3
事實上synchronized(object)是更安全的上鎖方式。由于直接聲明方法的形式用的是類的鎖,而聲明代碼塊的形式用的是私有屬性的鎖,尤其是在server開發的時候,外面類的鎖非常easy被黑客獲取。從而獲取了攻擊server的入口,而私有屬性的私有性讓黑客難以獲取,所以它的鎖就相對安全的多。
類同步
上面的main方法的三個線程用的是同一個TestSyncClass syncClass對象。假設每一個線程都各自創建一個對象就不能達到鎖定代碼的目標了。要想達到同步的目的,代碼須要改動成例如以下:
import java.util.Random;
public class TestSyncClass {
?private int num = 0;
?private static Object object = new Object();
?private Random random = new Random();
?public void testAdd1() {
??synchronized (object) {
???System.out.println("testAdd1--->>");
???num++;
???try {
????Thread.sleep(1000);
???} catch (InterruptedException e) {
????e.printStackTrace();
???}
???System.out.println("1-result-->>" + num);
??}
?}
?public void testAdd2() {
??synchronized (object) {
???System.out.println("testAdd2--->>");
???num++;
???try {
????Thread.sleep(1000);
???} catch (InterruptedException e) {
????e.printStackTrace();
???}
???System.out.println("2-result-->>" + num);
??}
?}
?public void testAdd3() {
??System.out.println("testAdd3--->>");
??num++;
??try {
???Thread.sleep(1000);
??} catch (InterruptedException e) {
???e.printStackTrace();
??}
??System.out.println("3-result-->>" + num);
?}
?public static void main(String[] args) {
??Thread thread1 = new Thread() {
???@Override
???public void run() {
????TestSyncClass syncClass = new TestSyncClass();
????syncClass.testAdd1();
????super.run();
???}
??};
??Thread thread2 = new Thread() {
???@Override
???public void run() {
????TestSyncClass syncClass = new TestSyncClass();
????syncClass.testAdd2();
????super.run();
???}
??};
??Thread thread3 = new Thread() {
???@Override
???public void run() {
????TestSyncClass syncClass = new TestSyncClass();
????syncClass.testAdd3();
????super.run();
???}
??};
??thread1.start();
??thread2.start();
??thread3.start();
?}
}
執行結果:
testAdd1--->>
testAdd3--->>
3-result-->>1
1-result-->>1
testAdd2--->>
2-result-->>1
事實上使用synchronized (TestSyncClass.class)類的鎖也能達到類似的效果,可是考慮到私有屬性的安全性就直接使用上面代碼做實例了。
注意:synchronized是不能繼承的,父類中synchronized的聲明在子類的繼承過程中須要再次聲明,否則synchronized將會丟失。
wait(), notify()。notifyAll()
基類不光有鎖。還有這三個方法。wait()會讓獲取鎖的線程等待并釋放鎖,直到notify()或notifyAll()喚醒并又一次獲取鎖。
先看一個樣例:
public class TestSyncClass {
?private int num = 0;
?private Object object = new Object();
?private Object object1 = new Object();
?public? void testAdd(int index) {
??System.out.println("testAdd" + index + "--->>");
??synchronized (object) {
???num++;
???try {
????object.wait();
???} catch (InterruptedException e) {
????e.printStackTrace();
???}
???System.out.println(index + "-result-->>" + num);
??}
?}
?public void release() {
??synchronized (object) {
???object.notifyAll();
???System.out.println("-release-->>");
??}
?}
?public static void main(String[] args) {
??final TestSyncClass syncClass = new TestSyncClass();
??Thread thread1 = new Thread() {
???@Override
???public void run() {
????syncClass.testAdd(1);
????super.run();
???}
??};
??Thread thread2 = new Thread() {
???@Override
???public void run() {
????syncClass.testAdd(2);
????super.run();
???}
??};
??Thread thread3 = new Thread() {
???@Override
???public void run() {
????syncClass.testAdd(3);
????super.run();
???}
??};
??thread1.start();
??thread2.start();
??thread3.start();
??Thread thread4 = new Thread() {
???@Override
???public void run() {
????try {
?????Thread.sleep(2000);
????} catch (InterruptedException e) {
?????e.printStackTrace();
????}
????syncClass.release();
????super.run();
???}
??};
??thread4.start();
?}
}
執行結果:
testAdd1--->>
testAdd2--->>
testAdd3--->>
-release-->>
3-result-->>3
2-result-->>3
1-result-->>3
調用object的wait(), notify()。notifyAll()法前,必須獲得object鎖,也就是這三個方法必須寫在synchronized(obj) {…} 代碼段內。否則跑出異常java.lang.IllegalMonitorStateException。
調用object.wait()后。線程A就釋放了object的鎖,否則syncClass.release()無法獲得object鎖,等待的線程。
當object.wait()方法返回后。各個線程須要再次獲得object鎖,才干繼續運行。
notify()僅僅能喚醒線程,notifyAll()則能所有喚醒,可是個線程須要又一次競爭object的鎖。
?
?