彻底明白Java的多线程-线程间的通信(二)
2009-02-27 11:50
579 查看
二. 共享资源的同步
1. 同步的必要性
例4:
上面是一个取得序列号的单例模式的例子,但调用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:
例5在例4的基础上,把get()函数声明为synchronized,那么在同一个对象中,就只能有一个线程调用get()函数,所以每个线程取得的number值就是唯一的了。
例6:
例6把getInstance()函数声明为synchronized,那样就保证通过getInstance()得到的是同一个seq对象。
2.2 non-static的synchronized数据只能在同一个对象的纯种实现同步访问,不同对象的线程仍可同时访问。
例7:
运行结果为:
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:
运行结果为:
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:
运行结果为:
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t1 : 4
t2 : 5
t2 : 6
t2 : 7
t2 : 8
t2 : 9
上面的代码的run()函数实现了同步,使每次打印出来的j总是不相同的。但实际上在整个run()函数中,我们只关心j的同步,而其余代码同步与否我们是不关心的,所以可以对它进行以下修改:
运行结果为:
t1 : 0
t2 : 1
t1 : 2
t2 : 3
t1 : 4
t2 : 5
t1 : 6
t2 : 7
t1 : 8
t2 : 9
由于进行同步的范围缩小了,所以程序的效率将提高。同时,代码(1)指出,当对大括号内的println()语句进行同步控制时,会取走当前对象的“锁标志”,即对当前对象“上锁”,不让当前对象下的其它线程执行当前对象的其它synchronized数据。
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数据。
相关文章推荐
- 彻底明白Java的多线程-线程间的通信(一)
- 【转】彻底明白Java的多线程-线程间的通信
- 彻底明白Java的多线程-线程间的通信
- 彻底明白Java的多线程-线程间的通信
- 彻底明白Java的多线程-线程间的通信
- 彻底明白Java的多线程-线程间的通信
- 彻底明白Java的多线程-线程间的通信
- 彻底明白Java的多线程-线程间的通信
- 彻底明白Java的多线程-线程间的通信(三)
- 彻底明白Java的多线程-实现多线程及线程的同步
- JAVA多线程之线程间的通信方式
- Java的多线程-线程间的通信
- java多线程学习之创建线程与线程间通信
- 彻底明白Java的多线程-实现线程同步 http://publish.it168.com/2005/0820/20050820006501.shtml
- java多线程-线程间通信_代码优化
- Java 多线程(三)线程间的通信jdk1.5中Lock,Condition---生产者消费者为例
- 【JAVA之多线程】6.线程间通信
- java基础——多线程(线程的同步互斥与通信)
- java多线程线程通信——生产者和消费者
- 鸟哥Java学习之线程间通信-多线程