JUC-java.util.concurrent学习(二)---Callable,CountDownLatch,CyclicBarrier,Semaphore,ReadWriteLock

本文围绕JUC-java.util.concurrent展开学习。介绍了Callable接口,需将其转换为Runnable使用,且获取返回值可能阻塞线程。还讲解了三大常用辅助类CountDownLatch、CyclicBarrier、Semaphore的功能、使用场景,以及ReadWriteLock的独占锁和共享锁特性。

JUC-java.util.concurrent学习(二)

Callable

在这里插入图片描述

Runnable是不能返回结果的,不能抛出异常的

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

发现实现Callable接口需要传递一个泛型,call方法的返回值类型就是该泛型

我们发现new一个线程时是不能把callable传递进去的,全都是runnable的
在这里插入图片描述

也就是说不能直接调用,我需要把callable转成runnable
在这里插入图片描述

我们发现runnable的实现类FutureTask是可以传递一个callable的类型进去的,也就是可以从这里找到两者之间的关系

public class MyCallable {
	public static void main(String[] args) throws ExecutionException, InterruptedException {
		FutureTask futureTask = new FutureTask(new Data1());
		new Thread(futureTask).start();
		String o = (String) futureTask.get();
		System.out.println(o);
	}
}
class Data1 implements Callable<String> {
	@Override
	public String call() throws Exception {
		System.out.println("hehehhe");
		return "aiyowei";
	}
}

这里当线程执行完过后,返回值就在futureTask里面,直接通过futureTask.get()拿去

futureTask.get()是会阻塞的,他必须等待线程new Thread(futureTask).start();执行完毕才能拿到返回值,如果任务时间太长,可能会导致线程阻塞

public static void main(String[] args) throws ExecutionException, InterruptedException {
   FutureTask futureTask = new FutureTask(new Data1());
   new Thread(futureTask).start();
   new Thread(futureTask).start();
   String o = (String) futureTask.get();
   System.out.println(o);
}

使用两个线程来跑futuretask,结结果只会打印一个hehehhe,因为结果会被缓存的

三大常用辅助类

CountDownLatch

做计数用的,减法

public class down {
   public static void main(String[] args) throws InterruptedException {
      CountDownLatch countDownLatch=new CountDownLatch(10);
	  //countDownLatch.countDown();//减一

      for (int i = 10; i > 0; i--) {
         int finalI = i;
         new Thread(()->{
            System.out.println("这是第"+ finalI +"个,还剩下"+(finalI +-1)+"个");
            countDownLatch.countDown();//减一
         }).start();
      }
      //countDownLatch.await();
      System.out.println("减完了吗?");
   }
}

如果我们必须等到所有的任务都执行完了在往下执行,那么就可以使用这个辅助类

现在我们不使用countDownLatch.await();会发现可能任务还没有执行完就打印了“减完了吗?”

这是第10个,还剩下9个
这是第6个,还剩下5个
这是第7个,还剩下6个
这是第8个,还剩下7个
这是第9个,还剩下8个
这是第4个,还剩下3个
减完了吗?
这是第3个,还剩下2个
这是第5个,还剩下4个
这是第2个,还剩下1个
这是第1个,还剩下0个

这是我们加上countDownLatch.await();发现不管执行多少次,“减完了吗?”都是在任务执行完了在打印的

这是第9个,还剩下8个
这是第6个,还剩下5个
这是第4个,还剩下3个
这是第7个,还剩下6个
这是第10个,还剩下9个
这是第8个,还剩下7个
这是第3个,还剩下2个
这是第5个,还剩下4个
这是第2个,还剩下1个
这是第1个,还剩下0个
减完了吗?

场景:

1、运动员跑步时,裁判可以给运动员5秒钟时间准备,也就是其他线程await(),时间一到,主线程countDown()(countDownLatch个数设置为1),实现统一时间执行

2、到达终点时,裁判以所有运动员都到达终点为结束比赛的标志,也就是裁判await(),其他线程执行完任务countDown()(countDownLatch个数设置为其他线程数),实现只要最后一个运动员到达终点就结束比赛,

其实上面两种场景是相反的,一个是某一个线程等待其他所有线程执行完任务在执行,一个是其他所有线程执行完任务在执行某一个线程

CyclicBarrier

做计数用的,加法

public class Add {
	public static void main(String[] args) {
		CyclicBarrier cyclicBarrier=new CyclicBarrier(5,()-> System.out.println("计数达到5了"));
		for (int i = 1; i <= 10; i++) {
			int finalI = i;
			new Thread(()->{
				try {
					TimeUnit.MILLISECONDS.sleep((long)(Math.random()*10000));
					System.out.println("这是第"+ finalI +"个,还剩下"+(10-finalI)+"个");
					cyclicBarrier.await();//等待,必须要cyclicBarrier计数器达到5,如果cyclicBarrier总数多余线程数的话可能会卡死
				} catch (InterruptedException e) {
					e.printStackTrace();
				} catch (BrokenBarrierException e) {
					e.printStackTrace();
				}
			}).start();
		}
	}
}
这是第6个,还剩下4个
这是第1个,还剩下9个
这是第8个,还剩下2个
这是第4个,还剩下6个
这是第5个,还剩下5个
计数达到5了
这是第3个,还剩下7个
这是第7个,还剩下3个
这是第10个,还剩下0个
这是第2个,还剩下8个
这是第9个,还剩下1个
计数达到5了

在这里插入图片描述

CyclicBarrier有两个构造方法,如果只加入一个int的话,就只初始化计数总数,如果是int和runnable的话就,意思就是当计数达到总数后就会执行这个任务

场景:

公司聚餐,大家陆陆续续出来到马路上打出租车,假设车一直有,4个人一个车,人员来了4个人就可以走一车,来4个人走一车

CountDownLatch 在倒数到 0 并且触发门闩打开后,就不能再次使用了,除非新建一个新的实例;而 CyclicBarrier 可以重复使用,在刚才的代码中也可以看出,每 5个一组,并不需要重新新建实例。CyclicBarrier 还可以随时调用 reset 方法进行重置,如果重置时有线程已经调用了 await 方法并开始等待,那么这些线程则会抛出 BrokenBarrierException 异常

Semaphore

信号量

这里以抢车位为例子,一共5个空车位(用信号量表示),有10个车

public class MySemaphore {
   public static void main(String[] args) {
      Semaphore semaphore=new Semaphore(5);
      for (int i = 1; i <= 10; i++) {
         int finalI = i;
         new Thread(()->{
            try {
               semaphore.acquire();//抢车位
               System.out.println(Thread.currentThread().getName()+"抢到了一个");
               TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }finally {
               System.out.println(Thread.currentThread().getName()+"离开");
               semaphore.release();//开走,释放车位
            }
         },"线程"+i).start();
      }
   }
}
线程2抢到了一个
线程4抢到了一个
线程1抢到了一个
线程3抢到了一个
线程5抢到了一个
线程3离开
线程2离开
线程7抢到了一个
线程4离开
线程1离开
线程5离开
线程9抢到了一个
线程8抢到了一个
线程6抢到了一个
线程10抢到了一个
线程7离开
线程9离开
线程6离开
线程8离开
线程10离开

semaphore.acquire();//抢占的意思,如果没有空位的话,线程会等待,直到被释放,信号量就会+1

semaphore.release();//释放车位,信号量就会+1

ReadWriteLock

我们写一个缓存,不加锁

public class MyWriteAndReadLock {

   public static void main(String[] args) {
      Date1 date1=new Date1();

      for (int i = 1; i < 10; i++) {
         int finalI = i;
         new Thread(()->{
            date1.put(String.valueOf(finalI),String.valueOf(finalI));
         }).start();
      }
      for (int i = 1; i < 10; i++) {
         int finalI = i;
         new Thread(()->{
            date1.get(String.valueOf(finalI));
         }).start();
      }
   }
}
class Date1{
	private volatile Map<String,String> map=new HashMap<>();

	public void put(String key ,String value){
		System.out.println("put"+key+"开始");
		map.put(key,value);
		System.out.println("put"+key+"完毕");
	}
	public String get(String key){
		System.out.println("get"+key+"开始");
		String s = map.get(key);
		System.out.println("get"+key+"完毕");
		return s;
	}
}
put1开始
put5开始
put4开始
put3开始
put3完毕
put7开始
put7完毕
put2开始
put9开始
put4完毕
put5完毕
put8开始
put8完毕
put6开始
put1完毕
put6完毕
put9完毕
put2完毕
get1开始
get2开始
get2完毕
get1完毕
get3开始
get4开始
get5开始
get5完毕
get3完毕
get7开始
get6开始
get6完毕
get4完毕
get9开始
get7完毕
get8开始
get8完毕
get9完毕

这肯定是有问题的,put时候应该是原子性操作,不能被其他线程操作

public class MyWriteAndReadLock {

   public static void main(String[] args) {
      Date2 date1=new Date2();

      for (int i = 1; i < 10; i++) {
         int finalI = i;
         new Thread(()->{
            date1.put(String.valueOf(finalI),String.valueOf(finalI));
         }).start();
      }
      for (int i = 1; i < 10; i++) {
         int finalI = i;
         new Thread(()->{
            date1.get(String.valueOf(finalI));
         }).start();
      }
   }
}
class Date2{
   private volatile Map<String,String> map=new HashMap<>();
   private ReadWriteLock lock=new ReentrantReadWriteLock();//读写锁,更加细粒度的操作,写入的时候同时只有一个线程,读取的时候可以有多个线程

   public void put(String key ,String value){
      lock.writeLock().lock();
      try {
         System.out.println("put"+key+"开始");
         map.put(key,value);
         System.out.println("put"+key+"完毕");
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         lock.writeLock().unlock();
      }
   }
   public String get(String key){
      lock.readLock().lock();
      String s=null;
      try {
         System.out.println("get"+key+"开始");
         s = map.get(key);
         System.out.println("get"+key+"完毕");
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         lock.readLock().unlock();
      }
      return s;
   }
}
put1开始
put1完毕
put3开始
put3完毕
put2开始
put2完毕
put5开始
put5完毕
put4开始
put4完毕
put6开始
put6完毕
put7开始
put7完毕
put8开始
put8完毕
put9开始
put9完毕
get2开始
get2完毕
get1开始
get3开始
get3完毕
get4开始
get4完毕
get1完毕
get5开始
get5完毕
get7开始
get7完毕
get9开始
get6开始
get8开始
get6完毕
get9完毕
get8完毕

效果达到

独占锁:写锁,只能被一个线程操作

共享锁:读锁,可以被多个线程操作

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值