多线程,各种锁,线程安全性问题、线程活跃性问题
线程实现的方式
实现Runnable,继承Thread,匿名内部类,定时器
wait和sleep区别
wait锁会被释放,在synchronized中通过notify,notifyAll释放,会释放系统资源
sleep锁不会释放,不会释放系统资源,指定时间会自动唤醒
为什么wait和notify、notifyAll要放在synchronized中?
因为他们需要一个同步队列,一个对象锁,一个监视器,只有保证同步才能保证唤醒是同一把锁,只有保证有监视器才能去监视状态,synchronized是一把同步锁和监视器。
活跃性问题
死锁:都有对方的资源,都没有释放,都拿不到想要的资源
饥饿:优先级低的线程就会存在一直获取不到锁的问题,一直轮不到
活锁: 互相礼让
如何解决死锁问题?
互斥条件,占有并等待,非抢占,循环等待
如何解决饥饿问题?
设置优先级,使用synchronized
线程安全性问题
多线程环境下,共享统一资源,非原子性操作会产生安全性问题
解决:使用synchronized同步
偏向锁:每次获取锁和释放锁会浪费资源,一个单线程竞争锁
轻量级锁(自旋锁/无锁):就是一直循环的锁,一个线程只能等待下一个线程结束,才会循环
重量级锁:
锁重入:同一个锁可以被一个线程调用还可以被其他锁调用
公平锁:排队获取锁
不公平锁:争抢锁
读写锁(排他锁和共享锁)
读锁:不互斥/共享锁,效率高
写锁:互斥/排他锁,一次只能一个线程进入
锁降级:将写锁降级为读锁,保证数据一致性
锁升级:将读锁升级为写锁,保证数据一致性,保证数据一致性
private ReentrantReadWriteLock locks = new ReentrantReadWriteLock();
private Lock read = locks.readLock();
private Lock write = locks.writeLock();
Synchronized、DCL(双重检查加锁)
指令重排序:会在不影响最终结果的情况下,执行顺序发生改变
步骤:1.申请一块内存空间 2.在内存中实例化对象 3.引用指向这块空间地址
指令重排序的问题:会出现安全性问题,会出现先执行3,这样对象就不是空了,双重加锁就不起作用
解决办法:加volatile,线程可见性,可以是线程互斥
Volatile
是线程可见性,互斥
AtoMic(原子性操作)
方法:AtomicInteger、Atomic基本类型/数组类型/应用类型
Lock
方便实现公平性
非阻塞的获取锁
能被中断的我获取锁
超时获取锁
AQS(抽象队列同步器)
CAS(比较并交换)
ABA
其他线程修改次数最后值和原值相同,出现线程安全性问题
解决:加版本号
Condition(指定线程释放)
和notifyAll相比它可以指定释放,比较方便
实现类:AQS
await(等待),signal(释放)
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
同步队列,等待队列(每new一个就有一个等待队列,和wait只有一个等待队列这是最大的区别)
ThreadLocal(线程局部变量)
线程私有化,解决线程安全性问题
ThreadLocal会产生内存泄漏?
java的引用类型:
强引用:GC回收不了的
软引用(softReference):内存不够的时候,会被回收
弱引用(weakReference):只要jvm启动回收,就会被回收
虚引用(phantomReference):管理直接内存,就是一个通知信号,不会get到
ThreadLocal 中entry会用到弱引用,通过set会把当前theadlocal作为key,value作为值,会被放在Map中
CyclicBarrier(线程屏障)
允许一组线程全部等待彼此达到共同屏障点的同步辅助。 循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。 屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。
简单点就是用来等待线程,等指定的线程到齐了才会运行。