美文网首页用代码说话:并发编程
用代码说话:synchronized关键字和多线程访问同步方法的

用代码说话:synchronized关键字和多线程访问同步方法的

作者: James_Shangguan | 来源:发表于2019-08-24 17:00 被阅读0次

    synchronized关键字在多线程并发编程中一直是元老级角色的存在,是学习并发编程中必须面对的坎,也是走向Java高级开发的必经之路。

    一、synchronized性质

    synchronized是Java提供的内置锁机制,有如下两种特性:

    • 互斥性:即在同一时间最多只有一个线程能持有这种锁。当线程1尝试去获取一个由线程2持有的锁时,线程1必须等待或者阻塞,知道线程2释放这个锁。如果线程2永远不释放锁,那么线程1将永远等待下去。

    • 可重入性:即某个线程可以获取一个已经由自己持有的锁。

    二、synchronized用法

    Java中的每个对象都可以作为锁。根据锁对象的不同,synchronized的用法可以分为以下两种:

    • 对象锁:包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己制定锁对象)

    • 类锁:指的是synchronized修饰静态的方法或指定锁为Class对象。

    三、多线程访问同步方法的7种情况

    本部分针对面试中常考的7中情况进行代码实战和原理解释。

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

    /**
    * 两个线程同时访问一个对象的同步方法
    */
    public class Demo1 implements Runnable {
    
        static Demo1 instance = new Demo1();
    
        @Override
        public void run() {
            fun();
        }
    
        public synchronized void fun() {
            System.out.println(Thread.currentThread().getName() + "开始运行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(instance);
            Thread thread2 = new Thread(instance);
            thread1.start();
            thread2.start();
            while (thread1.isAlive() || thread2.isAlive()) {
    
            }
            System.out.println("finished");
        }
    }
    

    结果:两个线程顺序执行。

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

    解释:thread1和thread2共用一把锁instance;同一时刻只能有一个线程获取锁;thread1先启动,先获得到锁,先运行,此时thread2只能等待。当thread1释放锁之后,thread2获取到锁,进行执行。

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

    public class Demo2 implements Runnable{
    
        static Demo2 instance1 = new Demo2();
        static Demo2 instance2 = new Demo2();
    
        @Override
        public void run() {
            fun();
        }
    
        public synchronized void fun() {
            System.out.println(Thread.currentThread().getName() + "开始运行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(instance1);
            Thread thread2 = new Thread(instance2);
            thread1.start();
            thread2.start();
            while (thread1.isAlive() || thread2.isAlive()) {
    
            }
            System.out.println("finished");
        }
    }
    

    结果: 两个线程并行执行。

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

    解释:thread1使用的锁对象是instance1,thread2使用的锁对象是instance2,两个对象使用的锁对象不是同一个,所以线程之间互不影响,是并行执行的。

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

    public class Demo3 implements Runnable{
    
        static Demo3 instance1 = new Demo3();
        static Demo3 instance2 = new Demo3();
    
        @Override
        public void run() {
            fun();
        }
    
        public static synchronized void fun() {
            System.out.println(Thread.currentThread().getName() + "开始运行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(instance1);
            Thread thread2 = new Thread(instance2);
            thread1.start();
            thread2.start();
            while (thread1.isAlive() || thread2.isAlive()) {
    
            }
            System.out.println("finished");
        }
    }
    

    结果:两个线程顺序执行。

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

    解释:虽然两个线程使用了两个不同的instance实例,但是只要方法是静态的,对应的锁对象是同一把锁,需要先后获取到锁进行执行。

    4. 同时访问同步方法与非同步方法

    public class Demo4 implements Runnable {
    
        static Demo4 instance = new Demo4();
    
        @Override
        public void run() {
            if (Thread.currentThread().getName().equals("Thread-0")){
                fun1();
            }else{
                fun2();
            }
        }
    
        public synchronized void fun1() {
            System.out.println(Thread.currentThread().getName() + "开始运行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "fun1运行结束");
        }
    
        public void fun2() {
            System.out.println(Thread.currentThread().getName() + "fun2开始运行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
        public static void main(String[] args) {
            Thread thread1 = new Thread(instance);
            Thread thread2 = new Thread(instance);
            thread1.start();
            thread2.start();
            while (thread1.isAlive() || thread2.isAlive()) {
    
            }
            System.out.println("finished");
        }
    }
    

    结果:两个线程并行执行。

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

    解释:synchronize的关键字只对fun1起作用,不会对其他方法造成影响。也就是说同步方法不会对非同步方法造成影响,两个方法并行执行。

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

    public class Demo5 implements Runnable {
    
        static Demo5 instance = new Demo5();
    
        @Override
        public void run() {
            if (Thread.currentThread().getName().equals("Thread-0")){
                fun1();
            }else{
                fun2();
            }
        }
    
        public synchronized void fun1() {
            System.out.println(Thread.currentThread().getName() + "开始运行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "fun1运行结束");
        }
    
        public synchronized void fun2() {
            System.out.println(Thread.currentThread().getName() + "fun2开始运行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(instance);
            Thread thread2 = new Thread(instance);
            thread1.start();
            thread2.start();
            while (thread1.isAlive() || thread2.isAlive()) {
    
            }
            System.out.println("finished");
        }
    }
    

    结果:顺序执行。

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

    解释:两个方法共用了instance对象锁,两个方法无法同时运行,只能先后运行。

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

    public class Demo6 implements Runnable{
    
        static Demo6 instance = new Demo6();
    
        @Override
        public void run() {
            if (Thread.currentThread().getName().equals("Thread-0")){
                fun1();
            }else{
                fun2();
            }
        }
    
        public static synchronized void fun1() {
            System.out.println(Thread.currentThread().getName() + "开始运行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "fun1运行结束");
        }
    
        public synchronized void fun2() {
            System.out.println(Thread.currentThread().getName() + "fun2开始运行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(instance);
            Thread thread2 = new Thread(instance);
            thread1.start();
            thread2.start();
            while (thread1.isAlive() || thread2.isAlive()) {
    
            }
            System.out.println("finished");
        }
    }
    

    结果:两个线程并行执行

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

    解释:有static关键字,锁的是类本身;没有static关键字,锁的是对象实例;锁不是同一把锁,两个锁之间是没有冲突的;所以两个线程可以并行执行。

    7. 方法抛异常后,会释放锁

    public class Demo7 implements Runnable{
    
        static Demo7 instance = new Demo7();
    
        @Override
        public void run() {
            if (Thread.currentThread().getName().equals("Thread-0")){
                fun1();
            }else{
                fun2();
            }
        }
    
        public synchronized void fun1() {
            System.out.println(Thread.currentThread().getName() + "开始运行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            throw new RuntimeException();
            //System.out.println(Thread.currentThread().getName() + "fun1运行结束");
        }
    
        public synchronized void fun2() {
            System.out.println(Thread.currentThread().getName() + "fun2开始运行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(instance);
            Thread thread2 = new Thread(instance);
            thread1.start();
            thread2.start();
            while (thread1.isAlive() || thread2.isAlive()) {
    
            }
            System.out.println("finished");
        }
    }
    

    结果:thread1运行时遇到异常,并未运行结束,thread2开始运行,并运行至结束。

    方法抛异常后,会释放锁

    解释:方法抛出异常后,JVM自动释放锁。

    8. 上述7种情况总结

    3点核心思想:

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

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

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

    四、synchronized和ReentrantLock比较

    虽然ReentrantLock是更加高级的锁机制,但是synchronized依然存在着如下的优点:

    1. synchronized作为内置锁为更多的开发人员所熟悉,代码简洁;

    2. synchronized较ReentrantLock更加安全,ReentrantLock如果忘记在finally中释放锁,虽然代码表面上运行正常,但实际上已经留下了隐患;

    3. synchronized在线程转储中能给出在哪些调用帧中获得了哪些琐,并能够检测和识别发生死锁的线程。

    五、总结

    1. synchronized关键字是Java提供的一种互斥的、可重入的内置锁机制。

    2. 其有两种用法:对象锁和类锁。

    3. 虽然synchronized与高级锁相比有着不够灵活、效率低等不足,但也有自身的优势:安全,依然是并发编程领域不得不学习的重要知识点。

    相关文章

      网友评论

        本文标题:用代码说话:synchronized关键字和多线程访问同步方法的

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