JUC包(面试常问)

2023-12-13 23:52:07

1. Callable接口

类似于Runnable接口,Runnable描述的任务,不带返回值;Callable描述的任务带返回值。

public class Test {
	//创建线程,计算1+2+...+1000
	public static void main(String[] args) throws ExecutionException, InterruptedException {
		//使用Callable定义一个任务
		Callable<Integer> callable = new Callable<Integer>() {
			@Override
			public Integer call() throws Exception {
				int sum = 0;
				for (int i = 1; i <= 1000; i++) {
					sum += i;
				}
				return sum;
			}
		};
		//类似于凭小票取餐,这是获取结果的凭证
		FutureTask<Integer> futureTask = new FutureTask<>(callable);
		//Thread构造方法不能直接传callable,需要借助futureTask
		Thread t = new Thread(futureTask);
		t.start();
		//获取线程计算结果
		//get方法会阻塞等待直到call方法计算完毕,get才会返回
		System.out.println(futureTask.get());
	}
}

2. ReentrantLock

和synchronized一样是可重入锁,但是两者又有些不同,可以说reentrantLock是对synchronized的补充。
核心方法

  1. lock()加锁
  2. unlock()解锁
  3. tryLock()尝试加锁
和synchronized相比的缺点

进入synchronized内自动加锁,出了synchronized自动解锁。而reentrantLock需要手动加锁,解锁。这就可能出现解锁失败

public static void main(String[] args) {
	ReentrantLock locker = new ReentrantLock();
	locker.lock();
	if(true) {
		return;
	}
	locker.unlock();
}

上面代码直接return没有执行unlock()方法,解决方法就是使用try finally

public static void main(String[] args) {
	ReentrantLock locker = new ReentrantLock();
	try {
		locker.lock();
		if(true) {
			return;
		}
	} finally {
		locker.unlock();
	}
}

和synchronized相比的优点
  1. tryLock尝试加锁,可以设置加锁等待时间。而synchronized采用的是“死等策略”,死等需要慎重。
  2. ReentrantLock可以实现成公平锁,默认是非公平锁
ReentrantLock locker = new ReentrantLock(true);
  1. synchronized是搭配wait/notify实现等待通知机制,随机唤醒一个等待的线程。ReentrantLock是搭配Condition类实现,可以指定唤醒哪个等待的线程。(实例化多个Condition对象,使用await/signal方法)
面试题:谈谈两者的区别

上面的优点+缺点
补充:synchronized是java关键字,底层是JVM实现的;而ReentrantLock是标准库的一个类,底层基于java实现的

3.原子类

原子类的底层是基于CAS实现的,使用原子类,最常见的场景是多线程计数。例如求服务器有多少并发量。

4.信号量Semaphore

信号量相当于一个计数器,表示可用资源的个数。
信号量的基本操作:

  1. P操作,申请一个资源
  2. V操作,释放一个资源

当计数为0的时候,继续P操作,就会阻塞等待到其他线程V操作。
信号量可用视为一个广义的锁,而锁相当于一个可用资源为1的信号量。

public static void main(String[] args) throws InterruptedException {
	//3个可用资源的信号量
	Semaphore semaphore = new Semaphore(3);
	//P操作,申请一个资源
	semaphore.acquire();
	System.out.println("p操作");
	//V操作释放一个资源
	semaphore.release();
	System.out.println("V操作");
	semaphore.acquire();
	System.out.println("p操作1");
	semaphore.acquire();
	System.out.println("p操作2");
	semaphore.acquire();
	System.out.println("p操作3");
	semaphore.acquire();
	System.out.println("p操作4");

}

image.png
发现“p操作4”没有打印,可用资源只有3,前面已经申请3次了,所以没打印,只能阻塞等待到释放资源。

5.CountDownLatch

直译过来就是“计数关闭门阀”,很难理解对吧?举个例子马上让你通透。
5名选手进行百米比赛的时候,当最后一个选手撞线比赛才结束。使用CountDownLatch也是类似,每个选手撞线的时候,就调用countDown方法,当撞线次数达到选手个数,就认为比赛结束。
在举个例子,使用多线程下载一个很大的文件,就切分成多个部分,每个线程负责下载一个部分,当一个线程下载完毕,就调用countDown方法,当所有线程都下载完毕,整个文件就下载完毕。

public static void main(String[] args) throws InterruptedException {
	//10个选手参加比赛
	CountDownLatch countDownLatch = new CountDownLatch(5);
	//创建10个线程来执行任务
	for (int i = 0; i < 5; i++) {
		Thread t = new Thread(() -> {
			System.out.println("选手出发" + Thread.currentThread().getName());
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("选手到达" + Thread.currentThread().getName());
			//选手撞线
			countDownLatch.countDown();
		});
		t.start();
	}
	//阻塞等待,直到所有选手都撞线,才能解除阻塞
	countDownLatch.await();
	System.out.println("比赛结束");
}

image.png

文章来源:https://blog.csdn.net/weixin_61427900/article/details/134900446
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。