美文网首页技术架构
java synchronized关键字用法详解(面试必考)

java synchronized关键字用法详解(面试必考)

作者: 行人墨客 | 来源:发表于2017-10-13 00:14 被阅读0次
    image.png

    Synchronized 我们在使用中,常用的都是使用同步代码块,如下:

    Synchronized (obj){
    
    }
    

    那么其实我们知道,synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:

    1. 修饰一个代码块
    2. 修饰一个方法
    3. 修改一个静态的方法
    4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

    但是为什么我们在日常使用中很少用来直接修饰静态方法、或者类呢?
    那么带着这样的问题,我们先来看一看上面的这些用法所带来的后果是什么!

    synchronized修饰一个方法


    package com.deem.thread.test;
    
    public class Tick2 implements Runnable {
        private static int count;
    
        public Tick2() {
            count = 0;
        }
    
        @Override
        public synchronized void run() {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
    //                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        }
    
    @Test
    public void test(){
    Tick2 t = new Tick2();
    
    Thread t1 = new Thread(t,"tickThread1");
    Thread t2 = new Thread(t,"tickThread2");
    
    t1.start();
    t2.start();
    }
    
    

    结果:
    tickThread1:0
    tickThread1:1
    tickThread1:2
    tickThread1:3
    tickThread1:4
    tickThread2:5
    tickThread2:6
    tickThread2:7
    tickThread2:8
    tickThread2:9

    在这里我们可以看到先是tickThread1执行完后,tickThread2才开始执行的。
    所以其实在这里我们来进行分析,synchronized 关键字在这里的用法获取得到一个对象的锁, 那么当tickThread1线程在执行时,是已经获取得到了t这个对象的锁,从而使得线程tickThread2被阻塞了,当tickThread1执行完并释放该对象锁时,线程tickThread2才开始执行。

    关于修饰方法的写法一般是下面两种

    写法一
    public synchronized void method()
    {
       // todo
    }
    
    写法二
    public void method()
    {
       synchronized(this) {
          // todo
       }
    }
    

    对于上面的是第一种写法,第二种写法其实是同步代码块的写法,但在这里也是相当于修饰了方法,下面是第二种写法,得出的结果与写法一 一样。

     @Override
        public void run() {
            synchronized (this) {
                for (int i = 0; i < 5; i++) {
                    try {
                        System.out.println(Thread.currentThread().getName() + ":" + (count++));
    //                    Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    

    synchronized修饰一个静态方法


    package com.deem.thread.test;
    
    public class TickStatic implements Runnable {
        private static int count;
    
        public TickStatic() {
            count = 0;
        }
    
        public  void run() {
            method();
        }
    
        public synchronized static void method() {
            for (int i = 0; i < 5; i ++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
    //                Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
    }
    
     @Test
        public void test1(){
            TickStatic t_0 = new TickStatic();
            TickStatic t_1 = new TickStatic();
    
            Thread t1 = new Thread(t_0,"tickThread1");
            Thread t2 = new Thread(t_1,"tickThread2");
    
            t1.start();
            t2.start();
        }
    

    运行结果

    tickThread1:0
    tickThread1:1
    tickThread1:2
    tickThread1:3
    tickThread1:4
    tickThread2:5
    tickThread2:6
    tickThread2:7
    tickThread2:8
    tickThread2:9

    其实这个时候我们会发现为什么明明是创建了两个对象,线程还能保持同步呢?
    这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁。这个锁其实也可以叫做为类锁
    在后续的章节中,将会详细的讲诉下类锁和对象锁之间的区别。

    synchronized修饰一个类


    用法如下

    class ClassName {
       public void method() {
          synchronized(ClassName.class) {
             // todo
          }
       }
    }
    
    package com.deem.thread.test;
    
    public class TickClass implements Runnable {
        private static int count;
    
        public TickClass() {
            count = 0;
        }
    
        public void run() {
            method();
        }
    
        public synchronized  void method() {
            synchronized (TickClass.class) {
                for (int i = 0; i < 5; i++) {
                    try {
                        System.out.println(Thread.currentThread().getName() + ":" + (count++));
    //                Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
    
        }
    
    }
    

    运行结果与TickStatic 执行的结果是一样的,因为synchronized作用于一个类T时,是给这个类T加锁,T的所有对象用的是同一把锁。

    其实在上面的几个用法当中,我们不难发现,当两个线程或者多个线程进行运行时,因为对象锁或者类锁被线程1占有而未得到释放,使得其他的线程都被阻塞住了,而我们在实际的生产环境中,这样必将导致资源耗尽,效率很低。

    synchronized同步代码块


    用法一:

    synchronized(this) {
        //todo
          }
    

    用法二

    Object obj =new Object();
    synchronized(obj) {
        //todo
          }
    
    第一种用法如下:
    package com.deem.thread.test;
    
    public class TickCodeBlock implements Runnable {
        private static int count;
    
        public TickCodeBlock() {
            count = 0;
        }
    
        public void run() {
            method();
        }
    
        public void method() {
            synchronized (this) {
                for (int i = 0; i < 5; i++) {
                    try {
                        System.out.println(Thread.currentThread().getName() + ":" + (count++));
    //                Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
    
        }
    
    }
    
     @Test
        public void test3(){
            TickCodeBlock t = new TickCodeBlock();
    
            Thread t1 = new Thread(t,"tickThread1");
            Thread t2 = new Thread(t,"tickThread2");
    
            t1.start();
            t2.start();
        }
    

    这时候,你会发现,还是与上面相同的结果

    tickThread1:0
    tickThread1:1
    tickThread1:2
    tickThread1:3
    tickThread1:4
    tickThread2:5
    tickThread2:6
    tickThread2:7
    tickThread2:8
    tickThread2:9

    但是呢 如果换成这种方式去运行呢

      @Test
        public void test3(){
    
            Thread t1 = new Thread(new TickCodeBlock(),"tickThread1");
            Thread t2 = new Thread(new TickCodeBlock(),"tickThread2");
    
            t1.start();
            t2.start();
        }
    

    结果

    tickThread2:0
    tickThread1:1
    tickThread2:2
    tickThread1:3
    tickThread2:4
    tickThread1:5
    tickThread2:6
    tickThread1:7
    tickThread2:8
    tickThread1:9

    其实在这里我们可以知道,synchronized 用来给对象获得对象锁,当不同的对象时,对象锁也是不一样的,所以此时能够保证两个线程是互斥的,不会影响,从而也不会导致堵塞的情况

    第二种用法如下:
    package com.deem.thread.test;
    
    public class TickCodeBlock implements Runnable {
        private static int count;
        private Object object =new Object();
    
        public TickCodeBlock() {
            count = 0;
        }
    
        public void run() {
            method2();
        }
    
      
        public void method2() {
            synchronized (object) {
                for (int i = 0; i < 5; i++) {
                    try {
                        System.out.println(Thread.currentThread().getName() + ":" + (count++));
    //                Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
    
        }
    
    }
    

    运行的结果是:

    tickThread1:1
    tickThread2:0
    tickThread1:2
    tickThread2:3
    tickThread1:4
    tickThread2:5
    tickThread1:6
    tickThread2:7
    tickThread1:8
    tickThread2:9

    说明:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

    总结

    1. 当synchronized用来修饰静态方法或者类时,将会使得这个类的所有对象都是共享一把类锁,导致线程阻塞,所以这种写法一定要规避
    2. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
    3. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
    4. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

    相关文章

      网友评论

        本文标题:java synchronized关键字用法详解(面试必考)

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