本文是学习Java多线程与高并发知识时做的笔记。
这部分内容比较多,按照内容分为5个部分:
- 多线程基础篇
- JUC篇
- 同步容器和并发容器篇
- 线程池篇
- MQ篇
本篇为JUC篇。
目录
1 什么是JUC?
java.util.concurrent,Java并发工具包。主要包括:
- java.util.concurrent
- java.util.concurrent.atomic
- java.util.concurrent.locks
2 并发与并行
并发:多个线程快速地轮换执行,使得在宏观上具有多个线程同时执行的效果。
并行:多核CPU下,多个线程同时执行。
3 使用Callable创建线程
在java.util.concurrent中提供了一个Callable接口用来创建线程。
比起实现Runnable接口创建线程,实现Callable接口创建的线程:
- 可以有返回值
- 可以抛出异常
代码演示:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CreateThread {
static class MyCallable implements Callable<Integer> { //泛型规定返回值类型
@Override
public Integer call() throws Exception { //重写call()方法,类似于Runnable接口中的run()方法
System.out.println("Hello MyCallable");
return 1024;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask futureTask = new FutureTask(myCallable); //适配器模式
new Thread(futureTask).start(); //调用start()方法启动线程
//打印返回值
Integer result = (Integer) futureTask.get();
System.out.println(result);
}
}
需要注意的是,Integer result = (Integer) futureTask.get(); 这行代码可能会导致线程阻塞。
4 Lock
4.1 Lock锁
除了使用synchronized关键字,JUC中提供了另一种线程同步方法——Lock锁。
Lock是java.util.concurrent.locks包中的接口,它共有3个实现类:
- ReentrantLock,可重入锁
- ReentrantReadWriteLock.ReadLock,可重入读锁
- ReentrantReadWriteLock.WriteLock,可重入写锁
使用Lock锁的3个步骤:
- Lock lock = new ReentrantLock()
- lock.lock()
- lock.unlock(),如果不调用unlock()方法释放锁资源,会造成死锁,一般放在finally代码块中。
代码演示:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) {
MyLock myLock = new MyLock();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
myLock.test();
}).start();
}
}
}
class MyLock {
private int number = 10;
Lock lock = new ReentrantLock();
public void test() {
lock.lock();
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + " 操作后,number = " + --number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
运行结果:
Thread-0 操作后,number = 9
Thread-3 操作后,number = 8
Thread-4 操作后,number = 7
Thread-5 操作后,number = 6
Thread-7 操作后,number = 5
Thread-1 操作后,number = 4
Thread-2 操作后,number = 3
Thread-6 操作后,number = 2
Thread-8 操作后,number = 1
Thread-9 操作后,number = 0
4.2 公平锁和非公平锁
公平锁:多个线程完全按照申请锁的顺序去获得锁,新来的线程会直接进入等待队列去排队。
非公平锁:新来的线程会尝试获取锁,如果获取不到,再进入等待队列。
synchronized锁是非公平锁;lock锁默认是非公平锁,也可以使用公平锁。
lock使用公平锁:
Lock lock = new ReentrantLock(true);
4.3 Lock锁的生产者和消费者问题
synchronized锁使用Object类中的监视器方法(wait、notify、notifyAll)实现了线程之间的通信,java.util.concurrent.locks包中提供了类似的监视器方法。
使用Lock锁监视器的步骤:
- Lock lock = new ReentrantLock()
- Condition condition = lock.newCondition()
- lock.lock()
- condition.await(),线程等待,总是出现在循环中。
- condition.signalAll(),通知其它线程。
- lock.unlock(),一般写在finally代码块中。
生产者和消费者问题:
由多个线程同时操作同一个变量number:
- 生产者每次操作,number++
- 消费者每次操作,number--
当number == 0时,消费者不能对其进行操作。
代码演示:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) {
Data data = new Data();
int n = 4; //消费者数目
int k = 5; //每个消费者的消费额
new Thread(() -> {
for (int i = 0; i < n * k; i++) {
data.increment();
}
}, "Producer").start(); //生产者
for (int id = 0; id < n; id++) {
new Thread(() -> {
for (int i = 0; i < k; i++) {
data.decrement();
}
}, "Consumer" + id).start(); //消费者们
}
}
}
class Data {
private int number = 0;
Lock lock = new ReentrantLock(); //创建锁
Condition condition = lock.newCondition(); //创建监视器
public void increment() {
try {
lock.lock();
while (number > 3) {
condition.await(); //线程等待
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
condition.signalAll(); //通知其它线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() {
try {
lock.lock();
while (number <= 0) {
condition.await(); //线程等待
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
condition.signalAll(); //通知其它线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
4.4 读写锁
读写锁ReadWriteLock本质上是一种细粒度的Lock锁,又分为读锁和写锁:
- 读锁:又叫共享锁,可以被多个线程同时占有。用于只读操作。
- 写锁:又叫独占锁、排他锁,只允许被一个线程占有。用于写操作。
在多个线程对同一个资源进行读写操作时:
- 允许多个线程同时执行读操作
- 不允许多个线程同时执行写操作
- 不允许读操作和写操作同时执行
使用读写锁可以满足上述要求。
使用读锁的步骤:
- ReadWriteLock readWriteLock = new ReentrantReadWriteLock()
- readWriteLock.readLock().lock()
- readWriteLock.readLock().unlock(),一般写在finally代码块中。
使用写锁的步骤:
- ReadWriteLock readWriteLock = new ReentrantReadWriteLock()
- readWriteLock.writeLock().lock()
- readWriteLock.writeLock().unlock(),一般写在finally代码块中。
代码演示:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* ReadWriteLock
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.write(temp + "", temp);
}).start();
}
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "读取结果:" + myCache.read(temp + ""));
}).start();
}
}
}
/**
* 自定义缓存
*/
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void write(String key, Object value) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完成");
} finally {
readWriteLock.writeLock().unlock();
}
}
public Object read(String key) {
Object o;
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取");
o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取完成");
} finally {
readWriteLock.readLock().unlock();
}
return o;
}
}
运行结果:
Thread-0写入
Thread-0写入完成
Thread-2写入
Thread-2写入完成
Thread-1写入
Thread-1写入完成
Thread-3写入
Thread-3写入完成
Thread-4写入
Thread-4写入完成
Thread-5读取
Thread-5读取完成
Thread-9读取
Thread-9读取完成
Thread-5读取结果:0
Thread-6读取
Thread-6读取完成
Thread-8读取
Thread-8读取完成
Thread-9读取结果:4
Thread-7读取
Thread-7读取完成
Thread-7读取结果:2
Thread-8读取结果:3
Thread-6读取结果:1
5 线程八锁
线程八锁并不是八把实际的锁,而是八个关于锁的经典问题。
第一问:下面代码中,先执行操作A还是操作B?
import java.util.concurrent.TimeUnit;
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Client client = new Client();
new Thread(() -> {
client.actionA();
}).start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
client.actionB();
}).start();
}
}
class Client {
public synchronized void actionA() {
System.out.println("执行操作A");
}
public synchronized void actionB() {
System.out.println("执行操作B");
}
}
答:操作A。毫无疑问。
第二问:下面代码中,先执行操作A还是操作B?
import java.util.concurrent.TimeUnit;
public class Test2 {
public static void main(String[] args) throws InterruptedException {
Client client = new Client();
new Thread(() -> {
client.actionA();
}).start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
client.actionB();
}).start();
}
}
class Client {
public synchronized void actionA() {
try {
TimeUnit.SECONDS.sleep(2); //线程阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行操作A");
}
public synchronized void actionB() {
System.out.println("执行操作B");
}
}
答:操作A。虽然线程A阻塞了2秒,但在2秒前就已经拿到了锁资源。
第三问:下面代码中,先执行操作A、操作B还是操作C?
import java.util.concurrent.TimeUnit;
public class Test3 {
public static void main(String[] args) throws InterruptedException {
Client client = new Client();
new Thread(() -> {
client.actionA();
}).start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
client.actionB();
}).start();
new Thread(() -> {
client.actionC();
}).start();
}
}
class Client {
//同步方法
public synchronized void actionA() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行操作A");
}
//同步方法
public synchronized void actionB() {
System.out.println("执行操作B");
}
//普通方法
public void actionC() {
System.out.println("执行操作C");
}
}
答:先操作C,然后操作A,最后操作B。普通方法不用拿到锁资源就可以执行。
第四问:下面代码中,先执行操作A还是操作B?
import java.util.concurrent.TimeUnit;
public class Test4 {
public static void main(String[] args) throws InterruptedException {
Client client1 = new Client();
Client client2 = new Client();
new Thread(() -> {
client1.actionA(); //client1调用
}).start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
client2.actionB(); //client2调用
}).start();
}
}
class Client {
public synchronized void actionA() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行操作A");
}
public synchronized void actionB() {
System.out.println("执行操作B");
}
}
答:操作B。线程加锁的目标是对象,不同对象之间不会出现锁冲突。
第五问:下面代码中,先执行操作A还是操作B?
import java.util.concurrent.TimeUnit;
public class Test5 {
public static void main(String[] args) throws InterruptedException {
Client client = new Client();
new Thread(() -> {
client.actionA();
}).start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
client.actionB();
}).start();
}
}
class Client {
//静态同步方法
public static synchronized void actionA() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行操作A");
}
//静态同步方法
public static synchronized void actionB() {
System.out.println("执行操作B");
}
}
答:操作A。需要注意的地方是静态方法上锁的对象是Client.class。
第六问:下面代码中,先执行操作A还是操作B?
import java.util.concurrent.TimeUnit;
public class Test6 {
public static void main(String[] args) throws InterruptedException {
Client client1 = new Client();
Client client2 = new Client();
new Thread(() -> {
client1.actionA(); //client1调用
}).start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
client2.actionB(); client2调用
}).start();
}
}
class Client {
//静态同步方法
public static synchronized void actionA() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行操作A");
}
//静态同步方法
public static synchronized void actionB() {
System.out.println("执行操作B");
}
}
答:操作A。静态方法上锁的对象是Client.class。
第七问:下面代码中,先执行操作A还是操作B?
import java.util.concurrent.TimeUnit;
public class Test7 {
public static void main(String[] args) throws InterruptedException {
Client client = new Client();
new Thread(() -> {
client.actionA();
}).start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
client.actionB();
}).start();
}
}
class Client {
//静态同步方法
public static synchronized void actionA() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行操作A");
}
//同步方法
public synchronized void actionB() {
System.out.println("执行操作B");
}
}
答:操作B。毫无疑问。
第八问:下面代码中,先执行操作A还是操作B?
import java.util.concurrent.TimeUnit;
public class Test8 {
public static void main(String[] args) throws InterruptedException {
Client client1 = new Client();
Client client2 = new Client();
new Thread(() -> {
client1.actionA(); //client1调用
}).start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
client2.actionB(); //client2调用
}).start();
}
}
class Client {
//静态同步方法
public static synchronized void actionA() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行操作A");
}
//同步方法
public synchronized void actionB() {
System.out.println("执行操作B");
}
}
答:操作B。毫无疑问。
6 常用的辅助类
6.1 CountDownLatch
CountDownLatch是一种通用的同步工具。它的核心思想是:
允许一个或多个线程等待,直到在其它线程中的一组操作完成。
CountDownLatch类中的关键方法有:
- countDown(),每次调用countDown(),CountDownLatch计数器的值-1。
- await(),当前线程等待,直到CountDownLatch计数器归零后,唤醒当前线程。
代码演示:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
int count = 5;
CountDownLatch countDownLatch = new CountDownLatch(count); //设置计数器countDownLatch的初始值
for (int i = 0; i < count; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName());
countDownLatch.countDown(); //countDownLatch的值-1
}).start();
}
countDownLatch.await();//等待countDownLatch归零,再往下执行
System.out.println("ok");
}
}
6.2 CyclicBarrier
CyclicBarrier也是一种通用的同步工具。它的核心思想是:
允许一组线程全部等待彼此达到共同屏障点。
CyclicBarrier类中的关键方法有:
- await()
代码演示:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
int count = 7;
CyclicBarrier cyclicBarrier = new CyclicBarrier(count, () -> { //设置计数器cyclicBarrier的阈值
System.out.println("召唤神龙成功");
});
for (int i = 0; i < count; i++) {
//lambda表达式不能直接操作 i
final int temp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "收集到" + (temp + 1) + "星球");
try {
cyclicBarrier.await(); //cyclicBarrier的值+1
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "继续工作");
}).start();
}
}
}
运行结果:
Thread-0收集到1星球
Thread-4收集到5星球
Thread-2收集到3星球
Thread-3收集到4星球
Thread-1收集到2星球
Thread-5收集到6星球
Thread-6收集到7星球
召唤神龙成功
Thread-6继续工作
Thread-0继续工作
Thread-4继续工作
Thread-3继续工作
Thread-2继续工作
Thread-5继续工作
Thread-1继续工作
6.3 Semaphore
Semaphore,信号量,通常用于限制线程数。
信号量维持一组许可证。如果有必要,每个线程都会阻塞,直到获得许可证。
Semaphore类中的关键方法有:
- acquire(),当前信号量-1,如果当前信号量为0,则等待。
- release(),当前信号量+1。
代码演示:
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
int count = 2;
Semaphore semaphore = new Semaphore(count);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "得到许可,开始了它的表演");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "表演结束");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
}
运行结果:
Thread-0得到许可,开始了它的表演
Thread-1得到许可,开始了它的表演
Thread-1表演结束
Thread-0表演结束
Thread-2得到许可,开始了它的表演
Thread-3得到许可,开始了它的表演
Thread-2表演结束
Thread-3表演结束
Thread-4得到许可,开始了它的表演
Thread-4表演结束
学习视频链接:(这个讲的挺好的)
https://www.bilibili.com/video/BV1B7411L7tE
加油!(ง •_•)ง