JAVA 多线程(8):synchronized 的同伙lock
Lock:
lock对象功能类似synchronized ,但是更加方便,或者说有更多的功能。
实现类:
1.ReentrantLock
2.ReentrantReadWriteLock : 读写互斥,比1功能再多一点
一、ReentrantLock
首先回顾一下synchronized:
public void test(){ synchronized (this){ System.out.println(Thread.currentThread().getName()+"begin"); try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+"end"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args){ TestRLock testRLock = new TestRLock(); Runnable runnable = new Runnable() { @Override public void run() { testRLock.test(); } }; Thread t = new Thread(runnable); Thread t2 = new Thread(runnable); t.start(); t2.start(); }
输出:
结果是同步的。
看一下lock 实现:
private Lock lock = new ReentrantLock(); public void test() { lock.lock(); System.out.println(Thread.currentThread().getName() + "begin"); try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "end"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }
输出结果与上面的一致。
相比synchronized个人感觉更清晰,至少不用去找大括号了。
关于lock 的wait与notify:
lock 同样有等待/通知机制,只不过调用的方法名称不同,并且细粒度更强。
在调用时,需要声明一个条件对象 condition(跟wait一样,必须在lock范围内),如下;
private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void test() { lock.lock(); System.out.println(Thread.currentThread().getName() + "begin"); try { condition.await(); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "end"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void test2() { lock.lock(); System.out.println(Thread.currentThread().getName() + "begin"); try { condition.signal(); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "end"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { TestRLock testRLock = new TestRLock(); Runnable runnable = new Runnable() { @Override public void run() { testRLock.test(); } }; Runnable runnable2 = new Runnable() { @Override public void run() { testRLock.test2(); } }; Thread t = new Thread(runnable); Thread t2 = new Thread(runnable2); t.start(); try { Thread.sleep(2000); t2.start(); } catch (InterruptedException e) { e.printStackTrace(); } }
输出:
wait 对应的是 await,notify 对应的是signal,notifyAll 对应的是signalAll。
区别:使用condition 的好处是可以控制通知部分线程,因为conditon 可以创建多个的缘故。
如下:
private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); private Condition condition2 = lock.newCondition(); public void test() { lock.lock(); System.out.println(Thread.currentThread().getName() + "begin"); try { condition.await(); System.out.println(Thread.currentThread().getName() + "end"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void test3() { lock.lock(); System.out.println(Thread.currentThread().getName() + "begin"); try { condition2.await(); System.out.println(Thread.currentThread().getName() + "end"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void test2() { lock.lock(); condition.signal(); System.out.println(Thread.currentThread().getName() + "唤醒了等待1"); lock.unlock(); } public static void main(String[] args) { TestRLock testRLock = new TestRLock(); Runnable runnable = new Runnable() { @Override public void run() { testRLock.test(); } }; Runnable runnable2 = new Runnable() { @Override public void run() { testRLock.test2(); } }; Runnable runnable3 = new Runnable() { @Override public void run() { testRLock.test3(); } }; Thread t = new Thread(runnable, "我是等待1"); Thread t3 = new Thread(runnable3, "我是等待2"); Thread t2 = new Thread(runnable2, "我来唤醒1"); t.start(); t3.start(); try { Thread.sleep(3000); t2.start(); } catch (InterruptedException e) { e.printStackTrace(); } }
输出:
例子中增加了一个conditon 实例,并且conditon2的等待是没有人通知他的。由此实现的是通知部分等待线程。
其他功能:
1.getHoldCount() :获取当前锁定的个数,在unlock之前保持此锁的个数。
2.getQueueLength():获取等待锁释放的线程数
3.getWaitQueyeLenth():获取调用await的conditon的线程数,如果有5个线程同时调用了同一个codition的await,且都没有调用signal 释放锁,那么此值为5.
4.hasQueueThread():查询某个线程是否获得了锁 ,返回值为布尔类型
5.hasQueueThreads():查询是否有线程在等待调用锁,返回值为布尔
6.hasWaiters():查询是否有线程在等待conditon 通知
7.isFair():判断是否为公平锁。 公平锁-》是否为先进先出。在创建锁实例时是否设定。
8.isHeldByCurrentThread():当前线程是否保持了该锁。
9.isLocked():查询lock是否被任意线程保持。
还有其他的一些用法,在此不一个个敲字了。。。
二、ReentrantReadWriteLock
从名字看出,它持有2把锁,一把read锁不互斥,也就是共享锁,另一把write锁互斥。
读读共享:
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); public void test() { lock.readLock().lock(); System.out.println(Thread.currentThread().getName() + System.currentTimeMillis()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } lock.readLock().unlock(); } public void test2() { lock.writeLock().lock(); System.out.println(Thread.currentThread().getName() + System.currentTimeMillis()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } lock.writeLock().unlock(); } public static void main(String[] args) { TestRLock testRLock = new TestRLock(); Runnable runnable = new Runnable() { @Override public void run() { testRLock.test(); } }; Thread t = new Thread(runnable, "A"); Thread t2 = new Thread(runnable, "B"); t.start(); t2.start(); }
输出:
写写互斥:
把main主线程调用中runnable run 方法改为调用test2
输出:
由结果看出,写锁必须等待,也就是同步操作。
读写互斥(凡是有写入操作的,都会等待)
main 方法修改为:
public static void main(String[] args) { TestRLock testRLock = new TestRLock(); Runnable runnable = new Runnable() { @Override public void run() { testRLock.test(); } }; Runnable runnable2 = new Runnable() { @Override public void run() { testRLock.test2(); } }; Thread t = new Thread(runnable, "A"); Thread t2 = new Thread(runnable2, "B"); t.start(); t2.start(); }
输出:
总结:
其实如果没有太需要的话使用synchronized 就足够了,但是如果追求控制欲的同学可以使用lock,对么?