1、如何优雅的使用synchronized
有些情况下,在方法上面加synchronized同步,会有性能问题。
请看下面代码,来计算下两个线程执行的耗时:
/**
* 执行耗时较长的任务
*
*/
public class LongTask {
//多个线程共享堆内存,在某个时间段内,多个线程可以同时运行changeNum方法修改num的值,这样会导致线程安全问题
private int num = 0;
Object obj = new Object();
//同步和异步
public synchronized void changeNum(boolean flag){
try {
//假设下面代码要执行一个耗时较长的任务
Thread.sleep(3000);
System.out.println("执行一个耗时较长的任务");
} catch (InterruptedException e) {
e.printStackTrace();
}
if(flag){
num = 88;
System.out.println(Thread.currentThread().getName() + "========== begin");
System.out.println(Thread.currentThread().getName() + "==========" + num);
System.out.println(Thread.currentThread().getName() + "========== end");
}else{
num = 66;
System.out.println(Thread.currentThread().getName() + "========== begin");
System.out.println(Thread.currentThread().getName() + "==========" + num);
System.out.println(Thread.currentThread().getName() + "========== end");
}
}
}
用下面代码来计算两个线程一共耗费了多长时间
/**
* 将一个耗时较长的任务放到两个线程中,计算这两个线程执行结束后所花费的时间
*
*/
public class SynchronizedTest02 {
public static long begin1;
public static long end1;
public static long begin2;
public static long end2;
public static void main(String[] args) {
final LongTask longTask = new LongTask();
Thread t1 = new Thread(){
public void run(){
begin1 = System.currentTimeMillis();
//执行耗时较长的任务方法
longTask.changeNum(true);
end1 = System.currentTimeMillis();
}
};
Thread t2 = new Thread(){
public void run(){
begin2 = System.currentTimeMillis();
//执行耗时较长的任务方法
longTask.changeNum(false);
end2 = System.currentTimeMillis();
}
};
t1.start();
t2.start();
//先让主线程睡眠,保证t1和t2线程执行完毕之后再计算时间
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long begin = 0;
long end = 0;
//将先执行的线程的时间和最后执行线程的时间获取到
if(begin1 > begin2){
begin = begin2;
}else{
begin = begin1;
}
if(end1 > end2){
end = end1;
}else{
end = end2;
}
System.out.println("两个线程总共耗时:" + (end -begin)/1000 + "秒");
}
}
使用同步代码块完善上面代码
上面代码打印结果是6秒,里面使用Thread.sleep方法来模拟了一个执行耗时较长的代码,假设这段代码并不会涉及到安全问题,所以就没有比较加入同步了。
来使用synchronized代码块来修改下上面的LongTask类。
/**
* 执行耗时较长的任务
*
*/
public class LongTask {
//多个线程共享堆内存,在某个时间段内,多个线程可以同时运行changeNum方法修改num的值,这样会导致线程安全问题
private int num = 0;
Object obj = new Object();
//同步和异步
public void changeNum(boolean flag){
try {
//假设下面代码要执行一个耗时较长的任务,并且这段任务不涉及到线程安全问题
Thread.sleep(3000);
System.out.println("执行一个耗时较长的任务");
} catch (InterruptedException e) {
e.printStackTrace();
}
//这个方法中,需要同步的代码块是这部分,而上面耗时操作的代码,不涉及到线程安全问题,所以不需要同步
synchronized(obj){
if(flag){
num = 88;
System.out.println(Thread.currentThread().getName() + "========== begin");
System.out.println(Thread.currentThread().getName() + "==========" + num);
System.out.println(Thread.currentThread().getName() + "========== end");
}else{
num = 66;
System.out.println(Thread.currentThread().getName() + "========== begin");
System.out.println(Thread.currentThread().getName() + "==========" + num);
System.out.println(Thread.currentThread().getName() + "========== end");
}
}
}
}
修改后将需要同步的代码放到synchronized代码块中,再次运行SynchronizedTest02类,打印结果是3秒,因为那段耗时较长的代码是在异步情况下运行,所以节省了一些时间。
注意:多个线程在执行synchronized同步代码块时,代码块括号里面可以传入任意对象,但一定要保证多个线程访问的是同一个对象。
比如将LongTask中的成员变量Object obj = new Object();移动到changeNum方法中,在t1和t2两个线程中分别调用了changeNum方法,这样会各自创建一个Object对象,仍然会导致线程安全问题发生。