这是我参与更文挑战的第6天,活动详情查看: 更文挑战
1、错误的加锁方式导致的线程不安全
上篇文章我们写的是Java线程间的共享,及共享的时候出现的线程安全问题,也分析了怎么解决线程安全问题,介绍了对象锁synchronized的使用,但是错误的使用方法,就算你用了synchronized,它也是不安全的
private static class Worker implements Runnable{
private Integer i;
public Worker(Integer i) {
this.i=i;
}
@Override
public void run() {
synchronized (i) {
i++;
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Worker worker=new Worker(1);
for(int i=0;i<5;i++) {
new Thread(worker).start();
}
}
复制代码
上面的代码我们使用了对象锁,但是我们多运行几次,发现运行的结果和我们想象的并不一样,发生了线程不安全,这是为什么呢?
我们就看看这个类编译完之后都干了一些什么了不得的事
看到没,它++的时候,用了Integer的valueOf方法,nice,咱们就去看一眼valueOf方法
看完这个方法,再结合我们上篇文章所说的知识,明白没。也就是说每次我们Worker里的Integer++的时候,他都会返回一个新new出的Integer,也就说我们锁的对象一直在变,所以线程在锁变化的时候,就极有可能产生了不安全
2、volatile
volatile关键字,最轻量的同步机制
volatile只能保证操作的可见性,不能保证操作的同步性,如果我们不加volatile关键字,我们线程之间操作对象的变量是不可见的,什么是不可见的呢
上代码
private static boolean ready;
private static int number;
private static class PrintThread extends Thread{
@Override
public void run() {
System.out.println("PrintThread is running.......");
while(!ready);
System.out.println("number = "+number);
}
}
public static void main(String[] args) {
new PrintThread().start();
SleepTools.second(1);
number = 51;
ready = true;
SleepTools.second(5);
System.out.println("main is ended!");
}
复制代码
上面的代码执行结果,子线程启动后,在主线程里更改了ready和number 的值,子线程并没有停止运行而且也没收到number的值,这就是操作的不可见性
看看我们加了volatile之后的运行结果
private static volatile boolean ready;
private static volatile int number;
复制代码
我们主线程操作number和ready子线程可见,也就结束了子线程的执行打印出来number
3、总结
synchronized它的作用是同步代码块,但是错误的使用方式,也是没有作用的,volatile是最轻量级的同步机制,但是只能保证操作的可见性,保证不了操作的同步性,想要实现操作的同步性,还得用synchronized!如果有不对的地方,请大佬们在评论区指出,我会第一时间进行改正,希望大佬们一键三连!