彻底明白Java的多线程-线程间的通信(二)

 二. 共享资源的同步

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数据。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值