掌握多线程是从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部完成的:
- 拿到i的值;
- 进行i+1运算;
- 给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赋值那一句上,所以其他部分都是异步执行的,那么休眠那部分就可以异步执行,这样同步代码块中处理性能就提升了。
原文来自下方公众号,转载请联系作者,并务必保留出处。
想第一时间看到更多原创技术好文和资料,请关注公众号:测试开发栈
网友评论