2018/4/16 15:09:00当前位置推荐好文程序员浏览文章

掌握多线程是从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赋值。
    所以在多线程环境下这一步操作就很容易出现同步问题,下面通过测试代码来验证:
@Testpublic 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();        }    }}

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

@Testpublic 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();        }    }}

也测试一下:

@Testpublic 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赋值那一句上,所以其余部分都是异步执行的,那么休眠那部分即可以异步执行,这样同步代码块中解决性可以就提升了。

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

网友评论