美文网首页测试开发栈程序员
Java进阶之synchronized关键字详解

Java进阶之synchronized关键字详解

作者: 测试开发栈 | 来源:发表于2018-04-16 09:37 被阅读85次

    掌握多线程是从Java入门后需要跳过的第一大坎,使用多线程就难以避免要处理数据同步问题,在Java多线程中实现数据同步的方式有很多,其中就有通过synchronized关键词来处理的方式,比如用synchronized来定义同步方法和同步代码块,下面就通过实例来比较synchronized方法和synchronized代码块两者之间的用法和差异。

    准备

    1、定一个用于多线程处理的Task接口:

    public interface Task {
        void dosomething();
    }
    

    2、定义一个线程执行类:

    public class MyThread extends Thread {
        private Task task;
    
        public MyThread(Task task) {
            super();
            this.task = task;
        }
    
        @Override
        public void run() {
            task.dosomething();
        }
    }
    

    数据同步问题

    定一个TaskWithAsync类实现上面的Task接口:

    public class TaskWithAsync implements Task{
    
        private String name;
        private int i = 0;
    
        @Override
        public void dosomething(){
            try {
                System.out.println(String.format("task [%s] is begin at [%d]", Thread.currentThread().getName(), System.currentTimeMillis()));
                Thread.sleep(1000);
                name = "Async "+ i++ +" threadName: " + Thread.currentThread().getName();
                System.out.println(name);
                System.out.println(String.format("task [%s] is end at [%d]", Thread.currentThread().getName(), System.currentTimeMillis()));
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    

    上面代码有一个会出现同步问题的地方(i++),因为i++;实际是有3部完成的:

    1. 拿到i的值;
    2. 进行i+1运算;
    3. 给i赋值。
      所以在多线程环境下这一步操作就很容易出现同步问题,下面通过测试代码来验证:
    @Test
    public void test() throws InterruptedException {
        long beginTime = System.currentTimeMillis();
        Task task1 = new TaskWithAsync();
    
        MyThread myThread1 = new MyThread(task1);
        MyThread myThread2 = new MyThread(task1);
    
        myThread1.start();
        myThread2.start();
    
        myThread1.join();
        myThread2.join();
        System.out.println("spend time: " + (System.currentTimeMillis() - beginTime) + "ms");
    }
    

    多运行几次,会看到下面的结果,打印出来的name中i并没有按预期对应,这就是出现了数据同步问题:


    synchronized同步方法

    下面利用synchronized关键字定义同步方法来解决上述的同步问题,新建TaskWithSyncMethod类实现Task接口:

    public class TaskWithSyncMethod implements Task{
    
        private String name;
        private int i = 0;
    
        @Override
        public synchronized void dosomething(){
            try {
                System.out.println(String.format("task [%s] is begin at [%d]", Thread.currentThread().getName(), System.currentTimeMillis()));
                Thread.sleep(1000);
                name = "SyncMethod "+ i++ +" threadName: " + Thread.currentThread().getName();
                System.out.println(name);
                System.out.println(String.format("task [%s] is end at [%d]", Thread.currentThread().getName(), System.currentTimeMillis()));
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    

    老规矩,在多线程环境中测试下:

    @Test
    public void test1() throws InterruptedException {
        long beginTime = System.currentTimeMillis();
        Task task1 = new TaskWithSyncMethod();
    
        MyThread myThread1 = new MyThread(task1);
        MyThread myThread2 = new MyThread(task1);
    
        myThread1.start();
        myThread2.start();
    
        myThread1.join();
        myThread2.join();
        System.out.println("spend time: " + (System.currentTimeMillis() - beginTime) + "ms");
    }
    

    测试结果没毛病,数据对应上了:


    synchronized同步代码块

    下面继续用同步代码块来处理试试,直接上代码:

    public class TaskWithSyncBlock implements Task{
    
        private String name;
        private int i = 0;
    
        @Override
        public void dosomething(){
            try {
                System.out.println(String.format("task [%s] is begin at [%d]", Thread.currentThread().getName(), System.currentTimeMillis()));
                Thread.sleep(1000);
                synchronized (this) {
                    name = "SyncBlock "+ i++ +" threadName: " + Thread.currentThread().getName();
                }
                System.out.println(name);
                System.out.println(String.format("task [%s] is end at [%d]", Thread.currentThread().getName(), System.currentTimeMillis()));
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    

    也测试一下:

    @Test
    public void test2() throws InterruptedException {
        long beginTime = System.currentTimeMillis();
        Task task2 = new TaskWithSyncBlock();
    
        MyThread myThread1 = new MyThread(task2);
        MyThread myThread2 = new MyThread(task2);
    
        myThread1.start();
        myThread2.start();
    
        myThread1.join();
        myThread2.join();
        System.out.println("spend time: " + (System.currentTimeMillis() - beginTime) + "ms");
    }
    

    同步方法和同步代码块对比


    从测试结果中可以看出同步方法和同步代码块都可以处理这个++的问题,但是它们之间也是有性能差别的,我特例将每个测试方法的耗时都打印出来了,可以看到同步代码块的耗时要比同步方法低不少。这是因为加上synchronized关键字后,同一时间点这个方法或代码块就只能有一个线程去访问,所以TaskWithSyncMethod 的同步方法中就会同步休眠1+1=2s,从而导致总的执行时间肯定是要大于2s的,而TaskWithSyncBlock中同步锁只加在name赋值那一句上,所以其他部分都是异步执行的,那么休眠那部分就可以异步执行,这样同步代码块中处理性能就提升了。

    原文来自下方公众号,转载请联系作者,并务必保留出处。
    想第一时间看到更多原创技术好文和资料,请关注公众号:测试开发栈

    相关文章

      网友评论

      • IT人故事会:做开发很累,还的学习,之前你这个我也碰到过,但是没记录谢谢了
        测试开发栈:是啊,要不断的学习,我这还是从测试转开发的,心更累:joy:

      本文标题:Java进阶之synchronized关键字详解

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