一、原子变量和CAS
使用synchronized可以保证原子更新操作,对于++这种操作来说,使用synchronized成本太高了,需要先获取锁,最后需要释放锁,获取不到锁的情况下需要等待,还会有线程的上下文切换,这些都需要成本。对于这种情况,可以使用原子变量代替,基本原子变量类型有:AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference,之所以称为原子变量,是因为包含一些以原子方式实现组合操作的方法:
//以原子方式获取旧值并设置新值 public final int getAndSet(int newValue) //以原子方式获取旧值并给当前值加1 public final int getAndIncrement() //以原子方式给当前值加1并获取新值 public final int incrementAndGet()
这些方法的实现都依赖一个方法:public final boolean compareAndSet(int expect, int update),比较并设置,简称CAS,如果当前值等于expect,则更新为update,否则不更新,更新成功返回true,失败返回false。
基本原理和思维:
public final int incrementAndGet() { for(;;) { int current = get(); int next = current + 1; if(compareAndSet(current, next)) return next; } }
主体是个死循环,先获取当前值current,计算期望的值next,然后调用CAS方法进行更新,如果更新没成功,说明value被别的线程更改了,则再去取最新值并尝试更新直到成功为止。
与synchronized相比,synchronized是悲观的,每次都获取锁,得到锁后才更新(代表一种阻塞式算法),原子变量的更新是乐观的,它假定冲突比较少,如果确实冲突了就继续尝试(更新逻辑是非阻塞式的)。
原子变量相对比较简单,对于复杂一些的数据结构和算法,非阻塞方式往往难于实现和理解,Java并发包提供了一些非阻塞容器,比如:
ConcurrentLinkedQueue、ConcurrentLinkedDeque 非阻塞并发队列
ConcurrentSkipListMap、ConcurrentSkipListSet 非阻塞并发Map和Set
二、显式锁
synchronized如果使用不当,容易出现死锁,使用显式锁可以解决死锁的问题,相比synchronized 显式锁支持以非阻塞方式获取锁,可以相应中断,可以限时,这使得它灵活的多。
可重入锁 ReentrantLock 它的lock/unlock实现了与synchronized一样的语义,包括:可重入、解决静态条件问题、保证内存可见性。构造方法如下:
public ReentrantLock() public ReentrantLock(boolean fair)
参数fair表示是否保证公平,默认不保证公平,所谓公平是指:等待时间最长的线程优先获得锁,保证公平会影响性能,一般也不需要,所以默认不保证。synchronized也是不保证公平的。
基本用法(实现计数器):
public class CounterThread extends Thread{ Counter counter; public CounterThread(Counter counter){ this.counter = counter; } @Override public void run() { for (int i = 0; i < 1000; i++) { counter.incre(); } } public static void main(String[] args) throws InterruptedException { int num = 1000; Counter counter = new Counter(); Thread[] threads = new Thread[num]; for (int i = 0; i < num; i++) { threads[i] = new CounterThread(counter); threads[i].start(); } for (int i = 0; i < num; i++) { threads[i].join(); } System.out.println(counter.getCount()); } } class Counter{ private final Lock lock = new ReentrantLock(); private volatile int count; public void incre(){ lock.lock(); try { count++; }finally { lock.unlock(); } } public int getCount(){ return count; } }
使用tryLock()可以避免死锁。
ReentrantLock的实现原理:依赖CAS方法,还依赖LockSupport中的一些方法,LockSupport的基本方法有:
public static void park(Object blocker) public static void unpark(Thread thread) public static void parkNanos(Object blocker, long nanos) public static void parkUntil(Object blocker, long deadline)
park使得当前线程放弃CPU,进入等待状态(WAITING),什么时候再调度呢?有其他线程调用了unpark,unpark使参数指定的线程恢复可运行状态,示例:
public class ParkDemo { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { LockSupport.park(); System.out.println("exit"); }); t.start(); Thread.sleep(1000); LockSupport.unpark(t); } }
park有两个变体,parkNanos:可以指定等待的最长时间,parkUntil:可以指定最长等到什么时候。
AQS:利用CAS和LockSupport就提供的方法,就可以实现ReentrantLock了。但Java中还有很多其它并发工具,如ReentrantReadWriteLock、Semaphore、CountDownLatch,它们的实现有很多类似的地方,为了复用代码,Java提供了一个抽象类AbstractQueuedSynchronizer 简称 AQS,它简化了并发工具的实现。
三、显式条件
锁用于解决竟态条件问题,条件是线程间的协作机制。显式锁与synchronized相对应,而显式条件与wait/notify对应。显式条件简单示例(线程启动后,在执行一项操作之前,等待主线程给它指令,收到指令才执行):
public class WaitThreadDemo extends Thread{ private volatile boolean fire = false; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); @Override public void run() { try { lock.lock(); try { while (!fire){ condition.await(); } }finally { lock.unlock(); } System.out.println("fired"); }catch (InterruptedException e){ Thread.interrupted(); } } public void fire(){ lock.lock(); try { this.fire = true; condition.signal(); }finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { WaitThreadDemo waitThread = new WaitThreadDemo(); waitThread.start(); Thread.sleep(1000); System.out.println("fire"); waitThread.fire(); } }
参考:老马说编程