zoukankan      html  css  js  c++  java
  • synchronized 锁

    1. synchronized 的用处

    ​ 在学习操作系统的时候,我们会经常听到死锁,互斥量等名词,我们知道这是在多个线程访问一些有限的资源而造成的。同样,在java多线程编程的时候,往往这些线程都会在某个时机访问相同的资源,这里的资源我们可以具体到一个变量,一个对象,一段代码块,甚至一个类。那么如何使这些线程按照我们的期望访问这些资源,就要使用关键字synchronized来保护访问资源的代码片段。这样,当其他任务或者线程想要访问这段受保护的代码块时,先检查锁是否可用,如果可用,则获取锁,然后执行代码,直到这段代码执行完毕,才能释放锁。在获取锁的线程执行这段受保护的代码期间,其他任何线程想要访问这段代码块,都将会被阻塞,直到锁被释放。所以我们可以把使用关键字synchronized的规则用<thinking in java>这本书中的一句话来概括:

    如果你正在写一个变量,它可能接下来被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那么你必须使用同步,并且,读写线程都必须用相同的监视器锁同步。

    2. synchronized 的用法

    ​ 既然知道了synchronized关键字的作用,那么接下来我们学习一下,synchronized关键字可以保护哪些资源,以及如何作用到这些资源上。这里呢,我们细分为6种情况。

    2.1 两个线程同时访问一个对象的同步方法

    这里我们看一个demo:

    package SynchronizedDemo;
    
    import java.util.concurrent.TimeUnit;
    public class SameThreadSync implements Runnable{
    
        @Override
        public void run() {
            synchronized (this) {
                try {
                    TimeUnit.MILLISECONDS.sleep(3000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("currentTime: " + System.currentTimeMillis() + "  " + Thread.currentThread().getName() + " has finished now");
            }
        }
    }
    
    
    package SynchronizedDemo;
    
    public class MainThread {
            public static void main(String[] args) {
                SameThreadSync runnable = new SameThreadSync();
                Thread thread1 = new Thread(runnable, "Amy thread");
                Thread thread2 = new Thread(runnable, "Bob thread");
                
                System.out.println("currentTime: " + System.currentTimeMillis());
                thread1.start();
                thread2.start();        
            }
    }
    

    然后我们运行一下,看一下运行结果:

    currentTime: 1547988932743
    currentTime: 1547988935744  Amy thread has finished now
    currentTime: 1547988938748  Bob thread has finished now
    

    可以看出,线程Bob是在线程Amy执行完,才开始执行。另外,如果我们把synchronized(this) 换成另外一种写法的结果是一样的。

    package SynchronizedDemo;
    
    import java.util.concurrent.TimeUnit;
    public class SameThreadSync implements Runnable{
    
        @Override
        public synchronized void run() {
                try {
                    TimeUnit.MILLISECONDS.sleep(3000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("currentTime: " + System.currentTimeMillis() + "  " + Thread.currentThread().getName() + " has finished now");
        }
    }
    

    因为synchronized块必须给定一个在其上进行同步的对象,并且最合理的方式是,使用其方法正在被调用的当前对象:synchronized(this). 所以我们这两种代码格式锁定的都是同一个对象,即当前的对象。所以根据synchronized这个关键字的定义,只有当当前对象的锁释放之后,另外一个线程才能访问这个对象。

    当我去掉同步synchronized(this)时,再运行一遍,结果如下:

    currentTime: 1547989082259
    currentTime: 1547989085265  Amy thread has finished now
    currentTime: 1547989085265  Bob thread has finished now
    

    这个时候,我们发现BobAmy几乎同时完成。

    2.2 两个线程访问的是两个对象的同步方法

    package SynchronizedDemo;
    
    public class MainThread {
            public static void main(String[] args) {
                SameThreadSync runnable = new SameThreadSync();
                SameThreadSync runnable2 = new SameThreadSync();
                
                Thread thread1 = new Thread(runnable, "Amy thread");
                Thread thread2 = new Thread(runnable2, "Bob thread");
                
                System.out.println("currentTime: " + System.currentTimeMillis());
                thread1.start();
                thread2.start();        
            }
    }
    

    运行结果如下:

    currentTime: 1547990419528
    currentTime: 1547990422533  Bob thread has finished now
    currentTime: 1547990422533  Amy thread has finished now
    

    发现两个线程几乎同时完成,并且第二个线程比第一个线程更先完成。因为synchronized锁住的是当前运行的对象,而此时AmyBob运行的是两个不同的对象,所以不会出现一个线程等待另外一个线程释放锁之后,才能执行。另外出现BobAmy先结束,这也不难理解,当Amy线程sleep后时,Amy线程进入休眠状态,cpu会给Bob线程,当Amy线程睡好之后,cpu 还没来的及给Amy,Bob就睡好了,就先执行了Bob线程的打印语句。

    总结: 对普通方法的锁,等同于对当前调用对象的锁。

    2.3 两个线程访问的是synchronized的静态方法

    我们修改一下类SameThread.java的方法,而在类MainThread.java中仍然创建的是两个不同的对象。

    package SynchronizedDemo;
    
    import java.util.concurrent.TimeUnit;
    public class SameThreadSync implements Runnable{
    
        @Override
        public  void run() {
            method();
        }
        
        private synchronized static void method() {
            try {
                TimeUnit.MILLISECONDS.sleep(3000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("currentTime: " + System.currentTimeMillis() + "  " + Thread.currentThread().getName() + " has finished now");
    
        }
    }
    

    运行一下,结果如下:

    currentTime: 1547991444723
    currentTime: 1547991447729  Amy thread has finished now
    currentTime: 1547991450734  Bob thread has finished now
    

    为什么这里Bob线程要等Amy线程完成之后,才能执行呢?因为这里静态方法是属于一个类的,所以对一个静态方法的锁,就是对这个类锁,所以对这个类的访问只能同时由一个线程访问,所以Bob只能等待Amy对这个类的锁释放之后,才能访问这个类。

    如果我们把synchronized static换成synchronized(SameThread.class)看看呢?

    currentTime: 1547991699001
    currentTime: 1547991702005  Amy thread has finished now
    currentTime: 1547991705006  Bob thread has finished now
    

    发现结果也是一样.

    总结: 对静态方法的锁等同于对当前类的锁。

    2.4 同时访问同步方法与非同步方法

    package SynchronizedDemo;
    
    import java.util.concurrent.TimeUnit;
    public class SameThreadSync implements Runnable{
    
        @Override
        public  void run() {
            if (Thread.currentThread().getName().equals("Amy thread")) {
                method1();
            } else if (Thread.currentThread().getName().equals("Bob thread")) {
                method2();
            }
        }
        
        private synchronized void method1() {
            try {
                TimeUnit.MILLISECONDS.sleep(3000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("currentTime: " + System.currentTimeMillis() + "  " + Thread.currentThread().getName() + " has finished now");
        }
        
        private void method2() {
            try {
                TimeUnit.MILLISECONDS.sleep(3000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("currentTime: " + System.currentTimeMillis() + "  " + Thread.currentThread().getName() + " has finished now");
        }
    }
    
    
    package SynchronizedDemo;
    
    public class MainThread {
            public static void main(String[] args) {
                SameThreadSync runnable = new SameThreadSync();
        
                Thread thread1 = new Thread(runnable, "Amy thread");
                Thread thread2 = new Thread(runnable, "Bob thread");
                
                System.out.println("currentTime: " + System.currentTimeMillis());
                thread1.start();
                thread2.start();        
            }
    }
    

    运行一下,结果如下:

    currentTime: 1547992475217
    currentTime: 1547992478219  Bob thread has finished now
    currentTime: 1547992478219  Amy thread has finished now
    

    两个线程几乎同时结束。所以,如果一个类中的一个成员变量,在多个方法被写访问时,同时这个类的对象可能会被多个线程持有时,那么最安全的做法就是将这个成员变量设置为私有的,同时对这个变量的写访问的所有方法都要用synchronized关键字保护起来。

    总结synchronized的方法并不能阻塞另一个线程对同一个对象的非synchronized的方法的调用。

    2.5 访问同一个对象的不同的普通同步方法

    我们将上例中的method2()方法也加上synchronized关键字,运行一个代码:

    currentTime: 1547992878720
    currentTime: 1547992881726  Amy thread has finished now
    currentTime: 1547992884730  Bob thread has finished now
    

    这个结果也验证了上一节说的对一个成员变量保护的方法。如果一个线程A访问了一个对象的synchronized关键字保护的方法,那么另一个线程B必须等待线程A释放了锁之后,才能访问其他的synchronized方法。

    2.6 同时访问静态的synchronized 和 非静态synchronized方法

    package SynchronizedDemo;
    
    import java.util.concurrent.TimeUnit;
    public class SameThreadSync implements Runnable{
    
        @Override
        public  void run() {
            if (Thread.currentThread().getName().equals("Amy thread")) {
                method1();
            } else if (Thread.currentThread().getName().equals("Bob thread")) {
                method2();
            }
        }
        
        private synchronized static void method1() {
            try {
                TimeUnit.MILLISECONDS.sleep(3000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("currentTime: " + System.currentTimeMillis() + "  " + Thread.currentThread().getName() + " has finished now");
        }
        
        private synchronized void method2() {
            try {
                TimeUnit.MILLISECONDS.sleep(3000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("currentTime: " + System.currentTimeMillis() + "  " + Thread.currentThread().getName() + " has finished now");
        }
    }
    
    

    运行结果:

    currentTime: 1547993122882
    currentTime: 1547993125888  Amy thread has finished now
    currentTime: 1547993125888  Bob thread has finished now
    

    发现二者几乎是同时运行的,为什么呢?因为method1()方法是一个静态方法,而静态方法是类级别,我们在上面也说过,对一个静态方法的锁,类似于synchronized(xx.class),说明锁的对象是.class对象,而method2()这个普通方法的锁,我们等同于synchronized(this), 说明锁的是当前运行的实例对象,也就是说method1()method2()锁的不是同一个对象,所以二者是相互不影响的。

    总结:对静态方法的锁,锁的是class对象,对普通方法的锁,锁的是当前类的实例对象,二者相互不影响。

    3. synchronized 的核心思想

    • 一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待

    • 每个实例都对应有自己的一把锁,不同实例之间互不影响;例外:锁对象是*.class以及synchronized修饰的static方法的时候,所有对象共用同一把类锁。

    • 无论是方法正常执行完毕或者方法抛出异常,都会释放锁。



    作者:雨打空城
    链接:https://www.jianshu.com/p/bd3ba6b9431a
    来源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
  • 相关阅读:
    <阿里工程师的自我素养>读后感-技术人应该具备的一些基本素质
    Hbase的基本原理(与HIVE的区别、数据结构模型、拓扑结构、水平分区原理、场景)
    大数据技术体系 && NoSQL数据库的基本原理
    软件测试面试经验
    APP非功能测试
    手机APP测试(测试点、测试流程、功能测试)
    性能测试学习之路 (四)jmeter 脚本开发实战(JDBC &JMS &接口脚本 & 轻量级接口自动化测试框架)
    HTML 实战生成一张页面
    前端性能测试(H5性能测试)
    JAVA基础——设计模式之观察者模式
  • 原文地址:https://www.cnblogs.com/hanwuxing/p/11362077.html
Copyright © 2011-2022 走看看