JUC并发编程之:简单概述(六)

JUC并发编程之:简单概述(六)

一、AQS

1.1、AQS原理

·AQS全程AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具类 的 框架

【类似synchronized,阻塞式锁】

##特点:
·用state属性来表示资源的状态(独占模式 和  共享模式),子类需要定义如何维护这个状态,
控制如何获取锁和释放锁
   >getState:获取state状态
   >setState:设置state状态
   >compareAndSetState:乐观锁机制设置state状态
   >独占模式是只有一个线程能够访问资源
   >共享模式可以允许多个线程访问资源

·提供了基于FIFO的等待队列,类似于Monitor的EntryList
·条件变量来实现等待、唤醒机制,支持多个条件变量,类似于Monitor的WaitSet

##子类主要实现这样一些方法:
tryAcquire  尝试获取锁(独占锁)
tryRelease  尝试释放锁(独占锁)
tryAcquireShared 获取共享锁
tryReleaseShared 释放共享锁
isHeldExclusively 是否持有独占锁

【AQS中阻塞和恢复线程使用的是park和unpark】

1.2、自定义锁

/**
 * 自定义一个不可重入锁
 */
@Slf4j
public class CustomAQS {
    public static void main(String[] args) {
        MyLock lock = new MyLock();

        new Thread(()->{
            lock.lock();
            try {
                log.debug("t1 locking...");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                log.debug("t1 unlock...");
                lock.unlock();
            }
        },"t1").start();

        new Thread(()->{
            lock.lock();
            try {
                log.debug("t2 locking...");
            }finally {
                log.debug("t2 unlock...");
                lock.unlock();
            }
        },"t2").start();
    }
}

//自定义锁(不可重入)
class MyLock implements Lock {

    //自定义同步器类
    class MySync extends AbstractQueuedSynchronizer{

        @Override//尝试获取锁(独占锁)
        protected boolean tryAcquire(int arg) {
            //将资源状态state设置为1--占用
            if(compareAndSetState(0,1)){
                //设置owner线程为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override//尝试释放锁 (独占锁)
        protected boolean tryRelease(int arg) {
            //将owner线程设置为空
            //非volatile可见的
            setExclusiveOwnerThread(null);
            //设置资源状态state为0--未被占用
            //state为volatile可见的,有写屏障,因此将owner设置放在其上
            setState(0);
            return true;
        }

        @Override//是否持有独占锁
        protected boolean isHeldExclusively() {
            return getState()==1;
        }

        //条件变量
        public Condition newCondition(){
            return new ConditionObject();
        }
    }

    private MySync mySync = new MySync();

    @Override //加锁(不成功会进入等待队列)
    public void lock() {
        mySync.acquire(1);
    }

    @Override //加锁(可打断的)
    public void lockInterruptibly() throws InterruptedException {
        mySync.acquireInterruptibly(1);
    }

    @Override //尝试加锁(尝试一次)
    public boolean tryLock() {
        return mySync.tryAcquire(1);
    }

    @Override //尝试加锁(带超时的)
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return mySync.tryAcquireNanos(1,unit.toNanos(time));
    }

    @Override //解锁
    public void unlock() {
        //tryRelease不会唤醒其他等待的线程,release会
        mySync.release(0);
    }

    @Override //条件变量
    public Condition newCondition() {
        return mySync.newCondition();
    }
}
##结果:
10:49:19.965 [t1] DEBUG  - t1 locking...
10:49:21.976 [t1] DEBUG  - t1 unlock...
10:49:21.976 [t2] DEBUG  - t2 locking...
10:49:21.976 [t2] DEBUG  - t2 unlock...

二、读写锁

2.1、ReentrantReadWriteLock

·当读操作远远高于写操作时,这时候使用读写锁让 读 < -- >读 可以并发,提高性能
    
##例子:一个数据容器,共享数据data,我们使用读锁保护数据的read方法,
用写锁保护数据的write方法    
@Slf4j
public class ReadWriteLockTest {
    public static void main(String[] args) {
        DataContainer dContainer = new DataContainer("a");
        new Thread(()->{
            dContainer.read();
        },"t1").start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            dContainer.write("b");
        },"t3").start();

    }
}

//数据容器
@Slf4j
@Data
class DataContainer{

    //共享数据
    private Object obj;

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    //读锁
    private ReentrantReadWriteLock.ReadLock r = lock.readLock();
    //写锁
    private ReentrantReadWriteLock.WriteLock w = lock.writeLock();

    public DataContainer(Object obj) {
        this.obj = obj;
    }

    public Object read(){
        log.debug("尝试获取读锁");
        r.lock();
        log.debug("获取读锁成功");
        try {
            Thread.sleep(1000);
            log.debug("读取数据"+obj);
            return obj;
        } catch (InterruptedException e) {
            e.printStackTrace();
            return null;
        } finally {
            log.debug("释放读锁");
            r.unlock();
            log.debug("释放读锁成功");
        }
    }

    public void write(Object res){
        log.debug("获取写锁");
        w.lock();
        log.debug("获取写锁成功");
        try {
            log.debug("修改数据"+res);
            setObj(res);
        } finally {
            log.debug("释放写锁");
            w.unlock();
            log.debug("释放写锁成功");
        }
    }
}
注意事项:
·读锁不支持条件变量Condition

·重入时升级不支持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待
【读的时候不可以写】
如下:
r.lock();
try{
  //...
  try{
     //...
     w.lock();
  }finally{
     w.unlock();
  }
}finally{
  r.unlock();
}

·重入时支持降级:即支持持有写锁的情况下去获取读锁
【写的时候可以读】

【读读可并发】
【读写互斥】

##其他
·读写锁用的是同一个Sycn同步器,因此等待队列、state也是同一个
·写锁占据了state的低16位,读锁使用了state的高16位

2.2、StampedLock

·StampedLock自JDK8加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都
必须配合【戳】的使用

##加戳读锁
long stamp = lock.readLock();
lock.unlockRead(stamp);

##加戳写锁
long stamp = lock.writeLock();
lock.unlockWrite(stamp);

##乐观读
StampedLock支持tryOptimisticRead()方法(乐观读),读取完毕后需要做一次
戳校验,如果校验通过表示这期间确实没有写操作,数据可以安全使用,如果校验没有通过,
需要重新获取读锁,保证数据安全

//这个方法没有加任何锁
long stamp = lock.tryOptimisticRead();
//验戳
if(!lock.validate(stamp)){
  //验证失败,锁升级,可以使用lock.readLock()
}else{
  //验证成功,证明这期间没有写操作
}


##注意:
·stampedLock不支持条件变量
·stampedLock不支持可重入
/**
 * 读写锁:乐观读
 */
@Slf4j
public class StampedLockTest {
    public static void main(String[] args) {
        DataContainerStamped containerStamped = new DataContainerStamped("a");
        new Thread(()->{
            containerStamped.read();
        },"t1").start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            containerStamped.write("b");
        },"t2").start();
    }
}

@Slf4j
@Data
class DataContainerStamped{
    //共享数据
    private Object Obj;
    //读写锁
    private StampedLock lock = new StampedLock();

    public DataContainerStamped(Object obj) {
        Obj = obj;
    }

    public Object read(){
        //乐观读
        log.debug("乐观读");
        long stamp = lock.tryOptimisticRead();
        log.debug("乐观读 stamp:{}",stamp);
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //乐观读验证--是否期间有写操作
        if(lock.validate(stamp)){
            log.debug("乐观读验证成功,期间没有写操作 stamp:{}, 读取数据obj:{}",stamp,getObj());
            return getObj();
        }

        log.debug("乐观读验证失败,锁升级");
        log.debug("尝试获取读锁");
        stamp = lock.readLock();
        log.debug("尝试获取读锁,stamp:{}",stamp);
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            log.debug("读取数据");
            log.debug("读取数据 obj:{}",getObj());
            return getObj();
        } finally {
            log.debug("尝试释放读锁 stamp:{}",stamp);
            lock.unlockRead(stamp);
            log.debug("释放读锁成功 stamp:{}",stamp);
        }

    }

    public void write(Object res){
        log.debug("尝试获取写锁");
        long stamp = lock.writeLock();
        log.debug("获取写锁成功 stamp:{}",stamp);
        try {
            log.debug("修改数据"+res);
            setObj(res);
        } finally {
            log.debug("尝试释放写锁 stamp:{}",stamp);
            lock.unlockWrite(stamp);
            log.debug("释放写锁成功 stamp:{}",stamp);
        }

    }

}
##结果:
17:16:51.894 [t1] DEBUG  - 乐观读
17:16:51.896 [t1] DEBUG  - 乐观读 stamp:256
17:16:52.393 [t2] DEBUG  - 尝试获取写锁
17:16:52.393 [t2] DEBUG  - 获取写锁成功 stamp:384
17:16:52.393 [t2] DEBUG  - 修改数据b
17:16:52.393 [t2] DEBUG  - 尝试释放写锁 stamp:384
17:16:52.393 [t2] DEBUG  - 释放写锁成功 stamp:384
17:16:53.897 [t1] DEBUG  - 乐观读验证失败,锁升级
17:16:53.897 [t1] DEBUG  - 尝试获取读锁
17:16:53.897 [t1] DEBUG  - 尝试获取读锁,stamp:513
17:16:53.897 [t1] DEBUG  - 读取数据
17:16:53.897 [t1] DEBUG  - 读取数据 obj:b
17:16:53.897 [t1] DEBUG  - 尝试释放读锁 stamp:513
17:16:53.897 [t1] DEBUG  - 释放读锁成功 stamp:513

##注意:
·stampedLock不支持条件变量
·stampedLock不支持可重入

三、其他工具类

3.1、Semaphore信号量

·Semaphore信号量,用来限制能同时访问共享资源的线程上限

【限制共享资源的并发访问数量】
【同之前的ReentrantLock之类的还是不一样的,之前的都是独占共享资源】

·使用semaphore可以做简单的限流,在访问高峰期时,让请求线程阻塞,高峰期过去再
释放许可,当然它只适合限制单机线程数量,并且仅是限制线程数,而不是限制资源数。


【适用于资源数和线程数一样的情况,如数据库连接池】
@Slf4j
public class SemaphoreTest {
    public static void main(String[] args) {
        //信号量--限制资源的访问数
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i <10 ; i++) {
            new Thread(()->{
                try {
                    //获取信号量(总信号量减1)
                    semaphore.acquire();
                    log.debug("running");
                    Thread.sleep(1000);
                    log.debug("end");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //释放信号量(总信号量加1)
                    semaphore.release();
                }
            },"t"+i).start();
        }
    }
}
##结果:
10:57:21.996 [t0]  - running
10:57:21.996 [t4]  - running
10:57:21.996 [t3]  - running
10:57:22.998 [t4]  - end
10:57:22.998 [t3]  - end
10:57:22.998 [t0]  - end
10:57:22.998 [t1]  - running
10:57:22.998 [t2]  - running
10:57:22.998 [t5]  - running
10:57:23.999 [t2]  - end
10:57:23.999 [t5]  - end
10:57:23.999 [t1]  - end
10:57:23.999 [t6]  - running
10:57:23.999 [t8]  - running
10:57:23.999 [t9]  - running
10:57:25.000 [t6]  - end
10:57:25.000 [t9]  - end
10:57:25.000 [t8]  - end
10:57:25.000 [t7]  - running
10:57:26.001 [t7]  - end

3.2、CountDownLatch倒计数锁

·CountDownLatch用来进行线程同步协作,等待所有线程完成倒计时,才恢复运行

·几种构造参数用来初始化等待计数值,await()用来等待计数清零,countDown()用来让
计数减一

##类似join()
@Slf4j
public class CountDownLatchTest {
    public static void main(String[] args) {
        //初始化倒计数值为3
        CountDownLatch countDownLatch = new CountDownLatch(3);
        log.debug("main begin...");

        ExecutorService executorService = Executors.newFixedThreadPool(3);

        executorService.submit(()->{
            log.debug("t1 running");
            //倒计数减1
            countDownLatch.countDown();
            log.debug("t1 end...");
        });

        executorService.submit(()->{
            log.debug("t2 running");
            //倒计数减1
            countDownLatch.countDown();
            log.debug("t2 end...");
        });

        executorService.submit(()->{
            log.debug("t3 running");
            //倒计数减1
            countDownLatch.countDown();
            log.debug("t3 end...");
        });

        log.debug("main waiting...");
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("main running");
    }
}
##结果:
11:21:06.141 [main]  - main begin...
11:21:06.177 [t1]  - t1 running
11:21:06.177 [t1]  - t1 end...
11:21:06.177 [main]  - main waiting...
11:21:06.178 [t3]  - t3 running
11:21:06.178 [t2]  - t2 running
11:21:06.178 [t3]  - t3 end...
11:21:06.178 [t2]  - t2 end...
11:21:06.178 [main]  - main running

3.3、Cyclicbarrier循环栅栏

·Cyclicbarrier循环栅栏,用来进行线程协作,等待线程满足某个计数。构造时设置
【计数个数】,每个线程执行到某个需要“同步”的时刻调用await()方法进行等待,
当等待的线程数满足【计数个数】时,继续执行

让一组线程达到屏障时(阻塞点)被阻塞,直到最后一个线程也达到了屏障时,屏障才会打
开,所有被屏障的线程才会继续干活。

【它会再计数编程0后,直接打印,下次再调用直接恢复原值】

【CountDownLatch的增强版】
@Slf4j
public class CyclicBarrierTest {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, ()->{
            log.debug("召唤神龙!");
        });
        ExecutorService pool = Executors.newFixedThreadPool(7);
        String[] dragonEge = new String[]{"赤","橙","黄","绿","青","蓝","紫"};
        for (int i = 0; i <7 ; i++) {
            int temp = i;
            Future<String> f = pool.submit(() -> {
                log.debug("开始收集 {}色 龙珠", dragonEge[temp]);
                Thread.sleep(1000);
                log.debug("收集 {}色 龙珠成功", dragonEge[temp]);
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
                return dragonEge[temp];
            });
        }
        pool.shutdown();
    }
}
##结果:
14:53:22.413 [pool-1-thread-3]- 开始收集 黄色 龙珠
14:53:22.413 [pool-1-thread-5]- 开始收集 青色 龙珠
14:53:22.413 [pool-1-thread-6]- 开始收集 蓝色 龙珠
14:53:22.413 [pool-1-thread-4]- 开始收集 绿色 龙珠
14:53:22.413 [pool-1-thread-7]- 开始收集 紫色 龙珠
14:53:22.413 [pool-1-thread-1]- 开始收集 赤色 龙珠
14:53:22.413 [pool-1-thread-2]- 开始收集 橙色 龙珠
14:53:23.416 [pool-1-thread-7]- 收集 紫色 龙珠成功
14:53:23.416 [pool-1-thread-3]- 收集 黄色 龙珠成功
14:53:23.416 [pool-1-thread-6]- 收集 蓝色 龙珠成功
14:53:23.416 [pool-1-thread-1]- 收集 赤色 龙珠成功
14:53:23.416 [pool-1-thread-4]- 收集 绿色 龙珠成功
14:53:23.416 [pool-1-thread-5]- 收集 青色 龙珠成功
14:53:23.416 [pool-1-thread-2]- 收集 橙色 龙珠成功
14:53:23.416 [pool-1-thread-2]- 召唤神龙!

3.4、线程安全集合类概述

##一、遗留的安全集合(synchronized修饰)
如:Hashtable Vector

##二、修饰的安全集合(使用Collections的方法修饰,本质上还是synchronized)
如:SynchronizedMap  SynchronizedList

Collections.synchronizedCollection
Collections.synchronizedList
Collections.synchronizedMap
Collections.synchronizedSet
Collections.synchronizedNavigableMap
Collections.synchronizedNavigableSet
Collections.synchronizedSortedMap
Collections.synchronizedSortedSet

##三、JUC安全类
Blocking类  CopyOnWrite类  Concurrent类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值