方法锁、类锁和synchronized代码块

2020-01-05  本文已影响0人  凉风拂面秋挽月

方法锁(对象锁)

修饰在实例方法上,多个线程调用同一个对象的同步方法会阻塞(不管同步方法是不是同一个,只要对象是同一个就行),调用不同对象的同步方法不会阻塞。
简单来说,锁住的仅仅是一个类中的一个方法,该方法在同一时刻只能被一个线程调用。相对于方法锁,对象锁这个名字更为合适,因为只要有线程在调用该类的同步方法,其他线程调用本类的任何同步方法都会无效。
示例:
具有方法锁的测试类

public class Sync {
    public synchronized void test(String name) {
        
        System.out.println(name+"test开始.."+System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+"test结束.."+System.currentTimeMillis());
    }
    public synchronized void test2(String name) {
        System.out.println(name+"test2开始.."+System.currentTimeMillis());
       try {
            Thread.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }      
        System.out.println(name+"test2结束.."+System.currentTimeMillis());
    }
}

两个线程类,分别调用Asyc的两个不同的同步方法

public class MyThread1 extends Thread{
    private Sync sync;

    public MyThread1(Sync sync) {
        this.sync = sync;
        this.setName("线程1");
    }
    @Override
    public void run() {     
        sync.test(currentThread().getName());
    }
}
public class MyThread2 extends Thread{
    private Sync sync;

    public MyThread2(Sync sync) {
        this.sync = sync;
        this.setName("线程2");
    }
    @Override
    public void run() {
        sync.test2(currentThread().getName());
    }
}

测试

public class Test {
    public static void main(String[] args) {
        Sync sync = new Sync();
        MyThread1 thread = new MyThread1(sync);
        MyThread2 thread2 = new MyThread2(sync);
        thread.start();
        thread2.start();
    }
}

运行结果:

线程2test2开始..1578217624004
线程2test2结束..1578217624804
线程1test开始..1578217624804
线程1test结束..1578217625805

ps:调用非同步方法

正如一开始所说,我们的方法锁会让整个类的所有同步方法都锁定,那么在A线程调用同步方法的时候,B线程调用该对象的非同步方法会不会阻塞?
测试一下,我们去掉test1的synchronized关键字

public class Sync {
    public void test(String name) {     
        System.out.println(name+"test开始.."+System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+"test结束.."+System.currentTimeMillis());
    }
    public synchronized void test2(String name) {
        System.out.println(name+"test2开始.."+System.currentTimeMillis());
       try {
            Thread.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }      
        System.out.println(name+"test2结束.."+System.currentTimeMillis());
    }
}

执行结果

线程2test2开始..1578222537075
线程1test开始..1578222537075
线程2test2结束..1578222537875
线程1test结束..1578222538075

答案是对线程A调用同步方法对线程B调用同一对象的非同步方法没有影响。

类锁

修饰在静态方法上,多个线程调用同一个类的同步方法会阻塞(不管同步方法是不是同一个,也不管是不是同一个对象,只要是同一个类就会阻塞)。
简单来说,类锁相对于方法锁,更加严格,即使是不同对象,只要有线程调用同一个类中任意一个对象的任意一个同步方法,都会导致其他线程调用该类的实例的同步方法阻塞。
实例:
具有类锁的测试类

public class Sync {
public static synchronized void test(String name) {
        
        System.out.println(name+"test开始.."+System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+"test结束.."+System.currentTimeMillis());
    }
}

两个工具线程

public class MyThread1 extends Thread{
    public MyThread1() {
        this.setName("线程1");
    }
    @Override
    public void run() {     
        Sync.test(currentThread().getName());
    }
}
public class MyThread2 extends Thread{
    public MyThread2() {
        this.setName("线程2");
    }
    @Override
    public void run() {
        Sync.test(currentThread().getName());
    }
}

测试

public class Test {
    public static void main(String[] args) {
        MyThread1 thread = new MyThread1();
        MyThread2 thread2 = new MyThread2();
        thread.start();
        thread2.start();
    }
}

运行结果

线程2test开始..1578226459350
线程2test结束..1578226460352
线程1test开始..1578226460352
线程1test结束..1578226461352

由于是静态方法,所以在两个线程中就不需要用实例调用了,最终结果说明类锁阻塞了试图调用被加锁的类中的同步方法。

ps再次验证非同步方法

1.我们对Asyc类增加一个非同步方法再次测试,让线程2去调非同步方法。
更改如下

public class Sync {
public static synchronized void test(String name) { 
        System.out.println(name+"test开始.."+System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+"test结束.."+System.currentTimeMillis());
    }
public void test2(String name) {
    
    System.out.println(name+"test2开始.."+System.currentTimeMillis());
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(name+"test2结束.."+System.currentTimeMillis());
}
}

测试类

public class Test {
    public static void main(String[] args) {
        Sync sync2= new Sync();
        MyThread1 thread = new MyThread1();
        MyThread2 thread2 = new MyThread2(sync2);
        thread.start();
        thread2.start();
    }
}

运行结果:

线程2test2开始..1578227227114
线程1test开始..1578227227114
线程2test2结束..1578227228115
线程1test结束..1578227228115

结果表明,类锁同样对非同步方法无作用。

2.如果对Asyc类增加一个方法锁呢。
更改如下:

public class Sync {
public static synchronized void test(String name) {
        
        System.out.println(name+"test开始.."+System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+"test结束.."+System.currentTimeMillis());
    }
public synchronized void test2(String name) {
    
    System.out.println(name+"test2开始.."+System.currentTimeMillis());
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(name+"test2结束.."+System.currentTimeMillis());
}
}

测试类不做更改,运行结果:

线程2test2开始..1578227470062
线程1test开始..1578227470062
线程2test2结束..1578227471062
线程1test结束..1578227471062

结果表明,类锁对方法锁无效,类锁锁住的只有静态同步方法。经过测试,同理,方法锁对类锁也无效,方法锁锁住的只有实例方法

synchronized代码块

使用synchronized声明的方法在某些情况下是有弊端的,比如A线程调用同步的方法执行一个长时间的任务,那么B线程就必须等待比较长的时间才能执行,这种情况可以使用synchronized代码块去优化代码执行时间,只有在必须锁住对象的时候再上锁,也就是通常所说的减少锁的粒度。
ps:synchronized(Object)相当于一个对象锁/方法锁。
示例:
下面用实例说明,在方法内部有两次sleep,代表两次长时间任务,只有在第二次任务时在用同步代码块上锁。

public class Asyc {
    public void doLongTimeTask(){
        try {
            
            System.out.println("当前线程开始:" + Thread.currentThread().getName() + 
                    ", 正在执行一个较长时间的业务操作,其内容不需要同步");
            Thread.sleep(2000);         
            synchronized(this){
                System.out.println("当前线程:" + Thread.currentThread().getName() + 
                    ", 执行同步代码块,对其同步变量进行操作");
                Thread.sleep(1000);
            }
            System.out.println("当前线程结束:" + Thread.currentThread().getName() +
                    ", 执行完毕");
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        final Asyc asyc = new Asyc();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                asyc.doLongTimeTask();
            }
        },"t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                asyc.doLongTimeTask();
            }
        },"t2");
        t1.start();
        t2.start();     
    }
}

执行结果如下:

当前线程开始:t2, 正在执行一个较长时间的业务操作,其内容不需要同步
当前线程开始:t1, 正在执行一个较长时间的业务操作,其内容不需要同步
当前线程:t2, 执行同步代码块,对其同步变量进行操作
当前线程结束:t2, 执行完毕
当前线程:t1, 执行同步代码块,对其同步变量进行操作
当前线程结束:t1, 执行完毕

效果显而易见,不加说明了,synchronized(this)也就是表明锁住当前调用这个方法的实例,跟方法锁一样。

ps:本例如果不用同步代码块也可用方法锁做优化,只需要把不需要执行同步的部分抽出来当成一个独立的方法即可。不写了。

上一篇 下一篇

猜你喜欢

热点阅读