并发02-并发包

一、原子变量和CAS

  使用synchronized可以保证原子更新操作,对于++这种操作来说,使用synchronized成本太高了,需要先获取锁,最后需要释放锁,获取不到锁的情况下需要等待,还会有线程的上下文切换,这些都需要成本。对于这种情况,可以使用原子变量代替,基本原子变量类型有:AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference,之所以称为原子变量,是因为包含一些以原子方式实现组合操作的方法:

//以原子方式获取旧值并设置新值
public final int getAndSet(int newValue)
//以原子方式获取旧值并给当前值加1
public final int getAndIncrement()
//以原子方式给当前值加1并获取新值
public final int incrementAndGet()
View Code

  这些方法的实现都依赖一个方法: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;
    }
}
View Code

  主体是个死循环,先获取当前值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)
View Code

  参数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;
    }
}
View Code

  使用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();
    }
}
View Code

  

参考:老马说编程

  

猜你喜欢

转载自www.cnblogs.com/wange/p/10992032.html
今日推荐