场景: 用两个线程对同一个变量分别自增5万次,预期结果和自增结果是一个累加和,10万次。
public static int count;
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 =new Thread(()->{
for(int i=0;i<5_0000;i++){
counter.count();
}
});
Thread t2 =new Thread(()->{
for(int i=0;i<5_0000;i++){
counter.count();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: "+counter.count);
}
public void count(){
{
count+=1;
}
} }
执行结果:
程序运行得到的结果与预期的结果值不一样,而且是一个错误的结果,而且我们程序的逻辑是正确的,这个现象所表现的问题称为线程安全问题
线程是抢占执行的(执行顺序是随机的) 由于线程的执行顺序无法为人控制,抢占式执行是造成线程安全问题的主要罪魁祸首,而且我们解决不了,完全是CPU自己调度,而且和CPU的核数有关
单个线程修改同一个变量不会产生线程安全问题 多个线程修改不同的变量不会产生线程安全问题 多个线程修改同一个变量,会产生线程安全问题
1.什么是指令重排序 我们写的代码,在编译之后可能与代码对应的指令顺序不同,这个过程就是指令重排序(JVM层面可能重排序,CPU执行指令也可能重排序) 1.一段代码是这样的 a.代阳去教室取英语书 b.代阳去食堂吃饭 c.代阳去教室去数学书 在单线程情况下,JVM,CPU指令集会对其优化,执行顺序按a–c–b的方式执行,也是没有问题,可以少跑一次教室,这就叫指令重排序 编译器对于指令重排序的前提是 “保持逻辑不发⽣变化”. 这⼀点在单线程环境下⽐较容易判断, 但是在多线程环境下就没那么容易了, 多线程的代码执⾏复杂程度更⾼, 编译器很难在编译阶段对代码的执⾏效果进⾏预测, 因此激进的重排序很容易导致优化后的逻辑和之前不等价
比如线程A拿到了锁,别的线程如果要执行被锁住的代码,那就要等到线程A释放锁之后,如果A没有释放锁,那么别的线程只能阻塞等待,这个状态就是BLOCK
修饰方法:
public synchronized void count(){ // 修饰代码块加锁synchronized(this){ // count+=1; // }
synchronized(this){
count+=1;
}
} }
如果修饰方法:其实把方法进行了串行化处理 如果修饰的是代码块:其实把修饰代码块的内容,进行了串行话处理。对于部分类似这种要修改共享变量的情况进行串行话,其他代码模块继续并行执行,这样就可以提高效率
画图分析:
注意的点: t1释放锁之后,也可能第二次还是t1先于t2拿到锁,因为线程是抢占式执行的,不一定是t2 由于线程在执行逻辑之前要拿到锁,当拿到锁时,上一个线程已经执行完所有的指令,并把修改的值刷新会主内存,所有当前线程永远读到的是上一个线程执行完后的值 synchronized保证了原子性 因为当前线程永远拿到的是前一个线程修改后的值,所有这样也现象上实现了内存可见性,但是并没有真正对内存可见性做出技术上的处理。 synchronized没有保证有序性(不会禁止指令重排序)
public static int count;
public static void main(String[] args) {
Counter_Demo1 counter = new Counter_Demo1();
Thread t1 =new Thread(()->{
for(int i=0;i<5_0000;i++){
counter.count();
}
});
Thread t2 =new Thread(()->{
for(int i=0;i<5_0000;i++){
counter.count1();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: "+counter.count);
}
public void count(){
//修饰非静态代码块
synchronized(this){
count+=1;
}
}
public void count1(){
{
count+=1;
}
}
//修饰非静态方法
// public void synchronized count(){
// {
// count+=1;
// }
// }
// public void count1(){
// {
// count+=1;
// }
// }
修饰静态的方法和代码块
// public static void count(){
// //修饰代码块
// //静态方法里面不能用this
// synchronized(Counter_Demo1.class){
// count+=1;
// }
// }
// public static void count1(){
// {
// count+=1;
// }
// }
// public synchronized static void count(){
// //修饰代码块
// //静态方法里面不能用this
// {
// count+=1;
// }
// }
// public static void count1(){
// {
// count+=1;
// }
// }
执行结果 都不符合预期
public static int count;
public static void main(String[] args) {
Counter_Demo1 counter1 = new Counter_Demo1();
Counter_Demo1 counter2 = new Counter_Demo1();
Thread t1 =new Thread(()->{
for(int i=0;i<5_0000;i++){
counter1.count();
}
});
Thread t2 =new Thread(()->{
for(int i=0;i<5_0000;i++){
counter2.count();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: "+counter1.count);
}
Object object=new Object();
public void count(){
synchronized (object){
count+=1;
}
}
执行结果不符合预期
用类对象来加锁
static Object object= new Object();
public void count(){
synchronized (object){
count+=1;
}
}
执行结果符合预期
结论:
-Vector(不推荐)
static class Counter {
public volatile int flag = 0;
}
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
while (counter.flag == 0) {
// do nothing
}
System.out.println("循环结束!");
});
Thread t2 = new Thread(() -> {
Scanner scanner = new Scanner(System.in);
System.out.println("输⼊⼀个整数:");
counter.flag = scanner.nextInt();
});
t1.start();
t2.start();
}
// 执⾏效果
// 当⽤⼾输⼊⾮0值时, t1 线程循环不会结束. (这显然是⼀个 bug)
//static class Counter {
// public volatile int flag = 0;
//}
// 执⾏效果
// 当⽤⼾输⼊⾮0值时, t1 线程循环能够⽴即结束.
MESI缓存 一致协议(可以理解是一种通知机制)
内存屏障:作用是保证指令执行的顺序,从而保证内存可见性
volatile写:
volatile读:
有序性:用volatile 修改过的变量,由于前后有内存屏障,保证了指令的执行顺序,也可以理解为告诉编译器,不要进行指令重排序。
volatile不保证原子性
public static volatile int count;
public synchronized static void main(String[] args) {
Counter counter = new Counter();
Thread t1 =new Thread(()->{
for(int i=0;i<5_0000;i++){
counter.count();
}
});
Thread t2 =new Thread(()->{
for(int i=0;i<5_0000;i++){
counter.count();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: "+counter.count);
}
public void count(){
count+=1;
}
volatile保证可见性:MESI缓存 一致协议(可以理解是一种通知机制) volatile保证有序性:内存屏障:作用是保证指令执行的顺序,从而保证内存可见性
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
System.out.println("等待中");
synchronized (object) {
object.wait(1000);
}
System.out.println("等待结束");
}
这样在执⾏到object.wait()之后就⼀直等待下去,
那么程序肯定不能⼀直这么等待下去了。这个时候就
需要使⽤到了另外⼀个⽅法唤醒的⽅法notify()。
static class WaitTask implements Runnable {
private Object locker;
public WaitTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker){
try {
System.out.println("等待开始") ;
locker.wait();
System.out.println("等待结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class NotifyTask implements Runnable {
private Object locker;
public NotifyTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker) {
System.out.println("notify 开始");
locker.notify();
System.out.println("notify 结束");
}
}
}
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(new WaitTask(locker));
Thread t2 = new Thread(new NotifyTask(locker));
t1.start();
Thread.sleep(1000);
t2.start();
}
static class WaitTask implements Runnable {
private Object locker;
public WaitTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker) {
try {
System.out.println("等待开始");
locker.wait();
System.out.println("等待结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class NotifyTask implements Runnable {
private Object locker;
public NotifyTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker) {
System.out.println("notify 开始");
locker.notifyAll();
System.out.println("notify 结束");
}
}
}
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(new WaitTask(locker));
Thread t3 = new Thread(new WaitTask(locker));
Thread t4 = new Thread(new WaitTask(locker));
Thread t2 = new Thread(new NotifyTask(locker));
t1.start();
t3.start();
t4.start();
sleep(1000);
t2.start();
}
}
注意: 虽然是同时唤醒 3 个线程, 但是这 3 个线程需要竞争锁. 所以并不是同时执⾏, ⽽仍然是有先有后的执⾏
– wait的线程进入阻塞状态,调用wait的线程会释放自己持有的锁(不再占有cpu资源)
– 指令举例