java synchronized块_Java多线程5:synchronized锁方法块

synchronized同步代码块

用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间。这种情况下可以尝试使用synchronized同步语句块来解决问题。看一下例子:

public classThreadDomain18

{public void doLongTimeTask() throwsException

{for (int i = 0; i < 100; i++)

{

System.out.println("nosynchronized threadName = " +Thread.currentThread().getName()+ ", i = " + (i + 1));}

System.out.println();synchronized (this)

{for (int i = 0; i < 100; i++)

{

System.out.println("synchronized threadName = " +Thread.currentThread().getName()+ ", i = " + (i + 1));}

}

}

}

public class MyThread18 extendsThread

{privateThreadDomain18 td;publicMyThread18(ThreadDomain18 td)

{this.td =td;

}public voidrun()

{try{

td.doLongTimeTask();

}catch(Exception e)

{

e.printStackTrace();

}

}

}

public static voidmain(String[] args)

{

ThreadDomain18 td= newThreadDomain18();

MyThread18 mt0= newMyThread18(td);

MyThread18 mt1= newMyThread18(td);

mt0.start();

mt1.start();

}

运行结果,分两部分来看:

synchronized threadName = Thread-1, i = 1

synchronized threadName = Thread-1, i = 2nosynchronized threadName= Thread-0, i = 95

synchronized threadName = Thread-1, i = 3nosynchronized threadName= Thread-0, i = 96

synchronized threadName = Thread-1, i = 4nosynchronized threadName= Thread-0, i = 97

synchronized threadName = Thread-1, i = 5nosynchronized threadName= Thread-0, i = 98

synchronized threadName = Thread-1, i = 6nosynchronized threadName= Thread-0, i = 99

synchronized threadName = Thread-1, i = 7nosynchronized threadName= Thread-0, i = 100

...synchronized threadName = Thread-1, i = 98

synchronized threadName = Thread-1, i = 99

synchronized threadName = Thread-1, i = 100

synchronized threadName = Thread-0, i = 1

synchronized threadName = Thread-0, i = 2

synchronized threadName = Thread-0, i = 3...

这个实验可以得出以下两个结论:

1、当A线程访问对象的synchronized代码块的时候,B线程依然可以访问对象方法中其余非synchronized块的部分,第一部分的执行结果证明了这一点

2、当A线程进入对象的synchronized代码块的时候,B线程如果要访问这段synchronized块,那么访问将会被阻塞,第二部分的执行结果证明了这一点

所以,从执行效率的角度考虑,有时候我们未必要把整个方法都加上synchronized,而是可以采取synchronized块的方式,对会引起线程安全问题的那一部分代码进行synchronized就可以了。

两个synchronized块之间具有互斥性

如果线程1访问了一个对象A方法的synchronized块,那么线程B对同一对象B方法的synchronized块的访问将被阻塞,写个例子来证明一下:

public classThreadDomain19

{public voidserviceMethodA()

{synchronized (this)

{try{

System.out.println("A begin time = " +System.currentTimeMillis());

Thread.sleep(2000);

System.out.println("A end time = " +System.currentTimeMillis());

}catch(InterruptedException e)

{

e.printStackTrace();

}

}

}public voidserviceMethodB()

{synchronized (this)

{

System.out.println("B begin time = " +System.currentTimeMillis());

System.out.println("B end time = " +System.currentTimeMillis());

}

}

}

写两个线程分别调用这两个方法:

public class MyThread19_0 extendsThread

{privateThreadDomain19 td;publicMyThread19_0(ThreadDomain19 td)

{this.td =td;

}public voidrun()

{

td.serviceMethodA();

}

}

public class MyThread19_1 extendsThread

{privateThreadDomain19 td;publicMyThread19_1(ThreadDomain19 td)

{this.td =td;

}public voidrun()

{

td.serviceMethodB();

}

}

写个main函数:

public static voidmain(String[] args)

{

ThreadDomain19 td= newThreadDomain19();

MyThread19_0 mt0= newMyThread19_0(td);

MyThread19_1 mt1= newMyThread19_1(td);

mt0.start();

mt1.start();

}

看一下运行结果:

A begin time = 1443843271982A end time= 1443843273983B begin time= 1443843273983B end time= 1443843273983

看到对于serviceMethodB()方法synchronized块的访问必须等到对于serviceMethodA()方法synchronized块的访问结束之后。那其实这个例子,我们也可以得出一个结论:synchronized块获得的是一个对象锁,换句话说,synchronized块锁定的是整个对象。

synchronized块和synchronized方法

既然上面得到了一个结论synchronized块获得的是对象锁,那么如果线程1访问了一个对象方法A的synchronized块,线程2对于同一对象同步方法B的访问应该是会被阻塞的,因为线程2访问同一对象的同步方法B的时候将会尝试去获取这个对象的对象锁,但这个锁却在线程1这里。写一个例子证明一下这个结论:

public classThreadDomain20

{public synchronized voidotherMethod()

{

System.out.println("----------run--otherMethod");

}public voiddoLongTask()

{synchronized (this)

{for (int i = 0; i < 1000; i++)

{

System.out.println("synchronized threadName = " +Thread.currentThread().getName()+ ", i = " + (i + 1));try{

Thread.sleep(5);

}catch(InterruptedException e)

{

e.printStackTrace();

}

}

}

}

}

写两个线程分别调用这两个方法:

public class MyThread20_0 extendsThread

{privateThreadDomain20 td;publicMyThread20_0(ThreadDomain20 td)

{this.td =td;

}public voidrun()

{

td.doLongTask();

}

}

public class MyThread20_1 extendsThread

{privateThreadDomain20 td;publicMyThread20_1(ThreadDomain20 td)

{this.td =td;

}public voidrun()

{

td.otherMethod();

}

}

写个main函数调用一下,这里"mt0.start()"后sleep(100)以下是为了确保mt0线程先启动:

public static void main(String[] args) throwsException

{

ThreadDomain20 td= newThreadDomain20();

MyThread20_0 mt0= newMyThread20_0(td);

MyThread20_1 mt1= newMyThread20_1(td);

mt0.start();

Thread.sleep(100);

mt1.start();

}

看一下运行结果:

...synchronized threadName = Thread-0, i = 995

synchronized threadName = Thread-0, i = 996

synchronized threadName = Thread-0, i = 997

synchronized threadName = Thread-0, i = 998

synchronized threadName = Thread-0, i = 999

synchronized threadName = Thread-0, i = 1000

----------run--otherMethod

证明了我们的结论。为了进一步完善这个结论,把"otherMethod()"方法的synchronized去掉再看一下运行结果:

...synchronized threadName = Thread-0, i = 16

synchronized threadName = Thread-0, i = 17

synchronized threadName = Thread-0, i = 18

synchronized threadName = Thread-0, i = 19

synchronized threadName = Thread-0, i = 20

----------run--otherMethodsynchronized threadName = Thread-0, i = 21

synchronized threadName = Thread-0, i = 22

synchronized threadName = Thread-0, i = 23...

"otherMethod()"方法和"doLongTask()"方法中的synchronized块异步执行了

将任意对象作为对象监视器

总结一下前面的内容:

1、synchronized同步方法

(1)对其他synchronized同步方法或synchronized(this)同步代码块呈阻塞状态

(2)同一时间只有一个线程可以执行synchronized同步方法中的代码

2、synchronized同步代码块

(1)对其他synchronized同步方法或synchronized(this)同步代码块呈阻塞状态

(2)同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码

前面都使用synchronized(this)的格式来同步代码块,其实Java还支持对"任意对象"作为对象监视器来实现同步的功能。这个"任意对象"大多数是实例变量及方法的参数,使用格式为synchronized(非this对象)。看一下将任意对象作为对象监视器的使用例子:

public classThreadDomain21

{privateString userNameParam;privateString passwordParam;private String anyString = newString();public voidsetUserNamePassword(String userName, String password)

{try{synchronized(anyString)

{

System.out.println("线程名称为:" + Thread.currentThread().getName() +

"在 " + System.currentTimeMillis() + " 进入同步代码块");

userNameParam=userName;

Thread.sleep(3000);

passwordParam=password;

System.out.println("线程名称为:" + Thread.currentThread().getName() +

"在 " + System.currentTimeMillis() + " 离开同步代码块");

}

}catch(InterruptedException e)

{

e.printStackTrace();

}

}

}

写两个线程分别调用一下:

public class MyThread21_0 extendsThread

{privateThreadDomain21 td;publicMyThread21_0(ThreadDomain21 td)

{this.td =td;

}public voidrun()

{

td.setUserNamePassword("A", "AA");

}

}

public class MyThread21_1 extendsThread

{privateThreadDomain21 td;publicMyThread21_1(ThreadDomain21 td)

{this.td =td;

}public voidrun()

{

td.setUserNamePassword("B", "B");

}

}

写一个main函数调用一下:

public static voidmain(String[] args)

{

ThreadDomain21 td= newThreadDomain21();

MyThread21_0 mt0= newMyThread21_0(td);

MyThread21_1 mt1= newMyThread21_1(td);

mt0.start();

mt1.start();

}

看一下运行结果:

线程名称为:Thread-0在 1443855101706进入同步代码块

线程名称为:Thread-0在 1443855104708离开同步代码块

线程名称为:Thread-1在 1443855104708进入同步代码块

线程名称为:Thread-1在 1443855107708离开同步代码块

这个例子证明了:多个线程持有"对象监视器"为同一个对象的前提下,同一时间只能有一个线程可以执行synchronized(非this对象x)代码块中的代码。

锁非this对象具有一定的优点:如果在一个类中有很多synchronized方法,这时虽然能实现同步,但会受到阻塞,从而影响效率。但如果同步代码块锁的是非this对象,则synchronized(非this对象x)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,大大提高了运行效率。

注意一下"private String anyString = new String();"这句话,现在它是一个全局对象,因此监视的是同一个对象。如果移到try里面,那么对象的监视器就不是同一个了,调用的时候自然是异步调用,可以自己试一下。

最后提一点,synchronized(非this对象x),这个对象如果是实例变量的话,指的是对象的引用,只要对象的引用不变,即使改变了对象的属性,运行结果依然是同步的。

细化synchronized(非this对象x)的三个结论

synchronized(非this对象x)格式的写法是将x对象本身作为对象监视器,有三个结论得出:

1、当多个线程同时执行synchronized(x){}同步代码块时呈同步效果

2、当其他线程执行x对象中的synchronized同步方法时呈同步效果

3、当其他线程执行x对象方法中的synchronized(this)代码块时也呈同步效果

第一点很明显,第二点和第三点意思类似,无非一个是同步方法,一个是同步代码块罢了,举个例子验证一下第二点:

public classMyObject

{public synchronized voidspeedPrintString()

{

System.out.println("speedPrintString__getLock time = " +System.currentTimeMillis()+ ", run ThreadName = " +Thread.currentThread().getName());

System.out.println("----------");

System.out.println("speedPrintString__releaseLock time = " +System.currentTimeMillis()+ ", run ThreadName = " +Thread.currentThread().getName());

}

}

ThreadDomain24中持有MyObject的引用:

public classThreadDomain24

{public voidtestMethod1(MyObject mo)

{try{synchronized(mo)

{

System.out.println("testMethod1__getLock time = " +System.currentTimeMillis()+ ", run ThreadName = " +Thread.currentThread().getName());

Thread.sleep(5000);

System.out.println("testMethod1__releaseLock time = " +System.currentTimeMillis()+ ", run ThreadName = " +Thread.currentThread().getName());

}

}catch(InterruptedException e)

{

e.printStackTrace();

}

}

}

写两个线程分别调用"speedPrintString()"方法和"testMethod1(MyObject mo)"方法:

public class MyThread24_0 extendsThread

{privateThreadDomain24 td;privateMyObject mo;publicMyThread24_0(ThreadDomain24 td, MyObject mo)

{this.td =td;this.mo =mo;

}public voidrun()

{

td.testMethod1(mo);

}

}

public class MyThread24_1 extendsThread

{privateMyObject mo;publicMyThread24_1(MyObject mo)

{this.mo =mo;

}public voidrun()

{

mo.speedPrintString();

}

}

写一个main函数启动这两个线程:

public static voidmain(String[] args)

{

ThreadDomain24 td= newThreadDomain24();

MyObject mo= newMyObject();

MyThread24_0 mt0= newMyThread24_0(td, mo);

MyThread24_1 mt1= newMyThread24_1(mo);

mt0.start();

mt1.start();

}

看一下运行结果:

testMethod1__getLock time = 1443855939811, run ThreadName = Thread-0testMethod1__releaseLock time= 1443855944812, run ThreadName = Thread-0speedPrintString__getLock time= 1443855944812, run ThreadName = Thread-1

----------speedPrintString__releaseLock time= 1443855944812, run ThreadName = Thread-1

看到"speedPrintString()"方法必须等待"testMethod1(MyObject mo)"方法执行完毕才可以执行,没有办法异步执行,证明了第二点的结论。第三点的验证方法类似,就不写代码证明了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值