Java中的线程同步

198 阅读3分钟

为什么需要线程同步?

内存是一个硬件,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<AClass> 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();
    }
}
...