Java多线程学习——JUC常见知识点全面总结
一. ReentrantLock
- 理解
和 synchronized 定位类似,都是用来实现互斥效果,用来保证线程安全,同时这个锁是可重入的
public class Test {
static class Counter{
public int count=0;
public void increase(){
count++;
}
}public static void main(String[] args) {
Counter counter=new Counter();
Thread t1 = new Thread() {
@Overridepublic void run() {
for (int i = 0;
i < 50000;
i++) {
counter.increase();
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
for (int i = 0;
i < 50000;
i++) {
counter.increase();
}
}
};
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(counter.count);
}
}
- 用法
经过之前的学习,我们认为此方法打印count是线程不安全的,不会每次都很准确地打印10000:
之前我们学过的解决方法是使用synchronized保证线程的安全性,代码如下:
static class Counter{
public int count=0;
synchronized public void increase(){
count++;
}
}
但此时我们可以通过创建ReentrantLock这一对象对其实现加锁,完整代码如下:
import java.util.concurrent.locks.ReentrantLock;
public class Test {
static class Counter {
public int count;
public ReentrantLock locker = new ReentrantLock();
public void increase() {
locker.lock();
count++;
locker.unlock();
}
}
public static void main(String[] args) {
Counter counter=new Counter();
Thread t1 = new Thread() {
@Override
public void run() {
for (int i = 0;
i < 50000;
i++) {
counter.increase();
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
for (int i = 0;
i < 50000;
i++) {
counter.increase();
}
}
};
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(counter.count);
}
}
- 与synchronized区别
ReentrantLock把加锁和解锁拆成了两个方法,确实存在遗忘解锁的风险,但可以让代码变得更加灵活,可以把加锁和解锁的代码分别放到两个方法之中
synchronized在申请锁失败时,代码会死等。而ReentrantLock 可以通过trylock这个方法等待一段时间就放弃,不会浪费时间
synchronized是非公平锁,而ReentrantLock默认是非公平锁。但可以通过构造方法传入一个 true 开启公平锁模式
ReentrantLock 有更强大的唤醒机制,synchronized 是通过 Object 的 wait / notify 方法实现等待唤醒过程的,每次唤醒的是一个随机等待的线程。而ReentrantLock搭配 Condition 类实现等待-唤醒,可以更精确控制唤醒某个指定的线程。
- 总结
锁竞争激烈的时候,使用ReentrantLock , 搭配 trylock 方法可以更灵活地控制加锁的行为,而不是死等。
如果需要使用公平锁, 使用 ReentrantLock
二. 原子类
- 理解
public AtomicInteger count = new AtomicInteger(0);
public void increase() {
count.getAndIncrement();
}
- 常见的原子类
AtomicInteger
AtomicIntegerArray
AtomicLong
AtomicReference
AtomicStampedReference
- 常见的方法
addAndGet(int delta);
相当于 i += delta;
decrementAndGet();
相当于–i;
getAndDecrement();
相当于i–;
incrementAndGet();
相当于++i;
getAndIncrement();
相当于i++;
三. 线程池
- 为什么要引入线程池
- 引入线程池的好处
当我们引入线程池后,相当于只在用户态完成各种操作,这样代码执行效率和系统开销会大大优化
- 创建线程池的方法
使用Java标准库中的ThreadPoolExecutor方式创建,但需注意里面各自的参数代表的含义,使用起来相对而言比较复杂。
构造方法
2) Executors
使用 Executors 这个类创建,这个类相当于一个工厂类,通过这个工厂类中的一些方法,就可以创建出不同风格的线程池实例了。
部分方法
Executors.newFixedThreadPool:创建一个固定大小的线程池
Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
Executors.newSingleThreadExecutor:创建出只包含一个线程数的线程池,它可以保证先进先出的执行顺序。
Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池(放入的任务能够过一会再执行)
Executors.newSingleThreadScheduledExecutor:创建出具有一个单线程并且可以执行延迟任务的线程池
用法示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
for (int i = 0;
i < 20;
i++) {
service.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
}
}
}
四. 信号量Semaphore
- 定义
当有车开进去的时候, 就相当于申请一个可用资源,可用车位就 -1 (这个称为信号量的 P 操作)
当有车开出来的时候, 就相当于释放一个可用资源, 可用车位就 +1 (这个称为信号量的 V 操作)
如果计数器的值已经为 0 了,还尝试申请资源,就会阻塞等待,直到有其他线程释放资源(计数器的值是大于等于0的)
- 作用
若把信号量的初始值设成1,则计数器的值只能取0或1了,此时把这个信号量称为二元信号量,和锁的功能类似,有加锁(没法申请资源)和解锁状态(可以申请资源)
- 用法示例
mport java.util.concurrent.Semaphore;
public class Test {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
System.out.println("准备申请资源");
semaphore.acquire();
System.out.println("申请资源成功");
// 申请到资源之后休眠1秒
Thread.sleep(1000);
semaphore.release();
// 释放资源
System.out.println("释放资源完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 创建15个线程,让这 15 个线程来分别去尝试申请资源
for (int i = 0;
i < 15;
i++) {
Thread t = new Thread(runnable);
t.start();
}
}
}
可以看到,由于资源数为3,所以前3个线程申请资源后很容易成功,而之后的线程就没有资源可以申请了,只能等到前3个线程把资源释放出来后再申请
信号量相当于是锁的升级版本,锁只能控制一个资源的有无,而信号量可以控制很多个资源的有无
五. CountDownLatch
- 理解
- 用法
用法示例
import java.util.concurrent.CountDownLatch;
public class Test {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(10);
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("任务开始");
try {
Thread.sleep((long) (Math.random() * 10000));
//生成随机数
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
System.out.println("任务完成!");
}
};
for (int i = 0;
i < 10;
i++) {
Thread t = new Thread(runnable);
t.start();
}
latch.await();
System.out.println("所有任务结束");
}
}
本文转载自博主「春风~十一载」的原创文章
GoodMaihttps://www.goodmai.com/ 好买网https://www.goodmai.com/
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 放屁有这三个特征的,请注意啦!这说明你的身体毒素太多
- 爱就是希望你好好活着
- 昨夜小楼听风
- 知识
- 死结。
- 我从来不做坏事
- 烦恼和幸福
- 关于QueryWrapper|关于QueryWrapper,实现MybatisPlus多表关联查询方式
- 事件代理