|
java基本教程之synchronized关键字 java多线程教程 |
|
|
java基本教程之synchronized关键字java多线程教程
这篇文章主要介绍了java的synchronized原理、synchronized基本规则、synchronized方法和synchronized代码块、实例锁和全局锁
本章,会对synchronized关键字进行介绍。涉及到的内容包括:
1.synchronized原理
2.synchronized基本规则
3.synchronized方法和synchronized代码块
4.实例锁和全局锁
1.synchronized原理
在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。
当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。例如,synchronized(obj)就获取了“obj这个对象”的同步锁。
不同线程对同步锁的访问是互斥的。也就是说,某时间点,对象的同步锁只能被一个线程获取到!通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问。例如,现在有两个线程A和线程B,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁”——线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。
2.synchronized基本规则
我们将synchronized的基本规则总结为下面3条,并通过实例对它们进行说明。
第一条:当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
第二条:当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。
第三条:当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
第一条
当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
下面是“synchronized代码块”对应的演示程序。
复制代码代码如下:
classMyRunableimplementsRunnable{
@Override
publicvoidrun(){
synchronized(this){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+"loop"+i);
}
}catch(InterruptedExceptionie){
}
}
}
}
publicclassDemo1_1{
publicstaticvoidmain(String[]args){
Runnabledemo=newMyRunable();//新建“Runnable对象”
Threadt1=newThread(demo,"t1");//新建“线程t1”,t1是基于demo这个Runnable对象
Threadt2=newThread(demo,"t2");//新建“线程t2”,t2是基于demo这个Runnable对象
t1.start();//启动“线程t1”
t2.start();//启动“线程t2”
}
}
运行结果:
复制代码代码如下:
t1loop0
t1loop1
t1loop2
t1loop3
t1loop4
t2loop0
t2loop1
t2loop2
t2loop3
t2loop4
结果说明:
run()方法中存在“synchronized(this)代码块”,而且t1和t2都是基于"demo这个Runnable对象"创建的线程。这就意味着,我们可以将synchronized(this)中的this看作是“demo这个Runnable对象”;因此,线程t1和t2共享“demo对象的同步锁”。所以,当一个线程运行的时候,另外一个线程必须等待“运行线程”释放“demo的同步锁”之后才能运行。
如果你确认,你搞清楚这个问题了。那我们将上面的代码进行修改,然后再运行看看结果怎么样,看看你是否会迷糊。修改后的源码如下:
复制代码代码如下:
classMyThreadextendsThread{
publicMyThread(Stringname){
super(name);
}
@Override
publicvoidrun(){
synchronized(this){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+"loop"+i);
}
}catch(InterruptedExceptionie){
}
}
}
}
publicclassDemo1_2{
publicstaticvoidmain(String[]args){
Threadt1=newMyThread("t1");//新建“线程t1”
Threadt2=newMyThread("t2");//新建“线程t2”
t1.start();//启动“线程t1”
t2.start();//启动“线程t2”
}
}
代码说明:
比较Demo1_2和Demo1_1,我们发现,Demo1_2中的MyThread类是直接继承于Thread,而且t1和t2都是MyThread子线程。
幸运的是,在“Demo1_2的run()方法”也调用了synchronized(this),正如“Demo1_1的run()方法”也调用了synchronized(this)一样!
那么,Demo1_2的执行流程是不是和Demo1_1一样呢?
运行结果:
复制代码代码如下:
t1loop0
t2loop0
t1loop1
t2loop1
t1loop2
t2loop2
t1loop3
t2loop3
t1loop4
t2loop4
结果说明:
如果这个结果一点也不令你感到惊讶,那么我相信你对synchronized和this的认识已经比较深刻了。否则的话,请继续阅读这里的分析。
synchronized(this)中的this是指“当前的类对象”,即synchronized(this)所在的类对应的当前对象。它的作用是获取“当前对象的同步锁”。
对于Demo1_2中,synchronized(this)中的this代表的是MyThread对象,而t1和t2是两个不同的MyThread对象,因此t1和t2在执行synchronized(this)时,获取的是不同对象的同步锁。对于Demo1_1对而言,synchronized(this)中的this代表的是MyRunable对象;t1和t2共同一个MyRunable对象,因此,一个线程获取了对象的同步锁,会造成另外一个线程等待。
第二条
当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。
下面是“synchronized代码块”对应的演示程序。
复制代码代码如下:
classCount{
//含有synchronized同步块的方法
publicvoidsynMethod(){
synchronized(this){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+"synMethodloop"+i);
}
}catch(InterruptedExceptionie){
}
}
}
//非同步的方法
publicvoidnonSynMethod(){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"nonSynMethodloop"+i);
}
}catch(InterruptedExceptionie){
}
}
}
publicclassDemo2{
publicstaticvoidmain(String[]args){
finalCountcount=newCount();
//新建t1,t1会调用“count对象”的synMethod()方法
Threadt1=newThread(
newRunnable(){
@Override
publicvoidrun(){
count.synMethod();
}
},"t1");
//新建t2,t2会调用“count对象”的nonSynMethod()方法
Threadt2=newThread(
newRunnable(){
@Override
publicvoidrun(){
count.nonSynMethod();
}
},"t2");
t1.start();//启动t1
t2.start();//启动t2
}
}
运行结果:
复制代码代码如下:
t1synMethodloop0
t2nonSynMethodloop0
t1synMethodloop1
t2nonSynMethodloop1
t1synMethodloop2
t2nonSynMethodloop2
t1synMethodloop3
t2nonSynMethodloop3
t1synMethodloop4
t2nonSynMethodloop4
结果说明:
主线程中新建了两个子线程t1和t2。t1会调用count对象的synMethod()方法,该方法内含有同步块;而t2则会调用count对象的nonSynMethod()方法,该方法不是同步方法。t1运行时,虽然调用synchronized(this)获取“count的同步锁”;但是并没有造成t2的阻塞,因为t2没有用到“count”同步锁。
第三条
当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
我们将上面的例子中的nonSynMethod()方法体的也用synchronized(this)修饰。修改后的源码如下:
复制代码代码如下:
classCount{
//含有synchronized同步块的方法
publicvoidsynMethod(){
synchronized(this){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+"synMethodloop"+i);
}
}catch(InterruptedExceptionie){
}
}
}
//也包含synchronized同步块的方法
publicvoidnonSynMethod(){
synchronized(this){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"nonSynMethodloop"+i);
}
}catch(InterruptedExceptionie){
}
}
}
}
publicclassDemo3{
publicstaticvoidmain(String[]args){
finalCountcount=newCount();
//新建t1,t1会调用“count对象”的synMethod()方法
Threadt1=newThread(
newRunnable(){
@Override
publicvoidrun(){
count.synMethod();
}
},"t1");
//新建t2,t2会调用“count对象”的nonSynMethod()方法
Threadt2=newThread(
newRunnable(){
@Override
publicvoidrun(){
count.nonSynMethod();
}
},"t2");
t1.start();//启动t1
t2.start();//启动t2
}
}
(某一次)执行结果:
复制代码代码如下:
synMethod():11
synBlock():3
4.实例锁和全局锁
实例锁--锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。
实例锁对应的就是synchronized关键字。
全局锁--该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。
全局锁对应的就是staticsynchronized(或者是锁在该类的class或者classloader对象上)。
关于“实例锁”和“全局锁”有一个很形象的例子:
复制代码代码如下:
pulbicclassSomething{
publicsynchronizedvoidisSyncA(){}
publicsynchronizedvoidisSyncB(){}
publicstaticsynchronizedvoidcSyncA(){}
publicstaticsynchronizedvoidcSyncB(){}
}
假设,Something有两个实例x和y。分析下面4组表达式获取的锁的情况。
(01)x.isSyncA()与x.isSyncB()
(02)x.isSyncA()与y.isSyncA()
(03)x.cSyncA()与y.cSyncB()
(04)x.isSyncA()与Something.cSyncA()
(01)不能被同时访问。因为isSyncA()和isSyncB()都是访问同一个对象(对象x)的同步锁!
复制代码代码如下:
//LockTest2.java的源码
classSomething{
publicsynchronizedvoidisSyncA(){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+":isSyncA");
}
}catch(InterruptedExceptionie){
}
}
publicsynchronizedvoidisSyncB(){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+":isSyncB");
}
}catch(InterruptedExceptionie){
}
}
publicstaticsynchronizedvoidcSyncA(){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+":cSyncA");
}
}catch(InterruptedExceptionie){
}
}
publicstaticsynchronizedvoidcSyncB(){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+":cSyncB");
}
}catch(InterruptedExceptionie){
}
}
}
publicclassLockTest2{
Somethingx=newSomething();
Somethingy=newSomething();
//比较(02)x.isSyncA()与y.isSyncA()
privatevoidtest2(){
//新建t21,t21会调用x.isSyncA()
Threadt21=newThread(
newRunnable(){
@Override
publicvoidrun(){
x.isSyncA();
}
},"t21");
//新建t22,t22会调用x.isSyncB()
Threadt22=newThread(
newRunnable(){
@Override
publicvoidrun(){
y.isSyncA();
}
},"t22");
t21.start();//启动t21
t22.start();//启动t22
}
publicstaticwww.visa158.commain(String[]args){
LockTest2demo=newLockTest2();
demo.test2();
}
}
运行结果:
复制代码代码如下:
t11:isSyncA
t11:isSyncA
t11:isSyncA
t11:isSyncA
t11:isSyncA
t12:isSyncB
t12:isSyncB
t12:isSyncB
t12:isSyncB
t12:isSyncB
(02)可以同时被访问。因为访问的不是同一个对象的同步锁,x.isSyncA()访问的是x的同步锁,而y.isSyncA()访问的是y的同步锁。
复制代码代码如下:
//LockTest2.java的源码
classSomething{
publicsynchronizedvoidisSyncA(){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+":isSyncA");
}
}catch(InterruptedExceptionie){
}
}
publicsynchronizedvoidisSyncB(){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+":isSyncB");
}
}catch(InterruptedExceptionie){
}
}
publicstaticsynchronizedvoidcSyncA(){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+":cSyncA");
}
}catch(InterruptedExceptionie){
}
}
publicstaticsynchronizedvoidcSyncB(){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.www.hunanwang.netprintln(Thread.currentThread().getName()+":cSyncB");
}
}catch(InterruptedExceptionie){
}
}
}
publicclassLockTest2{
Somethingx=newSomething();
Somethingy=newSomething();
//比较(02)x.isSyncA()与y.isSyncA()
privatevoidtest2(){
//新建t21,t21会调用x.isSyncA()
Threadt21=newThread(
newRunnable(){
@Override
publicvoidrun(){
x.isSyncA();
}
},"t21");
//新建t22,t22会调用x.isSyncB()
Threadt22=newThread(
newRunnable(){
@Override
publicvoidrun(){
y.isSyncA();
}
},"t22");
t21.start();//启动t21
t22.start();//启动t22
}
publicstaticvoidmain(String[]args){
LockTest2demo=newLockTest2();
demo.test2();
}
}
运行结果:
复制代码代码如下:
t21:isSyncA
t22:isSyncA
t21:isSyncA
t22:isSyncA
t21:isSyncA
t22:isSyncA
t21:isSyncA
t22:isSyncA
t21:isSyncA
t22:isSyncA
(03)不能被同时访问。因为cSyncA()和cSyncB()都是static类型,x.cSyncA()相当于Something.isSyncA(),y.cSyncB()相当于Something.isSyncB(),因此它们共用一个同步锁,不能被同时反问。
复制代码代码如下:
//LockTest3.java的源码
classSomething{
publicsynchronizedvoidisSyncA(){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+":isSyncA");
}
}catch(InterruptedExceptionie){
}
}
publicsynchronizedvoidisSyncB(){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+":isSyncB");
}
}catch(InterruptedExceptionie){
}
}
publicstaticsynchronizedvoidcSyncA(){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+":cSyncA");
}
}catch(InterruptedExceptionie){
}
}
publicstaticsynchronizedvoidcSyncB(){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+":cSyncB");
}
}catch(InterruptedExceptionie){
}
}
}
publicclassLockTest3{
Somethingx=newSomething();
Somethingy=newSomething();
//比较(03)x.cSyncA()与y.cSyncB()
privatevoidtest3(){
//新建t31,t31会调用x.isSyncA()
Threadt31=newThread(
newRunnable(){
@Override
publicvoidrun(){
x.cSyncA();
}
},"t31");
//新建t32,t32会调用x.isSyncB()
Threadt32=newThread(
newRunnable(){
@Override
publicvoidrun(){
y.cSyncB();
}
},"t32");
t31.start();//启动t31
t32.start();//启动t32
}
publicstaticvoidmain(String[]args){
LockTest3demo=newLockTest3();
demo.test3();
}
}
运行结果:
复制代码代码如下:
t31:cSyncA
t31:cSyncA
t31:cSyncA
t31:cSyncA
t31:cSyncA
t32:cSyncB
t32:cSyncB
t32:cSyncB
t32:cSyncB
t32:cSyncB
(04)可以被同时访问。因为isSyncA()是实例方法,x.isSyncA()使用的是对象x的锁;而cSyncA()是静态方法,Something.cSyncA()可以理解对使用的是“类的锁”。因此,它们是可以被同时访问的。
复制代码代码如下:
//LockTest4.java的源码
classSomething{
publicsynchronizedvoidisSyncA(){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+":isSyncA");
}
}catch(InterruptedExceptionie){
}
}
publicsynchronizedvoidisSyncB(){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+":isSyncB");
}
}catch(InterruptedExceptionie){
}
}
publicstaticsynchronizedvoidcSyncA(){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+":cSyncA");
}
}catch(InterruptedExceptionie){
}
}
publicstaticsynchronizedvoidcSyncB(){
try{
for(inti=0;i<5;i++){
Thread.sleep(100);//休眠100ms
System.out.println(Thread.currentThread().getName()+":cSyncB");
}
}catch(InterruptedExceptionie){
}
}
}
publicclassLockTest4{
Somethingx=newSomething();
Somethingy=newSomething();
//比较(04)x.isSyncA()与Something.cSyncA()
privatevoidtest4(){
//新建t41,t41会调用x.isSyncA()
Threadt41=newThread(
newRunnable(){
@Override
publicvoidrun(){
x.isSyncA();
}
},"t41");
//新建t42,t42会调用x.isSyncB()
Threadt42=newThread(
newRunnable(){
@Override
publicvoidrun(){
Something.cSyncA();
}
},"t42");
t41.start();//启动t41
t42.start();//启动t42
}
publicstaticvoidmain(String[]args){
LockTest4demo=newLockTest4();
demo.test4();
}
}
运行结果:
复制代码代码如下:
t41:isSyncA
t42:cSyncA
t41:isSyncA
t42:cSyncA
t41:isSyncA
t42:cSyncA
t41:isSyncA
t42:cSyncA
t41:isSyncA
t42:cSyncA
|
|
|
|
|
|
|
|
|
|
|