美文网首页
方法锁、类锁和synchronized代码块

方法锁、类锁和synchronized代码块

作者: 凉风拂面秋挽月 | 来源:发表于2020-01-05 21:12 被阅读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:本例如果不用同步代码块也可用方法锁做优化,只需要把不需要执行同步的部分抽出来当成一个独立的方法即可。不写了。

    相关文章

      网友评论

          本文标题:方法锁、类锁和synchronized代码块

          本文链接:https://www.haomeiwen.com/subject/gwhcactx.html