同步和协作工具类

简介:

之前章节我们学习的线程之间的协作是靠wait和notify,或者使用显示条件Condition 的await和signal。但是在一些特殊的场景,使用起来比较复杂。而
java提供了一些用于在这些特殊场景使用的工具类,他们都是基于AQS实现的。AQS是基于CAS和LockSupport提供的基础方法来实现的。

ReentrantReadWriteLock – 读写锁:

synchronized和ReentrantLock 无论是读还是写的操作,都要获取到同样的锁才能操作,否则进入等待。在一些场景是没必要这样的,多个线程的读操作完全是可以并行的,在读多写少的时候,让读操作并行是可以提高效率的。ReentrantReadWriteLock可以让读并行,同时也能保证一致性,它是可重入读写锁,继承读写锁readWriteLock。ReentrantReadWriteLock内部有两个锁。一个读锁,一个写锁。ReentrantReadWriteLock 只能保证读并行,不可以保证读写和写写并行。

//获取读锁
public ReentrantReadWriteLock.ReadLock readLock() {
        return this.readerLock;
    }

//获取写锁    
public ReentrantReadWriteLock.WriteLock writeLock() {
        return this.writerLock;
    }
  
//ReentrantReadWriteLock使用例子:  
public class ReentrantReadWriteLockMIne {
    static class MyCacher {
        private Map<String,Object> map = new HashMap<>();
       private  ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
       private Lock readLock = readWriteLock.readLock();
       private Lock writeLock = readWriteLock.writeLock();

       public Object get(String key){
           readLock.lock();
           try{
               return map.get(key);
           }finally {
               readLock.unlock();
           }
       }

       public void put(String key, Object value){
           writeLock.lock();
           try{
               map.put(key,value);
           }finally {
               writeLock.unlock();
           }
       }

       public void clear(){
           writeLock.lock();
           try{
               map.clear();
           }finally {
               writeLock.unlock();
           }
       }
    }

    public static void main(String[] args) throws InterruptedException {
        //ReentrantReadWriteLock读写锁只有一个线程可以进行写操作,在获取写锁时,
        //只有没有任何线程持有任何锁才可以获取到,在持有写锁时,其他线程都获取不到任何锁。
        //没有线程持有写锁时,多个线程可以获取和持有读锁。
        MyCacher myCacher = new MyCacher();
        Thread readCached = new Thread(() ->{
            for (int i = 0; i <= 50 ;i++){
                myCacher.get(("key"+i));
                Object value = myCacher.get(("key"+i));
                System.out.println("同步读缓存"+i+":"+value);
            }
        });

        Thread readCached1 = new Thread(() ->{
            for (int i = 50; i <= 100 ;i++){
                Object value = myCacher.get(("key"+i));
                System.out.println("同步读缓存"+i+":"+value);
            }
        });

        Thread writeCached = new Thread(() -> {
            for (int i =0; i<200;i++) {
                myCacher.put("key"+i,i);
                System.out.println("写入缓存"+i);
            }
        });
        Thread readCached2 = new Thread(() ->{
            for (int i = 100; i <= 150 ;i++){
                Object value = myCacher.get(("key"+i));
                System.out.println("同步读缓存"+i+":"+value);
            }
        });
        Thread readCached3 = new Thread(() ->{
            for (int i = 150; i < 200 ;i++){
                Object value = myCacher.get(("key"+i));
                System.out.println("同步读缓存"+i+":"+value);
            }
        });
        writeCached.start();
        readCached.start();
        readCached1.start();
        readCached2.start();
        readCached3.start();
    }
}

Semaphore – 信号量:

信号量是用来限制并发对同一资源访问数量的控制,比如医院每天门诊部门的 并发看诊人数。

Semaphore 常用方法:

方法名返回值说明
acquire() throw InterruptedExceptionvoid获取许可,响应中断
acquireUninterruptedExceptionvoid获取许可,不响应中断
tryAcquire()boolean尝试获取许可
tryAcquire(long timeout,TimeUnit unit)boolean限定时间获取许可
release()void释放许可
acquire(int permits) throw InterruptedExceptionvoid批量获取许可 ,响应中断
public class SemaphoreMine {

    static class ScheduleService {
        private static final int MAX_PERMITS = 1;
        private Semaphore  semaphore = new Semaphore(MAX_PERMITS,false);
        public boolean scheduleCount(String name){
            if(!semaphore.tryAcquire()){
                System.out.println("看诊号已预约完!");
            }
            return true;
        }
        public void scheduleOut(){
            //预约退改
            semaphore.release();
        }
    }

    public static void main(String[] args) {
        ScheduleService scheduleService = new ScheduleService();
        //Semaphore每调用一次tryAcquire()和acquire()都会消耗掉一个信号
        scheduleService.scheduleCount("张三");
        scheduleService.scheduleCount("李四");
    }
}

CountDownLatch – 门栓:

倒计时门栓CountDownLatch,它相当于是一个门栓,一开始是关闭的,所有希望通过该门的线程都需要等待,然后开始倒计时,倒计时为0后,门栓打开,等待的线程都可以通过,它是一次性的,打开就不能再关上了。创建CountDownLatch需要初始化计数。

CountDownLatch 常用方法:

方法名返回值说明
await()void检查计数是否为0,大于零则等待,响应中断
countDown()void检查计数,当计数大于0时,减1,如果为0,则唤醒所有等待的线程
public class MyLatch {
    //运动员跑步同时开始,使用门栓实现。
    static class Runner extends Thread {
        CountDownLatch countDownLatch;

        public Runner(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                countDownLatch.await();
                System.out.println("运动员同时起步!!");
            }catch (InterruptedException i){
                Thread.interrupted();
            }

        }
    }

    static class MasterAndSlaveCooperation extends Thread {
        CountDownLatch countDownLatch;

        public MasterAndSlaveCooperation(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }


        @Override
        public void run() {
            //工作线程执行业务
            System.out.println("执行业务!!!");
            countDownLatch.countDown();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //运动员跑步同时开始
        CountDownLatch countDownLatch = new CountDownLatch(1);
        Thread[] threads = new Thread[10];
        for(int i =0;i<threads.length;i++){
            threads[i] = new Runner(countDownLatch);
            threads[i].start();
        }
        Thread.sleep(1000);
        countDownLatch.countDown();

        //主从协作
        CountDownLatch msLatch = new CountDownLatch(20);
        Thread[] slaves = new Thread[20];
        for(int i =0; i<slaves.length;i++){
            slaves[i] = new MasterAndSlaveCooperation(msLatch);
            slaves[i].start();
        }
        msLatch.await();
        System.out.println("master and slave is work!!");
    }
}

CyclicBarrier --同步栅栏:

类似集合点问题可以使用CyclicBarrier,它相当于一个栅栏,所有线程到达该栅栏都需要等待其他的线程,等所有线程都到了再一起通过,它是循环的,可以用作重复的同步。CyclicBarrier特别适用于并行迭代计算,每个线程负责计算一部分,然后再栅栏处等待其他线程完成,所有线程到齐后交换数据和计算结果,再进行下一次的迭代。CyclicBarrier的构造函数如下:

public CyclicBarrier(int parties):partie参数代表参与的线程数
public CyclicBarrier(int parties, Runnable barrierAction):Runnable 参数表示栅栏动作,在所有线程到达栅栏后,由最后一个到达的线程执行这个动作。

CyclicBarrier 常用方法:

方法名返回值说明
await() throw InterruptedException,BrokenBarrierExceptionvoid线程到达栅栏后调用await表示到达,等待其他线程如果自己是最后一个到达,执行可选的命令,执行后,唤醒所有等待线程,然后重置内部的同步计数,以循环使用。CyclicBarrier中参与的线程都是相互影响的,其中一个被中断或者超时,所有调用await的线程都会被取消,同时抛出BrokenBarrierException意思是栅栏破了。
await(long timeout,TimeUnit unit)int执行线程集合时间,如果超时有线程未执行完,直接抛出超时异常。
public class CyclicBarrierMine {
    //旅游集合点
    static class MarshalPoint extends  Thread {
        CyclicBarrier cyclicBarrier;

        public MarshalPoint(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            try {
                System.out.println("执行计算");
                cyclicBarrier.await();
                //重复执行
                System.out.println("并发迭代");
                cyclicBarrier.await();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(10,() ->{
            System.out.println("并发数据交互,计算结果");
        });

        Thread[] threads = new Thread[10];
        for (int i =0;i<threads.length;i++){
            threads[i] = new MarshalPoint(cyclicBarrier);
            threads[i].start();
        }
    }
}

ThreadLocal --线程本地变量:

每个线程都有同一个变量的拷贝。Threadlocal 常用于日期处理,传递上下文信息。

ThreadLocal 常用方法:

方法名返回值说明
get()T获取当前线程变量值,如果没设置过,返回null,否则调用initalValue()方法获取初始化值。
set(T t)void设置本地变量值
initialValue()T用于提供初始值,这是一个受保护的方法,可以通过匿名类提供。
remove()void删除当前线程设置的变量值,如果删除了,再次调用get,会调用initalValue()方法获取初始化值。
public class ThreadLocalMine {

    //上下文信息。这是ThreadLocal的典型用途。它被用于各种框架中比如spring。
    //使用ThreadLocal保存上下文信息
    static class RequestContext {
        private static ThreadLocal<String> userIdThreadLocal = new ThreadLocal<>();
        private static ThreadLocal<Request> requestThreadLocal = new ThreadLocal<>();
        public static String getUserId(){
            return userIdThreadLocal.get();
        }
        public static void setUserId(String userId){
            userIdThreadLocal.set(userId);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //每个线程都有同一个变量的拷贝,原理是ThreadLocal每次调用set都是获取当前线程的属性ThreadLocalMap,
        // 将线程作为key,传入的值位value,存到ThreadLocalMap中。调用get时,从当前线程中的ThreadLocalMap中取出值。
        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        Thread  thread = new Thread(() ->{
            System.out.println("childrenThread initValue is:"+ threadLocal.get());
           threadLocal.set("3");
            System.out.println("childrenThread set is :"+ threadLocal.get());
        });
        thread.start();
        thread.join();
        threadLocal.remove();
        threadLocal.set("2");
        System.out.println("mainThread value is:"+threadLocal.get());

        //日期处理,SimpleDateFormat是线程不安全的,当并发时使用时,会出现获取到的时间都是同一时间。
        //要解决这问题是加锁,或者每次都创建一个新的对象。更好的方式是用ThreadLocal,每个线程只需要创建一次,又是线程安全的
        ThreadLocal<SimpleDateFormat> formatThreadLocal = new ThreadLocal<>(){
            @Override
            protected SimpleDateFormat initialValue() {
                return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            }
        };

    }
}

总结:

本章学习的读写锁,信号量,门栓,栅栏,线程本地变量可以很方便的帮助我们解决特定场景的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值