基于bilibili狂神说JUC并发编程视频所做笔记
JUC时java.util工具包中的三个包的简称
java.util.concurrent
java.util.concurrent.atomic
java.util.concurrent.locks
业务:普通的线程代码中,我们常使用Runnable接口
但Runnable没有返回值,且效率相比较于Callable来说相对较低,功能也没有Callable强大
进程:相当于一个程序
一个进程当中往往可以包含多个线程,且至少包含一个线程
Java默认有2个线程:mian(主线程),GC(垃圾回收)
Java真的可以开启线程吗?
java是无法开启线程的,Java运行在JVM(虚拟机)之上,无法直接操作硬件,因此其实际上是无法开启线程的,在我们无论使用Runnable接口还是继承Thread,用start()方法开启线程,其本质上都是调用==private native void start0()==方法,而该方法是本地方法,是运行底层的C++
并发编程:
并发与并行:
并发:(多个线程同时操作一个核)
并行:(多个线程操作多个核)
查看自己CPU核数:
public class Test1 {
public static void main(String[] args) {
// 获取CPU的核数
// COU 密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
并发编程的本质:充分利用CPU的资源
线程有几个状态
答: 6个,分别为:NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED
public enum State{
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
*
* - {@link Object#wait() Object.wait} with no timeout
* - {@link #join() Thread.join} with no timeout
* - {@link LockSupport#park() LockSupport.park}
*
*
* A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called {@code Object.wait()}
* on an object is waiting for another thread to call
* {@code Object.notify()} or {@code Object.notifyAll()} on
* that object. A thread that has called {@code Thread.join()}
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
*
* - {@link #sleep Thread.sleep}
* - {@link Object#wait(long) Object.wait} with timeout
* - {@link #join(long) Thread.join} with timeout
* - {@link LockSupport#parkNanos LockSupport.parkNanos}
* - {@link LockSupport#parkUntil LockSupport.parkUntil}
*
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
WAITING 与 TIMED_WAITING的区别为:
WAITING会一直等待唤醒,或其他线程,资源的响应
TIMED_WAITING为超时等待,一旦时间到,则不再等待
wait/sleep的区别
wait 来自Object类
sleep来自Thread类
wait会释放锁
sleep不会释放锁,抱着锁睡觉
wait 必须在同步代码块中使用
sleep 可以在任何地方睡
wait不需要捕获异常
sleep必须要捕获异常
(但是,只要与线程有关的操作,都要捕获中断异常)
传统Synchronized
package syn;
// OOP并发编程
public class SaleTicketDemo01 {
public static void main(String[] args) {
// 声明一个票对象,使3个线程可以调用买票方法
Ticket ticket = new Ticket();
// 使用lambda表达式,回顾:lambda表达式是一种极简的表达艺术,但仅用于函数式接口
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"C").start();
}
}
// 高耦合,对象及为对象,不要附加多余功能,不要将其变成线程类
class Ticket{
// 属性、方法
private int ticketNums = 30;
// synchronized 本质就是锁,队列
public synchronized void sale(){
if (ticketNums > 0) {
System.out.println(Thread.currentThread().getName()+" sales "+ticketNums--+",and remains"+ticketNums);
}
}
}
java.util.concurrent.locks下有三个接口
Lock接口
实现类:
在ReentrantLock中,其构造函数:
公平锁:先到先得
非公平锁(默认):可以插队,看CPU调度
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo02 {
public static void main(String[] args) {
// 声明一个票对象,使3个线程可以调用买票方法
Ticket ticket = new Ticket();
// 使用lambda表达式,回顾:lambda表达式是一种极简的表达艺术,但仅用于函数式接口
new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"A").start();
new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"B").start();
new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"C").start();
}
}
// 高耦合,对象及为对象,不要附加多余功能,不要将其变成线程类
// 使用lock锁
/*
Lock三部曲
1、 new ReentrantLock()
2、 lock.lock()
3、 finally => lock.unlock()
*/
class Ticket{
// 属性、方法
private int ticketNums = 30;
Lock lock = new ReentrantLock();
// synchronized 本质就是锁,队列
public void sale(){
lock.lock();// 加锁
try {
//业务代码
if (ticketNums > 0) {
System.out.println(Thread.currentThread().getName()+" sales "+ticketNums--+",and remains"+ticketNums);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.unlock();// 解锁
}
}
}
1、synchronized内置的Java关键字;而Lock是一个Java类
2、synchronized无法判断获取锁的状态;Lock可以判断是否获取到了锁
3、synchronized会自动释放锁;Lock必须手动释放锁,如果不释放锁,则会造成死锁
4、synchronized线程1(获得锁),线程2(一直等待); Lock锁时,线程2就不一定会等待下去
5、synchronized可重入锁,不可中断的,非公平锁(不可更改);Lock,可重入锁,可以判断锁,默认非公平(可以修改)
6、synchronized适合锁少量的代码同步问题;Lock锁适合锁大量的同步代码
看8锁现象!!!
生产者消费者问题 synchronized版
package PC;
/**
* 线程之间的通信问题:生产者和消费者问题 等待唤醒 通知唤醒
* 线程交替执行 A B 操作同一个变量 num = 0
* A num+1
* B num-1
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
}
}
// 口诀:等待,业务,通知
// 资源类
class Data{
private int number = 0;
// +1操作
public synchronized void increment() throws InterruptedException {
// 等待
if (number != 0){
this.wait();
}
// 业务
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我加一完毕
this.notifyAll();
}
// -1操作
public synchronized void decrement() throws InterruptedException {
// 等待
if (number == 0){
this.wait();
}
// 业务
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我减一完毕
this.notifyAll();
}
}
这真的线程安全吗?如果有四个线程同时跑呢?
答:不安全,当四个线程在跑时,则会出现意料之外的情况
为什么会产生这种情况?
答:造成这种现象的原因是虚假唤醒
什么是虚假唤醒?
多线程环境下,有多个线程执行了wait()方法,需要其他线程执行notify()或者notifyAll()方法去唤醒它们,假如多个线程都被唤醒了,但是只有其中一部分是有用的唤醒操作,其余的唤醒都是无用功;对于不应该被唤醒的线程而言,便是虚假唤醒。
比如:仓库有货了才能出库,突然仓库入库了一个货品;这时所有的线程(货车)都被唤醒,来执行出库操作;实际上只有一个线程(货车)能执行出库操作,其他线程都是虚假唤醒。
参考博客:Java线程虚假唤醒是什么、如何避免?_java 虚假唤醒_秃秃爱健身的博客-CSDN博客
很重要:以下是个人感悟!
其实当初笔者在此处还是很困惑的,为什么虚假唤醒会造成线程同时运行而不顾if条件语句,后来笔者意识到一个很重要的问题:“wait()方法会使线程放弃锁”。也就是说,当A线程拿到了同步锁之后,进入if条件语句判断,如果此时条件为true,它会进入waiting状态并放弃同步锁,因此,C线程在这段时间有可能会乘虚而入,抢在B线程或D线程将A线程唤醒前进入同步代码块,同样进入if语句的waiting状态,之后,B线程或D线程完成其业务逻辑后,执行notifyAll()方法,就会将A线程与C线程同时唤醒,然后两者都会执行业务逻辑,导致一次减,两次加,与我们的预期(我们的逻辑是加一次,减一次)不符。因此线程不安全。
以上解释只是个人猜想,还未曾验证过,比如将wait换成sleep,抱着线程休眠是否会出现同样的问题
如何避免虚假唤醒?
将if条件语句改为while循环语句
当使用if条件语句时,如果线程在if条件语句中被wait中断退出,当其重新进入回到它原本所在的位置后就会发现,它已经进行过判断了,接下来,就算已经有线程抢先一步操作,它也会义无反顾地往下走,因为没有条件能够拦住它啦!
而当我们使用while循环语句会发现:(以下是官方文档所给的推荐代码)
synchronized (obj) {
while (<condition does not hold> and <timeout not exceeded>) {
long timeoutMillis = ... ;
// recompute timeout values int nanos = ... ;
obj.wait(timeoutMillis, nanos);
}
... // Perform action appropriate to condition or timeout
}
如果线程在while循环中被wait中断退出,当其重新进入回到它原本所在的位置后就会发现,本次循环已经结束,**接下来并不是执行后面的业务代码,而是返回到while循环开头,重新判断一次是否满足条件。**这样的操作就保证了即使在退出重进,也会进行再一次的判断确保线程安全。
以下为示例中被修改的代码片段:
public synchronized void increment() throws InterruptedException {
// 等待
// ***************** 此处的if被改为while **********************
while (number != 0){
this.wait();
}
// 业务
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我加一完毕
this.notifyAll();
}
// -1操作
public synchronized void decrement() throws InterruptedException {
// 等待
// ***************** 此处的if被改为while **********************
while (number == 0){
this.wait();
}
// 业务
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我减一完毕
this.notifyAll();
}
生产者消费者问题 JUC版
对应于synchronized,JUC版本下,Lock锁也有对应的唤醒与停止方法,分别是condition接口下的signal()与await()
以下是官方文档的描述:
示例代码如下:
package PC;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class B {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"D").start();
}
}
// 口诀:等待,业务,通知
// 资源类
class Data2{
private int number = 0;
Lock lock = new ReentrantLock();
// +1操作
public void increment() throws InterruptedException {
Condition condition = lock.newCondition();
/**
* condition.await(); 等待
* condition.signalAll(); 唤醒全部
*/
try {
lock.lock();
// =============== 业务代码 ==================
// 等待
while (number != 0){
condition.await();
}
// 业务
number++;
// 通知其他线程,我加一完毕
System.out.println(Thread.currentThread().getName()+"=>"+number);
// ===========================================
} catch (Exception e) {
throw new RuntimeException(e);
}finally{
lock.unlock();
}
}
// -1操作
public void decrement() throws InterruptedException {
Condition condition = lock.newCondition();
try {
lock.lock();
// =============== 业务代码 ==================
// 等待
while (number == 0){
}
// 业务
number--;
// 通知其他线程,我减一完毕
System.out.println(Thread.currentThread().getName()+"=>"+number);
// ===========================================
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
Condition 的优势在哪里
可以实现精准的通知和唤醒线程
以下示例实现精准唤醒线程,在A线程执行完后精准唤醒B线程执行,B线程执行完后精准唤醒C线程执行,C线程执行完后精准唤醒A线程执行
package PC;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* A 执行完调用 B,B 执行完调用 C,C 执行调用 A
*/
public class C {
public static void main(String[] args) {
// new 资源类
Data3 data = new Data3();
// 创建线程并执行线程
new Thread(()->{for (int i = 0; i < 5; i++) data.printA();},"A").start();
new Thread(()->{for (int i = 0; i < 5; i++) data.printB();},"B").start();
new Thread(()->{for (int i = 0; i < 5; i++) data.printC();},"C").start();
}
}
//资源类
class Data3{
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
private Condition conditionC = lock.newCondition();
private int number = 1; // 若number=1则A执行,若number=2则B执行,若number=3则C执行
public void printA(){
lock.lock();
try {
// 业务, 判断->执行->通知
while(number != 1){
conditionA.await();
}
System.out.println(Thread.currentThread().getName()+" now is AAAAA time!");
// 唤醒指定的线程B
number ++;
conditionB.signal();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while(number != 2){
conditionB.await();
}
System.out.println(Thread.currentThread().getName()+"now is BBBBB time!");
// 唤醒指定线程C
number++;
conditionC.signal();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while(number != 3){
conditionC.await();
}
System.out.println(Thread.currentThread().getName()+"now is CCCCC time!");
number = 1;
conditionA.signal();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}