为什么需要线程同步?
内存是一个硬件,CPU计算速度比内存快很多,程序计算的问题都由CPU来完成的,但不是每次计算,CPU都和内存进行数据交互,CPU会先把一些数据写在缓存里,完成计算的时候,才把结果写到内存里。所以在多线程情况下,可能一个线程拿到CPU使用权,已经在计算,但是还没有完成计算的时候,另一个线程对内存中同一个数据进行操作,这时候拿到的数据,可能是一个不正确的数据。一个线程对共享数据的修改对另一个线程而言,可能是不可见的,这就是内存的可见性问题。
这时候我们需要考虑线程同步的问题。
线程同步的方式有哪些?
Lock: lock()+unlock()
void lock() :获得锁;如果锁被其他线程持有则阻塞线程。
void unlock() :释放锁。
...
例子1
Lock mlock=new ReentrantLock();
...
pulic void lockMethod(){
mlock.lock();
try{
...
}finally{
mlock.unlock();//在finally中执行解锁,确保异常情况下仍居可以解锁,否则可能导致所有线程阻塞。
}
}
复制代码Condition: await()+signal() ,await()+signalAll()
之所以需要Condition,就像他的名字一样,正是需要某个条件方法才能正确执行。比如执行同步的方法需要某个条件,才能继续,否则就等待。但是可能本身就需要再次执行锁方法,才能达到这个条件。lock()并不会释放锁,这时候Condition的await(),就能达到目的,他阻塞当前线程,把线程放到Condition管理的集合中,然后释放锁,当达到前面提到的某个条件后,就调用Condition的signalAll()解除Condition管理集合中所有线程的阻塞状态。
Condition是锁相关的条件,可以通过锁的对象调用Condition newCondition()来构造。
Tip: Condition实例管理的是已经进入锁保护范围代码区域的线程集合。
await()的调用通常在一个循环体中。
例子2
Lock mlock=new ReentrantLock();
Condition mcondition;
public void method(){
if(mcondition==null){
mcondition=mlock.newCondition();
}
mlock.lock();
try{
while(!(是否符合条件的boolean值)))
mcondition.await();
//等待其他线程执行,来给阻塞的线程解开阻塞状态
mcondition.signalAll();
}finally{
mlock.unlock();
}
}
复制代码Synchronized,
例子3
public synchronized void method(){
...
}
复制代码
等介于
例子4
public void method(){
yourlockinstance.lock();
try{
...
}finally{
yourlockinstance.unlock();
}
}
复制代码Tip:
调用Object的wait(),notifyAll(),notify()效果和Condition的await(),signalAll(),signal()一致。
例子5,等介于例子2:
public synchronized void method(){
while(!(是否达到执行条件))
wait();
//等待其他线程来达到某个条件
notifyAll();
}
复制代码同步阻塞
除了线程调用Lock对象的Lock()和线程调用被Synchronized修饰的方法能获得锁外,还可以通过给任意的Java对象加Synchronized来使调用代代码块的线程获取对象的锁:
Object lock=new Object();
synchronized(lock){
...
}
复制代码volatile关键字可以同步实例域的访问
private volatile A a;
复制代码final关键字能使域被多过线程安全地访问
final AClass a=new AClass();
复制代码
(不加final其他线程可能在AClass构造前访问a,就可能得到a=null的结果。)
死锁
两把锁,互相等待对方的某个动作才能达到继续程序的条件,就发生死锁了。
ThreadLocal可以为各自线程管理各自的实例
final ThreadLocal a=ThreadLocal.withInitial(()->new AClass());
//调用get()就可以得到当前线程的实例
T t=a.get();
复制代码尝试获得锁
boolean isgetlock = yourlock.tryLock();
复制代码锁超时机制
boolean isgetlock = yourlock.tryLock(long time,TimeUnit unit);
复制代码读写锁
在多线程频繁地读,但很少写,就适用读写锁
ReentrantReadWriteLock rwl=new ReentrantReadWirteLock();
...
Lock rlock=rwl.readLock();
Lock wlock=rwl.writeLock();
public int getNum(){
rlock.lock();
try{
...
}finally{
rlock.unlock();
}
}
public void setNum(){
wlock.unlock();
try{
...
}finally{
wlock.unlock();
}
}
...
复制代码