公司的项目开始用到多线程。有用过多线程,但是了解不深 又去学习了下 学习资料:王宝令《java并发编程实战》 看完且搞懂前15节基本就都明白了
大概总结一下:
并发编程会导致线程不安全,常说的线程不安全指的是 多个线程操作一个共享数据,导致线程之间的读取到的数据不一致。
1.并发编程导致线程不安全的根源
1 可见性 cpu缓存导致。 一般cpu缓存中进行操作之后再将数据写到内存,在多核服务器中 每个线程都会分配一个cpu 都会在各自的cpu中进行处理再将数据统一写到内存中。每个cpu缓存中的数据对于其他的线程,都是不可见的。导致最后写入内存,然后再从内存中读出来时候数据不一致。
解决办法:禁用缓存,使用volidate 关键字和 happen-before 规则就可以避免
volidate:告诉编译器,对这个变量的读写,不能使用 CPU 缓存,必须从内存中读取或者写入
happen-before :前面一个操作的结果对后续操作是可见的
2 原子性 线程切换导致,在java语句中一行语句在计算机底层进行了多次操作。比如创建一个对象,new 在堆中开辟一块空间 将地址赋予这个对象 最后才会在这个空间上初始化对象。如果只是进行到地址值赋予对象,并未创建出对象,此时发生线程的切换 在运行的时候就会出翔空指针的情况。就可能会报错
解决办法:用synchornized
3有序性 编译优化产生。代码在JVM上执行的时候都会进行一定的编译优化,我们所写的代码在优化之后 执行顺序可能变换,可能在创建对象的时候 先是将地址指向了对象然后再分配的内存空间。
解决办法:禁用编译优化
理论上 那全部用synchornized,禁用编译优化和缓存,完美解决所有问题。但是在实际开发工程中,显然不现实,我们需要考虑到性能。
大家也许都知道,实际上所有的并发问题都可以用信号量来解决。这个信号量实际上和管程是一样的 ,都能用来处理并发问题。
管程是一种模型,是一把解决并发的万能钥匙,Java中的synchronized时间上就是采用了管程的思想。配合 wait() notify().notifyAll().
在synchronized下面会自动的加上 lock unlock的操作,必须要注意的是 加锁的资源一定要不变。对象是this,整个类为字节码对象。
比如 a转账给b 两个对象的账户都要被锁定。怎么办?
这个时候先锁定 a 然后再锁定b.如果在a转给 b,a账户被锁定,等待锁定b账户,b此刻也要转给a,b锁定等待锁定a.此刻,就出现了一组资源相互等待各自所需资源,而出现的一组永久循环等待的现象。
这就是死锁!
一般发生死锁没有什么好的办法 一般只有重启服务器。所以只能去避免死锁的发生。
首先要搞清楚 发生死锁的必要条件
1 互斥
2 占用且等待
3 不可抢占
4 循环等待
理论上只要打破其中的一个条件就能避免死锁,但是 我们 用锁就是希望互斥,所以这个没办法去处理 不可抢占的特性可以使用第三方的sdk来解决。
现在讲一下占用且等待。实际上 ,我们不要一个一个去锁定,而是等到拿到两个账户了一起锁定。比如我们可以创建一个单例的工具类去获取这两个对象,如果只拿到其中的一个的话都不会锁定。
对于循环等待的话 我们可以定一个一定的规则来进行排序 如果需要一组细粒度锁 会将两个锁进行排序 从小到大的顺序进行加锁 这样也能避免这个循环等待的问题
总之,在使用细粒度锁一定要注意死锁的问题。
其实不只是死锁,也会有活锁和饥饿两种情况也会导致线程执行不下去的情况。
活锁指的是当