1.基本概念
多线程:这个程序(一个进程)运行时产生了不止一个线程。
并行:多个CPU实例或者多台机器同时执行一段处理逻辑,是真正的同时。
并发:通过CPU调度算法,让用户看上去同时执行,实际上并不是真正的同时。
同步:通过人为的控制和调度,保证共享和可变的资源的多线程访问时线程安全的。
2.线程的状态
1.调用join()或sleep()方法,sleep()时间结束或被打断,join中断,IO完成,都会回到Runnable状态。
2.调用wait(),使该线程处于等待池,直到notify()或者notifyAll(),线程会释放到锁定池,释放同步锁后会进入到Runnable状态。
3.对running状态的线程加同步锁,会使其进入等待池/锁定池。
3.显示锁
1.lock和ReentrantLock
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
内置锁局限性:无法中断一个正在等待获取锁的线程、无法在请求获取锁时无限地等待下去。。。
与内置锁不同的是,Lock提供了一种无条件的、可轮询的、定时的以及可中断的操作,所有加锁和解锁的操作都是显示的。
ReentrantLock实现了Lock,并提供了与synchronized相同的互斥性和内存可见性,在获取reentrantLock时进入同步代码块,在释放reeentrantLock时
退出同步代码块,并且和synchronized提供重入锁。
Lock lock = new ReentrantLock();
...
lock.lock();
try{
//更新对象状态
//捕获异常,并在必要时恢复不变性条件
}finally{
lock.unlock();
}
注意:可重入锁必须在finally中释放锁。而且当且仅当内置锁不能满足需求时(比如:可定时的、可轮询的与可中断的锁获取操作,公平队列以及非块结构 的锁)才会考虑使用ReentrantLock。
2.读-写锁
互斥是一种保守的加锁策略,可以避免”写/写”冲突,”读/写”冲突,但也避免了”读/读”冲突,但大多数情况都是读读比较多,因此如何能放宽加锁需求,允许 “读/读”不加锁将会极大的提高性能。”读/写”锁就解决了这个问题,一个资源可以被多个读操作同时访问,或者被一个写操作访问,但两者不能同时进行。
public interface ReadWriteLock {
//返回读锁
Lock readLock();
//返回写锁
Lock writeLock();
}
//主要实现
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
要获取由ReadWriteLock保护的数据,首先要获得读锁,要修改则要先获得写锁。
读取锁和写入锁之间的交互方式:
- 公平性选择:支持公平与非公平的的锁获取方式,吞吐量非公平优先于公平。
- 可重入:读线程获取锁后,可以再次获取读锁,写锁也一样。
- 可降级:写线程获得写锁后,还可以再次获得读锁,在释放写锁后此时线程是读锁状态,这就是降级。
原理:ReentrantReadWriteLock是由一个基于AQS的同步器Sync构成,而后扩展出ReadLock(共享锁),WriteLock(排他锁)所组成。
读写锁的实现原理
4.原子变量
与锁相比,volatile变量时一种更轻量级的同步机制,因为在使用这些变量的时候不会发生上下文切换或线程调度等工作。然而它也有局限性:
不能用于构建原子的复合操作,因此当一个变量依赖其它的变量时,或者当变量的新值依赖旧值时就不能用volatile变量。
原子变量相当于一种泛化的volatile变量,能够支持原子的和有条件的读-写-改操作。比如,AtomicInteger表示一个int类型的值,并提供get、set方法和
原子的compareAndSet方法,原子的添加、递增和递减等方法。
共有12个原子变量类,分4组:标量类、更新器类、数组类和复合变量类。常见的就是标量类:AtomicInteger、AtomicLong、AtomicReference、AtomicBoolean。
原理:比较并交换(CAS):包含3个操作数,需要读写的内存位置V,进行比较的值A,拟写入的新值B。当且仅当如果V的值等于A,则用B来更新V的值,否则
不执行任何操作。
5.死锁
1.死锁的原因
死锁:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放,而该资源又被其他线程锁定,从而导致每一个线程都得等其它线程释放其锁定的资源,
造成了所有线程都无法正常结束。
死锁产生原因:系统资源的竞争、进程推进顺序非法。
死锁产生的四个必要条件:
- 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
- 不可剥夺,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
- 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
- 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
2.如何避免死锁
- 加锁顺序(线程按照一定的顺序加锁)
- 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
- 死锁检测