二. 共享資源的同步
1. 同步的必要性
例4:
classSeq{
privatestaticintnumber = 0;
privatestaticSeq seq = newSeq();
privateSeq() {}
publicstaticSeq getInstance(){
returnseq;
}
publicintget(){
number++; //(a)
returnnumber; //(b)
}
}
publicclassTestThread{
publicstaticvoidmain(java/lang/String.java.html" target="_blank">
String
[] args){
Seq.getInstance().get(); //(1)
Seq.getInstance().get(); //(2)
}
}
上面是一個取得序列號的單例模式的例子,但調用get()時,可能會產生兩個相同的序列號:
當代碼(1)和(2)都試圖調用get()取得一個唯一的序列。當代碼(1)執行完代碼(a),正要執行代碼(b)時,它被中斷了並開始執行代碼(2)。一旦當代碼(2)執行完(a)而代碼(1)還未執行代碼(b),那麼代碼(1)和代碼(2)就將得到相同的值。
2. 通過synchronized實現資源同步
2.1 鎖標誌
2.1.1 每個對象都有一個標誌鎖。當對象的一個線程訪問了對象的某個synchronized數據(包括函數)時,這個對象就將被“上鎖”,所以被聲明爲synchronized的數據(包括函數)都不能被調用(因爲當前線程取走了對象的“鎖標誌”)。只有當前線程訪問完它要訪問的synchronized數據,釋放“鎖標誌”後,同一個對象的其它線程才能訪問synchronized數據。
2.1.2 每個class也有一個“鎖標誌”。對於synchronized static數據(包括函數)可以在整個class下進行鎖定,避免static數據的同時訪問。
例5:
classSeq{
privatestaticintnumber = 0;
privatestaticSeq seq = newSeq();
privateSeq() {}
publicstaticSeq getInstance(){
returnseq;
}
publicsynchronizedintget(){ //(1)
number++;
returnnumber;
}
}
例5在例4的基礎上,把get()函數聲明爲synchronized,那麼在同一個對象中,就只能有一個線程調用get()函數,所以每個線程取得的number值就是唯一的了。
例6:
classSeq{
privatestaticintnumber = 0;
privatestaticSeq seq = null;
privateSeq() {}
synchronizedpublicstaticSeq getInstance(){ //(1)
if(seq==null) seq = newSeq();
returnseq;
}
publicsynchronizedintget(){
number++;
returnnumber;
}
}
例6把getInstance()函數聲明爲synchronized,那樣就保證通過getInstance()得到的是同一個seq對象。
2.2 non-static的synchronized數據只能在同一個對象的純種實現同步訪問,不同對象的線程仍可同時訪問。
例7:
classTestSynchronized implementsjava/lang/Runnable.java.html" target="_blank">
Runnable
{
publicsynchronizedvoidrun(){ //(1)
for(inti=0; i<10; i++){
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + i);
/*(2)*/
try{
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100);
}
catch(java/lang/InterruptedException.java.html" target="_blank">
InterruptedException
e){
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted");
}
}
}
}
publicclassTestThread{
publicstaticvoidmain(java/lang/String.java.html" target="_blank">
String
[] args){
TestSynchronized r1 = newTestSynchronized();
TestSynchronized r2 = newTestSynchronized();
java/lang/Thread.java.html" target="_blank">
Thread
t1 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r1, "t1");
java/lang/Thread.java.html" target="_blank">
Thread
t2 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r2, "t2"); //(3)
//Thread t2 = new Thread(r1, "t2"); (4)
t1.start();
t2.start();
}
}
運行結果爲:
t1 : 0
t2 : 0
t1 : 1
t2 : 1
t1 : 2
t2 : 2
t1 : 3
t2 : 3
t1 : 4
t2 : 4
t1 : 5
t2 : 5
t1 : 6
t2 : 6
t1 : 7
t2 : 7
t1 : 8
t2 : 8
t1 : 9
t2 : 9
雖然我們在代碼(1)中把run()函數聲明爲synchronized,但由於t1、t2是兩個對象(r1、r2)的線程,而run()函數是non-static的synchronized數據,所以仍可被同時訪問(代碼(2)中的sleep()函數由於在暫停時不會釋放“標誌鎖”,因爲線程中的循環很難被中斷去執行另一個線程,所以代碼(2)只是爲了顯示結果)。
如果把例7中的代碼(3)註釋掉,並去年代碼(4)的註釋,運行結果將爲:
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t1 : 4
t1 : 5
t1 : 6
t1 : 7
t1 : 8
t1 : 9
t2 : 0
t2 : 1
t2 : 2
t2 : 3
t2 : 4
t2 : 5
t2 : 6
t2 : 7
t2 : 8
t2 : 9
修改後的t1、t2是同一個對象(r1)的線程,所以只有當一個線程(t1或t2中的一個)執行run()函數,另一個線程才能執行。
2.3 對象的“鎖標誌”和class的“鎖標誌”是相互獨立的。
例8:
classTestSynchronized extendsjava/lang/Thread.java.html" target="_blank">
Thread
{
publicTestSynchronized(java/lang/String.java.html" target="_blank">
String
name){
super(name);
}
publicsynchronizedstaticvoidprt(){
for(inti=10; i<20; i++){
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + i);
try{
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100);
}
catch(java/lang/InterruptedException.java.html" target="_blank">
InterruptedException
e){
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted");
}
}
}
publicsynchronizedvoidrun(){
for(inti=0; i<10; i++){
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + i);
try{
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100);
}
catch(java/lang/InterruptedException.java.html" target="_blank">
InterruptedException
e){
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted");
}
}
}
}
publicclassTestThread{
publicstaticvoidmain(java/lang/String.java.html" target="_blank">
String
[] args){
TestSynchronized t1 = newTestSynchronized("t1");
TestSynchronized t2 = newTestSynchronized("t2");
t1.start();
t1.prt(); //(1)
t2.prt(); //(2)
}
}
運行結果爲:
main : 10
t1 : 0
main : 11
t1 : 1
main : 12
t1 : 2
main : 13
t1 : 3
main : 14
t1 : 4
main : 15
t1 : 5
main : 16
t1 : 6
main : 17
t1 : 7
main : 18
t1 : 8
main : 19
t1 : 9
main : 10
main : 11
main : 12
main : 13
main : 14
main : 15
main : 16
main : 17
main : 18
main : 19
在代碼(1)中,雖然是通過對象t1來調用prt()函數的,但由於prt()是靜態的,所以調用它時不用經過任何對象,它所屬的線程爲main線程。
由於調用run()函數取走的是對象鎖,而調用prt()函數取走的是class鎖,所以同一個線程t1(由上面可知實際上是不同線程)調用run()函數且還沒完成run()函數時,它就能調用prt()函數。但prt()函數只能被一個線程調用,如代碼(1)和代碼(2),即使是兩個不同的對象也不能同時調用prt()。
3. 同步的優化
1) synchronized block
語法爲:synchronized(reference){ do this }
reference用來指定“以某個對象的鎖標誌”對“大括號內的代碼”實施同步控制。
例9:
classTestSynchronized implementsjava/lang/Runnable.java.html" target="_blank">
Runnable
{
staticintj = 0;
publicsynchronizedvoidrun(){
for(inti=0; i<5; i++){
//(1)
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + j++);
try{
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100);
}
catch(java/lang/InterruptedException.java.html" target="_blank">
InterruptedException
e){
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted");
}
}
}
}
publicclassTestThread{
publicstaticvoidmain(java/lang/String.java.html" target="_blank">
String
[] args){
TestSynchronized r1 = newTestSynchronized();
TestSynchronized r2 = newTestSynchronized();
java/lang/Thread.java.html" target="_blank">
Thread
t1 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r1, "t1");
java/lang/Thread.java.html" target="_blank">
Thread
t2 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r1, "t2");
t1.start();
t2.start();
}
}
運行結果爲:
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t1 : 4
t2 : 5
t2 : 6
t2 : 7
t2 : 8
t2 : 9
上面的代碼的run()函數實現了同步,使每次打印出來的j總是不相同的。但實際上在整個run()函數中,我們只關心j的同步,而其餘代碼同步與否我們是不關心的,所以可以對它進行以下修改:
classTestSynchronized implementsjava/lang/Runnable.java.html" target="_blank">
Runnable
{
staticintj = 0;
publicvoidrun(){
for(inti=0; i<5; i++){
//(1)
synchronized(this){
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + j++);
}
try{
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100);
}
catch(java/lang/InterruptedException.java.html" target="_blank">
InterruptedException
e){
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted");
}
}
}
}
publicclassTestThread{
publicstaticvoidmain(java/lang/String.java.html" target="_blank">
String
[] args){
TestSynchronized r1 = newTestSynchronized();
TestSynchronized r2 = newTestSynchronized();
java/lang/Thread.java.html" target="_blank">
Thread
t1 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r1, "t1");
java/lang/Thread.java.html" target="_blank">
Thread
t2 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r1, "t2");
t1.start();
t2.start();
}
}
運行結果爲:
t1 : 0
t2 : 1
t1 : 2
t2 : 3
t1 : 4
t2 : 5
t1 : 6
t2 : 7
t1 : 8
t2 : 9
由於進行同步的範圍縮小了,所以程序的效率將提高。同時,代碼(1)指出,當對大括號內的println()語句進行同步控制時,會取走當前對象的“鎖標誌”,即對當前對象“上鎖”,不讓當前對象下的其它線程執行當前對象的其它synchronized數據。