您的位置:首页 > 编程语言 > Java开发

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

2009-02-27 11:50 579 查看
二. 共享资源的同步

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数据。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: