用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个长时间的任务,那么B线程则必须等待较长时间。可以使用synchronized同步语句块。
public class NoSysnTest {
public void play(){
try{
//其他无关紧要的部分,放到代码块外部,节省时间
System.out.println("sleep begin threadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("sleep end threadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
synchronized (this){
//涉及到主要的操作的,避免 非线程安全的 部分可以放到代码块中
}
System.out.println("sleep end threadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
}catch (InterruptedException e){
}
}
}
当一个线程访问object的一个synchronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized同步代码块。
二、一半异步,一半同步
不在synchronized块中就是异步执行,在synchronized块中就是同步执行。
public class NoSysnTest {
public void play(){
//异步执行
for(int i=0;i<100;i++){
System.out.println("异步 threadName"+Thread.currentThread().getName()+" i="+i);
}
synchronized (this){
//同步执行
for(int i=0;i<100;i++){
System.out.println("同步 threadName"+Thread.currentThread().getName()+" i="+i);
}
}
System.out.println("sleep end threadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
}
}
结果:
异步 threadNameB i=20
异步 threadNameB i=21
异步 threadNameA i=0
异步 threadNameA i=1
。。。
异步 threadNameB i=98
异步 threadNameB i=99
同步 threadNameB i=0
同步 threadNameB i=1
同步 threadNameB i=2
同步 threadNameB i=3
。。。
三、synchronized代码块间的同步性
当一个线程访问object的一个synchronized同步代码块时,其他线程对同一个object中所有其他的synchronized同步代码块(可以是不同的方法中的)的访问将被阻塞。
public class NoSysnTest {
public void play(){
//线程A调用该代码块,其他的线程也不能调用service()方法中的代码块
synchronized (this){
System.out.println("sleep end threadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
}
}
public void service(){
//只有等到线程A调用完毕,线程B才能调用该代码块
synchronized (this){
System.out.println("threadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
}
}
}
多线程调用同一个对象中的不同名称的synchronized**同步方法或synchronized(this)同步代码块时,调用的效果是按顺序执行,也就是同步的**,阻塞的。
四、将任意对象作为对象监视器
这个“任意对象”大多数是实例变量及方法的参数,使用格式是synchronized(非this对象)
它的规则是:
4.1、在多个线程持有“对象监视器”为同一个对象(相同的实例变量 或者 方法参数 )的前提下,同一时间只有一个线程可以执行synchronized(非this对象)同步代码块中的代码。
public class NoSysnTest {
private String anyStr=new String();
public void play(){
//使用 实例变量作为 对象监视器
synchronized (anyStr){
System.out.println("sleep end threadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
}
}
}
//针对一个NoSysnTest 实例对象,它的**实例变量anyStr**对于线程A 和线程B来说都是一样的,因此只有一个线程可以调用该代码块。
//但是如果**把 anyStr 对象实例 放在方法里**。那就是不同的对象,那就是异步了(不是同一个锁)。
4.2、当持有”对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象)同步代码块中的代码
锁非this对象的优点:
如果在一个类中有很多个synchronized方法,虽然能实现同步,但是会受到阻塞,所以影响运行效率;如果使用同步代码块非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,可以提高效率。
//由于对象监视器不同,所以运行结果是异步的
public class NoSysnTest {
private String anyStr=new String();
public void play(){
//使用 实例变量作为 对象监视器
synchronized (anyStr){
System.out.println("sleep end threadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
}
}
//同步方法
synchronized public void service(){
System.out.println("sleep end threadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
}
}
三个结论:
1、当多个线程同时执行synchronized(非this对象 x)同步代码块时呈同步效果(监视器对象是一样的)。
public class NoSysnTest {
public void play(CountOperate operate){
//使用 实例变量作为 对象监视器
synchronized (operate){
System.out.println("sleep end threadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
}
}
}
2、当其他线程执行x对象(也就是监控器对象)中synchronized同步方法时呈同步效果。
//监控器对象
public class HasSelfPrivateNum {
//线程B调用了该同步方法
public synchronized void addI(String userName,NoSysnTest noSysnTest){
System.out.println("sleep end threadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
}
}
public class NoSysnTest {
//线程A调用了该代码块
public void play(HasSelfPrivateNum priNum){
//使用 实例变量作为 对象监视器
synchronized (priNum){
System.out.println("sleep end threadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
}
}
}
结果:线程A 和 线程B 是同步的
3、当其他线程执行x对象(也就是监控器对象)中synchronized(this)代码块时也呈同步效果。