系列:多线程(二)线程安全

系列:多线程(二)线程安全

原子三大特性

原子性

可见性

有序性

模拟不安全问题

原子性

一个对象Count count,10个线程执行+1 100次,输出最后结果(正确:1000)

demo
@Data
class Count {
    private int count;

    public void add() {
        ++count;
    }
}

void test2() throws InterruptedException {
    ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 10, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
    Count count = new Count();
    for (int i = 0; i < 10; ++i) {
        pool.submit(() -> {
            for (int j = 0; j < 100; ++j) {
                count.add();
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    pool.shutdown();
    TimeUnit.SECONDS.sleep(3);
    System.out.println(count.getCount());
}
输出

789、813…

解决

synchronized、Lock、原子类…

public synchronized void add() {
    ++count;
}
public synchronized int getCount() {
    return count;
}
可见性
demo
@Setter
class Task extends Thread {
    private  boolean flag = false;

    public boolean isFlag() {
        return flag;
    }

    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        flag = true;
        System.out.println(Thread.currentThread().getName() + " flag = " + flag);
    }
}

public class VolatileTest {
    public static void main(String[] args) throws InterruptedException {
        Task task = new Task();

        task.start();
        while (true) {
            if (task.isFlag()) {
                System.out.println("终止了");
                break;
            }
        }
    }
}
输出

Thread-0 flag = true

// 主线程没有输出,一直while循环,检测,但是由于flag非volatile,task线程即使修改了flag = true,主线程仍获取不到其值变化

提示:如果主线程在while循环内,加上sleep,就可以检测到flag的变化

解决

private volatile boolean falg = false;

有序性(略)

线程安全

不可变必然安全

  1. 线程间不共享

    • 不共享
  2. 线程间共享

    • ThreadLocal:线程创建共享变量的副本

    • 线程同步

原理

CPU

参考:https://juejin.im/post/5c6b99e66fb9a049d51a1094

JMM内存操作

lock和unlock

read和load

use和assign

store和write

happens-before原则

1)程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作。
2)锁定规则:一个unLock操作先行发生于后面对同一个锁的Lock()操作。
3)volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作。
4)传递规则:如果操作A先行发生与操作B,而操作B先行发生于操作C,则操作A先行发生于操作C。
5)线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。
6)线程终端规则:对线程interrupt()方法的调用先行发生与被中断线程的代码检测到中断事件的发生(只有执行了interrupt()方法才可以检测到中断事件的发生)。
7)线程终结规则:线程中所有操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束,Thread.isAlive()的返回值手段检测到线程已经终止执行。
8)对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始。

内存屏障:禁止重排序

内存屏障前和后的不能乱序,即内存屏障前不能重排序到屏障后,内存屏障后不能重排序到屏障前。

内存屏障:volatile变量读写前后加指令

对象的布局

对象头 + 类型指针 + 实力数据 + 对齐

图源自:https://www.bilibili.com/video/BV1xK4y1C7aT?p=3

在这里插入图片描述

volatile

作用:线程可见性;防止指令重排序

  1. Java: volatile int i

  2. class: ACC_VOLATILE

  3. JVM的内存屏障

    屏障两端不可以重排

  4. Hotspot实现:lock指令

JSR内存屏障:

LoadLoad
StoreStore
LoadStore
StoreLoad

volatile写

StoreStore
volatile 写
StoreLoad

volatile读

LoadLoad
volatile 读
LoadStore
synchronized

作用:同步

锁升级:无锁 --> 偏向锁 --> 轻量级锁 --> 重量级锁

字节码:

语句块:monitorenter和monitorexit

方法:flags加ACC_SYNCHRONIZED(执行方法前先获取monitor,执行完后释放monitor)

monitor

任何一个对象都有一个monitor与之关联,当一个monitor被持有后,对象将处于锁定状态。

synchronized对象被锁,MarkWord的锁标识位为10(重量级锁),其指针指向monitor对象的起始地址。

锁优化

自适应CAS自旋

锁消除

锁粗化

偏向锁,轻量级锁

偏向锁和轻量级锁
偏向锁:ThreadID的CAS
轻量级锁:MarkWord的lock word的CAS更新为当前线程Lock Record的指针,并将Lock Record的owner指针指向锁对象MarkWord
	线程栈帧:Lock Record,锁记录,当前锁对象的MarkWord拷贝
重量级锁

涉及底层OS,存在用户态到核心态的切换。

参考

https://juejin.im/post/5c6b99e66fb9a049d51a1094#heading-24

https://blog.csdn.net/u011877584/article/details/81157317

https://www.bilibili.com/video/BV1xK4y1C7aT?p=6

https://juejin.im/post/5b4eec7df265da0fa00a118f

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值