概述:
在java多线程中,有synchronized关键字来实现线程间的同步互斥工作,
那么其实还有一个更优秀的机制去完成这个“同步互斥”工作,他就是Lock对象,
用得最多的是重入锁ReentrantLock和读写锁ReentrantReadWriteLock。他们具有比synchronized更为强大的功能,并且有嗅探锁定、多路分支等功能。重入锁ReentrantLock:
在需要进行同步的代码部分加上锁定,但不要忘记最后一定要释放锁定,不然会造成锁永远无法释放,其他线程永远进不来的结果。
public class UseReentrantLock { private Lock lock = new ReentrantLock(); public void method1(){ try { lock.lock(); System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method1.."); Thread.sleep(1000); System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method1.."); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void method2(){ try { lock.lock(); System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method2.."); Thread.sleep(2000); System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method2.."); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { final UseReentrantLock ur = new UseReentrantLock(); Thread t1 = new Thread(new Runnable() { @Override public void run() { ur.method1(); ur.method2(); } }, "t1"); t1.start(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } }
读写锁ReentrantReadWriteLock:
其核心就是实现读写分离的锁。在高并发访问下,尤其是读多写少的情况下,性能要远高于重入锁。
synchronized、ReentrantLock,同一时间内,只能有一个线程进行访问被锁定的代码,
那么读写锁则不同,其本质是分成两个锁,即读锁、写锁。在读锁下,多个线程可以并发的进行访问,但是在写锁的时候,只能一个一个的顺序访问。
口诀:读读共享(只有所有线程都是读才是共享),写写互斥,读写互斥(重要:这个一定得互斥)。
package com.bjsxt.height.lock021; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; public class UseReentrantReadWriteLock { private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); private ReadLock readLock = rwLock.readLock(); private WriteLock writeLock = rwLock.writeLock(); public void read(){ try { readLock.lock(); System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..."); Thread.sleep(3000); System.out.println("当前线程:" + Thread.currentThread().getName() + "退出..."); } catch (Exception e) { e.printStackTrace(); } finally { readLock.unlock(); } } public void write(){ try { writeLock.lock(); System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..."); Thread.sleep(3000); System.out.println("当前线程:" + Thread.currentThread().getName() + "退出..."); } catch (Exception e) { e.printStackTrace(); } finally { writeLock.unlock(); } } public static void main(String[] args) { final UseReentrantReadWriteLock urrw = new UseReentrantReadWriteLock(); Thread t1 = new Thread(new Runnable() { @Override public void run() { urrw.read(); } }, "t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { urrw.read(); } }, "t2"); Thread t3 = new Thread(new Runnable() { @Override public void run() { urrw.write(); } }, "t3"); Thread t4 = new Thread(new Runnable() { @Override public void run() { urrw.write(); } }, "t4"); /** * 两个读锁: * 当前线程:t2进入... * 当前线程:t1进入... * 当前线程:t2退出... * 当前线程:t1退出... * 结论:两个都是读锁,可以同时进入 */ // t1.start(); // t2.start(); /** * 读写两个锁: * 当前线程:t1进入... * 当前线程:t1退出... * 当前线程:t3进入... * 当前线程:t3退出... * 结论:读写两个锁:互斥进入,谁先抢到锁,谁先进入,下个线程只有等前一个锁释放了才能进 */ // t1.start(); // R // t3.start(); // W /** * 两个写锁: * 当前线程:t3进入... * 当前线程:t3退出... * 当前线程:t4进入... * 当前线程:t4退出... * 结论:互斥 */ t3.start(); t4.start(); } } 锁与等待/通知。 (Lock替代synchronize,Condition代替wait,notify)使用synchronized的时候,如果需要多线程间进行协作工作则需要Object的wait()和notify()、notifyAll()方法进行配合工作。那么同样,我们在使用Lock的时候,可以使用一个新的等待/通知的类,它就是Condition。
这个Condition一定是针对具体某一把锁的。也就是在只有锁的基础之上才会产生Condition。
public class UseCondition { private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void method1(){ try { lock.lock(); System.out.println("当前线程:" + Thread.currentThread().getName() + "进入等待状态.."); Thread.sleep(3000); System.out.println("当前线程:" + Thread.currentThread().getName() + "释放锁.."); condition.await(); //相当于 Object wait System.out.println("当前线程:" + Thread.currentThread().getName() +"继续执行..."); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void method2(){ try { lock.lock(); System.out.println("当前线程:" + Thread.currentThread().getName() + "进入.."); Thread.sleep(3000); System.out.println("当前线程:" + Thread.currentThread().getName() + "发出唤醒.."); condition.signal(); //相当于 Object notify } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { final UseCondition uc = new UseCondition(); Thread t1 = new Thread(new Runnable() { @Override public void run() { uc.method1(); } }, "t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { uc.method2(); } }, "t2"); t1.start(); t2.start(); } } 当前线程:t1进入等待状态.. 当前线程:t1释放锁.. 当前线程:t2进入.. 当前线程:t2发出唤醒.. 当前线程:t1继续执行... 我们可以通过一个Lock对象产生多个Condition进行多线程间的交互,非常的灵活。可以使得部分需要唤醒的线程唤醒,其他线程则继续等待通知。 package com.bjsxt.height.lock020; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class UseManyCondition { private ReentrantLock lock = new ReentrantLock(); private Condition c1 = lock.newCondition(); private Condition c2 = lock.newCondition(); public void m1(){ try { lock.lock(); System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m1等待.."); c1.await(); //await能释放锁,但是会阻塞在这里 System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m1继续.."); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void m2(){ try { lock.lock(); System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m2等待.."); c1.await(); //await能释放锁,但是会阻塞在这里 System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m2继续.."); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void m3(){ try { lock.lock(); System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m3等待.."); c2.await(); //await会释放锁,但是会阻塞在这里 System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m3继续.."); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void m4(){ try { lock.lock(); System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒.."); c1.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void m5(){ try { lock.lock(); System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒.."); c2.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { final UseManyCondition umc = new UseManyCondition(); Thread t1 = new Thread(new Runnable() { @Override public void run() { umc.m1(); } },"t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { umc.m2(); } },"t2"); Thread t3 = new Thread(new Runnable() { @Override public void run() { umc.m3(); } },"t3"); Thread t4 = new Thread(new Runnable() { @Override public void run() { umc.m4(); } },"t4"); Thread t5 = new Thread(new Runnable() { @Override public void run() { umc.m5(); } },"t5"); t1.start(); // c1 t2.start(); // c1 t3.start(); // c2 //过一会再唤醒线程 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } t4.start(); // c1 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } t5.start(); // c2 } } 当前线程:t1进入方法m1等待.. 当前线程:t3进入方法m3等待.. 当前线程:t2进入方法m2等待.. 当前线程:t4唤醒.. 当前线程:t1方法m1继续.. 当前线程:t2方法m2继续.. 当前线程:t5唤醒.. 当前线程:t3方法m3继续.. 公平锁和非公平锁: Lock lock = new ReentrantLock(boolean isFair); //不公平锁,顺序由CPU定,公平锁,要维护顺序,性能不及不公平锁 lock用法: tryLock(): 尝试获得锁,获得结果用true/false返回。 tryLock():在给定的时间内尝试获得锁,获得结果用true/false返回。 isFair():是否是公平锁。 isLocked():是否锁定。 getHoldCount(): 查询当前线程保持此锁的个数,也就是调用lock()次数。 lockInterruptibly():优先响应中断的锁。 getQueueLength():返回正在等待获取此锁定的线程数。 getWaitQueueLength():返回等待与锁定相关的给定条件Condition的线程数。 hasQueuedThread(Thread thread): 查询指定的线程是否正在等待此锁。 hasQueuedThreads(): //查询是否有线程正在等待此锁。 hasWaiters():查询是否有线程正在等待与此锁定有关的condition条件。 优化: 1 避免死锁 2 减小锁的持有时间 3 减小锁的粒度 4 锁的分离 5 尽量使用无锁的操作,如原子操作(Atomic系列类),volatile关键字