zoukankan      html  css  js  c++  java
  • java并发之synchronized详解

    前言

    多个线程访问同一个类的synchronized方法时, 都是串行执行的 ! 就算有多个cpu也不例外 ! synchronized方法使用了类java的内置锁, 即锁住的是方法所属对象本身. 同一个锁某个时刻只能被一个执行线程所获取, 因此其他线程都得等待锁的释放. 因此就算你有多余的cpu可以执行, 但是你没有锁, 所以你还是不能进入synchronized方法执行, CPU因此而空闲. 如果某个线程长期持有一个竞争激烈的锁, 那么将导致其他线程都因等待所的释放而被挂起, 从而导致CPU无法得到利用, 系统吞吐量低下.甚至导致死锁的产生, 因此要尽量避免某个线程对锁的长期占有 !

    一、修饰方法

    方法声明时使用,即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候

    用法

    public synchronized void synMethod() {
    
        //方法体
    
    }
    

    demo

    public class SyncMethod {
    public synchronized void syncMethod2() {
        try {
            System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 已经获取内置锁`SyncMethod.this`)");
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 即将释放内置锁`SyncMethod.this`)");
    }
    public synchronized void syncMethod1() {
        System.out.println("######################## (syncMethod1, 已经获取内置锁`SyncMethod.this`, 并即将退出)");
    }
    static class Thread1 extends Thread {
        SyncMethod syncMethod;
        public Thread1(SyncMethod syncMethod) {
            this.syncMethod = syncMethod;
        }
        @Override
        public void run() {
            syncMethod.syncMethod2();
        }
    }
    static class Thread2 extends Thread {
        SyncMethod syncMethod;
        public Thread2(SyncMethod syncMethod) {
            this.syncMethod = syncMethod;
        }
        @Override
        public void run() {
            System.out.println("Thread2 running ...");
            syncMethod.syncMethod1();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        SyncMethod syncMethod = new SyncMethod();
        Thread1 thread1 = new Thread1(syncMethod);
        Thread2 thread2 = new Thread2(syncMethod);
        thread1.start();    //先执行, 以便抢占锁
        Thread.sleep(500); //放弃cpu, 让thread1执行, 以便获的锁
        thread2.start(); //在syncMethod1()方法获得锁时, 看看syncMethod2()方法能否执行
    }
    }
    

    console打印:

    @@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 已经获取内置锁`SyncMethod.this`)
    Thread2 running ...
    @@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 即将释放内置锁`SyncMethod.this`)
    ######################## (syncMethod1, 已经获取内置锁`SyncMethod.this`, 并即将退出)
    

    上述代码synchronized修饰的方法,锁住的是类的实例化对象syncMethod,所以Thread1执行syncMethod2的方法将syncMethod对象锁住,使得Thread2受到阻塞必须在Thread1释放锁之后才能执行syncMethod1方法。

    将上述代码中的Main方法修改如下:

    SyncMethod syncMethod1 = new SyncMethod();
    SyncMethod syncMethod2 = new SyncMethod();
    Thread1 thread1 = new Thread1(syncMethod1);
    Thread2 thread2 = new Thread2(syncMethod2);
    thread1.start();    
    Thread.sleep(500); 
    thread2.start(); 
    

    console打印:

    @@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 已经获取内置锁`SyncMethod.this`)
    Thread2 running ...
    ######################## (syncMethod1, 已经获取内置锁`SyncMethod.this`, 并即将退出)
    @@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 即将释放内置锁`SyncMethod.this`)
    

    上述代码中Thread1锁的是syncMethod1对象,而Thread2锁的是syncMethod2对象,所以Thread1线程执行syncMethod2并不会阻塞Thread2

    当然还有第二种改进措施:

    public class SyncObject {
    private Object lock1 = new Object();
    private Object lock2 = new Object();
    public void syncMethod2() {
        synchronized (lock1) {
            try {
                System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 已经获取内置锁`SyncMethod.this`)");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 即将释放内置锁`SyncMethod.this`)");
        }
    }
    public void syncMethod1() {
        synchronized (lock2) {
            System.out.println("######################## (syncMethod1, 已经获取内置锁`SyncMethod.this`, 并即将退出)");
        }
    }
    
    static class Thread1 extends Thread {
        SyncObject syncObject;
    
        public Thread1(SyncObject syncObject) {
            this.syncObject = syncObject;
        }
    
        @Override
        public void run() {
            syncObject.syncMethod2();
        }
    }
    static class Thread2 extends Thread {
        SyncObject syncObject;
    
        public Thread2(SyncObject syncObject) {
            this.syncObject = syncObject;
        }
    
        @Override
        public void run() {
            System.out.println("Thread2 running ...");
            syncObject.syncMethod1();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        SyncObject syncObject = new SyncObject();
    
        Thread1 thread1 = new Thread1(syncObject);
        Thread2 thread2 = new Thread2(syncObject);
    
        thread1.start();    //先执行, 以便抢占锁
        Thread.sleep(500); //放弃cpu, 让thread1执行, 以便获的锁
    
        thread2.start(); //在syncMethod1()方法获得锁时, 看看syncMethod2()方法能否执行
    
    
    
    }
    }
    

    下面是一些关于使用锁的一些建议: 为了避免对锁的竞争, 你可以使用锁分解,锁分段以及减少线程持有锁的时间, 如果上诉程序中的syncMethod1和syncMethod2方法是两个不相干的方法(请求的资源不存在关系), 那么这两个方法可以分别使用两个不同的锁。

    上面Thread1锁的是对象lock1,而Thread2锁的是对象lock2。

    二、修饰静态方法

    用法

    public synchronized static void method() {
        // todo
    }
    

    demo

    我们知道静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象。我们对第一节的Demo进行一些修改如下:

    class SyncThread implements Runnable {
    private static int count;
    public SyncThread() {
      count = 0;
    }
    public synchronized static void method() {
      for (int i = 0; i < 5; i ++) {
         try {
            System.out.println(Thread.currentThread().getName() + ":" + (count++));
            Thread.sleep(100);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
    }
    public synchronized void run() {
      method();
      }
    }
    
    public static void main(String[] args) {
        SyncStatic syncThread1 = new SyncStatic();
        SyncStatic syncThread2 = new SyncStatic();
        Thread thread1 = new Thread(syncThread1, "SyncThread1");
        Thread thread2 = new Thread(syncThread2, "SyncThread2");
        thread1.start();
        thread2.start();
    }
    

    console打印:

    SyncThread1:0 
    SyncThread1:1 
    SyncThread1:2 
    SyncThread1:3 
    SyncThread1:4 
    SyncThread2:5 
    SyncThread2:6 
    SyncThread2:7 
    SyncThread2:8 
    SyncThread2:9
    

    印证了我们刚开始说的

    synchronized修饰的静态方法锁定的是这个类的所有对象

    三、修饰代码块

    当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。

    class SyncThread implements Runnable {
        private static int count;
        public SyncThread() {
        count = 0;
        }
        public  void run() {
            synchronized(this) {
                for (int i = 0; i < 5; i++) {
                    try {
                 System.out.println(Thread.currentThread().getName() + ":" + (count++));
               Thread.sleep(100);
               //this.wait(100);释放锁,其他线程可以执行
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }
    }
        public int getCount() {
            return count;
    }
    }
    //main函数调用
    SyncThread的调用:
    SyncThread syncThread = new SyncThread();
    Thread thread1 = new Thread(syncThread, "SyncThread1");
    Thread thread2 = new Thread(syncThread, "SyncThread2");
    thread1.start();
    thread2.start();
    

    console打印:

    SyncThread1:0 
    SyncThread1:1 
    SyncThread1:2 
    SyncThread1:3 
    SyncThread1:4 
    SyncThread2:5 
    SyncThread2:6 
    SyncThread2:7 
    SyncThread2:8 
    SyncThread2:9
    

    值得注意的是:

    当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。

    给指定的对象加锁:

    public void method(SomeObject obj)
    {
    //obj 锁定的对象
    synchronized(obj)
    {
         // todo
    }
    }
    

    四、修饰类

    public class SyncClass {
        public void methodA(){
        try {
                synchronized (SyncClass.class){
                    System.out.println("methodA begin 线程名称:"+Thread.currentThread().getName()+"times:"+System.currentTimeMillis());
                    Thread.sleep(3000);
                    System.out.println("methodA end 线程名称:"+Thread.currentThread().getName()+"times:"+System.currentTimeMillis());
    
                }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
        public void methodB(){
        synchronized (SyncClass.class){
            System.out.println("methodB begin 线程名称:"+Thread.currentThread().getName()+"times:"+System.currentTimeMillis());
            System.out.println("methodB end 线程名称:"+Thread.currentThread().getName()+"times:"+System.currentTimeMillis());
        }
    }
    
    static class Thread1 extends Thread {
        private SyncClass syncClass;
    
        public Thread1(SyncClass syncClass) {
            super();
            this.syncClass = syncClass;
        }
    
        @Override
        public void run() {
            syncClass.methodA();
        }
    }
    
    static class Thread2 extends Thread {
        private SyncClass syncClass;
    
        public Thread2(SyncClass syncClass) {
            super();
            this.syncClass = syncClass;
        }
    
        @Override
        public void run() {
            syncClass.methodB();
        }
    }
    
    public static void main(String[] args) {
        SyncClass syncClass1 = new SyncClass();
        SyncClass syncClass2 = new SyncClass();
    
        Thread1 thread1=new Thread1(syncClass1);
        Thread2 thread2 = new Thread2(syncClass2);
    
        thread1.setName("A");
        thread2.setName("B");
    
        thread1.start();
        thread2.start();
    }
    }
    

    console打印:

    methodA begin 线程名称:Atimes:1533208268430
    methodA end 线程名称:Atimes:1533208271431
    methodB begin 线程名称:Btimes:1533208271431
    methodB end 线程名称:Btimes:1533208271431
    

    由打印结果以及与第一节改进1结果相比可得结论:

    synchronized作用于一个类T时,是给这个类T加锁,T的所有实例化对象用的是同一把锁。

    所以才会出现methodB在等methodA执行完毕才执行,收到阻塞。

    五、synchronized原理

    修饰静态代码块

    将一个synchronized静态代码块反编译会看到两个专有名词

    monitorenter

    每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

    1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

    2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

    3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

    monitorexit:

    执行monitorexit的线程必须是objectref所对应的monitor的所有者。

    指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
      
    总结

    通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

    修饰方法

    将一个synchronized同步方法反编译:

    ACC_SYNCHRONIZED

    从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

    六、总结

    • 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
    • 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
    • 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
  • 相关阅读:
    Cesium加载Geoserver wtms服务和wms服务
    使用GeoServer+PostgreSQL+PostGIS+OpenLayers3
    Cesium 绕点旋转飞行效果
    时间分片技术(解决 js 长任务导致的页面卡顿)
    Cesium随笔:视锥绘制(下)
    使用geoserver发布arcgis切片
    Cesium点击获取模型或者地形点的位置
    npm库使用roullup封装经验总结
    一个删除node_modules文件夹的脚本
    cesium点击面高亮事件
  • 原文地址:https://www.cnblogs.com/sxkgeek/p/9415818.html
Copyright © 2011-2022 走看看