线程安全的问题
2019-03-12 本文已影响0人
Okami_
为什么会有线程安全的问题
- 在Java虚拟机中,每个线程 的操作都在自己的工作内存中,当要操作主内存中的数据的时候,会先将主内存的数据读取到线程工作内存.操作完成之后再写回到主内存.但是线程之间是无法直接通信的.
处理线程安全的方式
一.不共享
- 1.不使用线程共享数据
- 2.让共享数据不可变
- 3.栈封闭
二.共享但是加锁
- synchornized
- synchornized是java内置关键字
- 属于jvm层面的锁
- 无法判断是否获取锁的状态
- synchronized会自动释放锁
- 如果两个线程同时竞争一个锁,获得锁的线程阻塞,没有获取锁的线程会一直等待
- synchronized锁可重入,不可中断,非公平锁
- Lock
- lock是java.util.concurrent包的接口,有多种实现类
- lock可以判断锁的状态
- 需要在最后手动执行unlock方法,否则容易造成死锁
- 可以尝试获取锁,如果获取不到可以立即返回,也可以在一段时间内获取不到立即返回失败
- 可重入,可中断,可以为公平锁, 也可以为非公平锁
- 可能会存在活锁的问题
- volatile(内存栅栏)
- 禁止指令重排
- 更新的内容的时候,直接写到主内存,其他线程读取的时候从主线程读取,保证了内存可见性
- 缺点:volatile无法保证原子性,多个线程同时对变量i做自增操作的话,会存在问题
- CAS操作
- CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做
- Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性
- 缺点: 如果变量的值在执行cas操作的时候,已经经历了A->B->A的过程,无法识别
- 写时复制
- 在并发访问的情景下,当需要修改JAVA中Containers的元素时,不直接修改该容器,而是先复制一份副本,在副本上进行修改。修改完成之后,将指向原来容器的引用指向新的容器(副本容器)。
- 由于不会修改原始容器,只修改副本容器。因此,可以对原始容器进行并发地读。其次,实现了读操作与写操作的分离,读操作发生在原始容器上,写操作发生在副本容器上
- 数据一致性问题:读操作的线程可能不会立即读取到新修改的数据,因为修改操作发生在副本上。但最终修改操作会完成并更新容器,因此这是最终一致性
死锁
-
死锁是指多个进程在执行过程中,由于竞争资源或者由于彼此通信造成的一种阻塞的现象,若无外力作用,他们经无法推进.此时称系统产生了死锁
-
程序的表现: 程序无法向前推进
-
本地查看
- dump
- 如果程序监测到java层死锁的话,会有日志
Found one Java-level deadlock
-
远程查看
- jsp
- jstack
# 把所有在运行的java进程打印出来
jps -v
# 打印对应进程的dump信息
jstack [进程号]