本文共 7168 字,大约阅读时间需要 23 分钟。
实现层面
公平性
中断等待
如果A和B都要获得对象O的锁定,假设A获取对象O锁,B将等待A释放对O的锁定 1.synchronized: 如果A不释放,B将一致等待下去,不能被中断 2.ReentrantLock:拥有synchronized相同语义,还多了锁投票,定时锁等候,中断锁等候。 如果A不释放,可以使B在等待了足够长时间以后,中断等待,转而去干别的事情。因此在高竞态的并发环境下,并发吞吐量会高于synchronized
更精细的同步操作
1.相比synchronized通过wait(),notify(),sleep()等方法只能指定一个隐含条件去进行同步操作,多一个条件则需要再加一个锁2.ReentranLock实现精细同步操作: a.带超时获取锁尝试 b.可以判断是否有线程,或某个特定线程在排队时等待获取锁 c.可以响应中断请求3.ReentranLock通过条件变量(Condition)对象同时绑定多个Condition对象,实现一把锁绑定多个条件 a.将wait,notify,notifyAll,signal等转化为响应对象, b.将复杂晦涩同步操作转变为直观可控对象行为 c.可以进行多个条件绑定实现同步操作。
性能
1.早期synchronized标记低效,相对ReentrantLock,大部分场景都比较差2.java6 对synchronized有非常大改进,在高竞态情况下,ReentranLock仍有一定优势(中断等待,转而去干别的事情)
1.封装:通过封装,我们可以将对象内部状态隐藏、保护起来2.不可变:将数据对象设置为不可变。java目前无真正意义的原生不可变。
定义:相关操作不会中途被其他线程干扰,一般通过同步机制实现
非原子性操作
例子:通过取两次数值然后进行对比,来模拟两次对共享状态(变量)的操作。 1.执行后可以看到仅仅两个线程低度并发,就非常容易碰到former,latter变量不相等情况。 2.原因是在取值过程中,其他线程可能已经对共享状态进行修改 3.代码及运行结果如下 public class ThreadSafeSample { public int sharedState; public void nonSafeAction() { while (sharedState < 100000) { int former = sharedState++; int latter = sharedState; if (former != latter - 1) { System.out.printf("Observed data race, former is " + former + ", " + "latter is " + latter); } } } public static void main(String[] args) throws InterruptedException { ThreadSafeSample sample = new ThreadSafeSample(); Thread threadA = new Thread(){ public void run(){ sample.nonSafeAction(); } }; Thread threadB = new Thread(){ public void run(){ sample.nonSafeAction(); } }; threadA.start(); threadB.start(); threadA.join(); threadB.join(); } } 运行结果: java ThreadSafeSample Observed data race, former is 13097, latter is 13099
添加synchronized,锁定最小的代码段,对类的对象级别进行互斥访问,实现原子操作
1.代码public class ThreadSafeSample { public int sharedState; public void nonSafeAction() { while (sharedState < 100000) { sychronized(this){ int former = sharedState++; int latter = sharedState; } if (former != latter - 1) { System.out.printf("Observed data race, former is " + former + ", " + "latter is " + latter); } } } public static void main(String[] args) throws InterruptedException { ThreadSafeSample sample = new ThreadSafeSample(); Thread threadA = new Thread(){ public void run(){ sample.nonSafeAction(); } }; Thread threadB = new Thread(){ public void run(){ sample.nonSafeAction(); } }; threadA.start(); threadB.start(); threadA.join(); threadB.join(); } } 2.运行结果:未输出不相等的情况 java ThreadSafeSample 3.jmap反编译代码结果,可以看到其利用monitorenter/monitorexit对,实现同步语义 11: astore_1 12: monitorenter 13: aload_0 14: dup 15: getfield #2 // Field sharedState:I 18: dup_x1 … 56: monitorexit 4.如果synchronized修饰静态方法,可以使用synchronized(className.class){}将代码块包裹起来,或者在静态方法添加 synchronized
ReentranLock再入锁实现公平性
public class ThreadSafeSample { private ReentranLock fairLock = new ReentranLock(true); public int sharedState; public void nonSafeAction() { while (sharedState < 100000) { try{ lock.lock(1000,TimeUnit.Seconds); int former = sharedState++; int latter = sharedState; }catch(Exception e){ e.printStackTrace(); }finally{ lock.unlock(); //锁在finally中要释放 } if (former != latter - 1) { System.out.printf("Observed data race, former is " + former + ", " + "latter is " + latter); } } } public static void main(String[] args) throws InterruptedException { ThreadSafeSample sample = new ThreadSafeSample(); Thread threadA = new Thread(){ public void run(){ sample.nonSafeAction(); } }; Thread threadB = new Thread(){ public void run(){ sample.nonSafeAction(); } }; threadA.start(); threadB.start(); threadA.join(); threadB.join(); } }
锁的公平性
1.所谓公平性,指竞态环境中,当公平性位置,会赋予等待时间最久的线程。2.目的是减少线程"饥饿"(个别线程长期等待锁,但无法获取)情况发生。3.如果使用synchronized时无法保证线程公平性获取锁,用 于不公平,也是主流操作系统调度选择。4.除非是有公平性要求的场景,通用场景公平不那么重要,建议在程序需要公平性时,再指定它。
1.获取锁的条件,队列不空时,不满时获取锁,来进行互斥访问,两个条件变量是从同一把再入锁创建。 /** Condition for waiting takes */ private final Condition notEmpty; /** Condition for waiting puts */ private final Condition notFull; public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = new Object[capacity]; lock = new ReentrantLock(fair); notEmpty = lock.newCondition(); notFull = lock.newCondition(); } 2.通过条件变量实现一把再入锁绑定两个条件,使用特定操作(take方法)时,判断和等待条件满足(获取锁后,判断为空时等待,不为空出队,最后释放锁) public E take() throws InterruptedException { final ReentrantLock lock = this.lock; //1.获取锁 lock.lockInterruptibly(); try { while (count == 0) //signal和await成对调用非常重要,不然只有await,线程会一直中断 notEmpty.await(); //2.条件(notEmpty)判断为空进入等待 return dequeue();//否则进行出队 } finally { lock.unlock();//3.出队后释放锁 } }3.通过条件变量NotEmpty判断触发enqueue操作,通过signal/await组合,完成条件判断通知等待线程,顺畅完成主体流转。 private void enqueue(E e) { // assert lock.isHeldByCurrentThread(); // assert lock.getHoldCount() == 1; // assert items[putIndex] == null; final Object[] items = this.items; items[putIndex] = e; if (++putIndex == items.length) putIndex = 0; count++; //signal和await成对调用非常重要,不然只有await,线程会一直中断 notEmpty.signal(); // 5.通知等待的线程,非空条件已经满足 }
转载地址:http://zhjzb.baihongyu.com/