Java 并发编程(1)
目录
进程与线程
-
进程是资源分配的最小单位,它提供了隔离性和稳定性。
-
线程是CPU调度的最小单位,它提供了更轻量的并发执行和更高效的通信
线程的六个状态
在Thread类下面有一个State枚举,可以看到线程的六种状态:
从源码可知:NEW新生,RUNNABLE运行,BLOCK阻塞,WAITING等待,TIMED_WAITING超时等待,TERMINATED终止
public enum State {
//线程新生
NEW,
//运行状态
RUNNABLE,
//阻塞状态
BLOCKED,
//等待,一直等
WAITING,
//超时等待,等一段时间
TIMED_WAITING,
//终止
TERMINATED;
}
sleep()和wait()的区别
1.来自不同的类:wait是Object类的,sleep是Thread类的。在企业中让线程休眠一般不会使用sleep,我们会使用java.util.concurrent包下的TimeUnit工具类操作:
TimeUnit.DAYS.sleep(1);//休眠一天
TimeUnit.SECONDS.sleep(2);//休眠两秒
2.锁的释放:wait会释放锁,sleep不会释放(抱着锁睡觉)
3.使用范围不同:wait必须在同步代码块当中使用,sleep可以在任何地方休眠
4.捕获异常:wait不需要捕获异常,sleep必须捕获异常
Lock锁
synchronized 关键字
一个买票的案例
package com.qcby;
public class SynchronizedDemo01 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(() -> {
for (int i = 0; i < 60; i++) {
ticket.sale();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 60; i++) {
ticket.sale();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 60; i++) {
ticket.sale();
}
}, "C").start();
}
}
class Ticket{
private int ticket = 50;
public void sale(){
if(ticket > 0){
System.out.println("线程" + Thread.currentThread().getName() + "卖出了第" + (50 - ticket) + "张票" +
",剩余" + ticket + "票");
ticket--;
}
}
}
结果:
线程之间相互争夺资源。
使用synchronized关键字之后:

结果就正常了

synchronized关键字就好比使这些线程排队了一样,不会像之前一样互相争夺资源
其的本质是队列和锁
Lock接口
Lock是java.util.concurrent.locks包下的一个接口,其有三个实现类:ReentrantLock(可重入锁)、ReentrantReadWriteLock.ReadLock(读锁)、ReentrantReadWriteLock.WriteLock(写锁);有两个方法:
Lock l = ...
l.lock(); //加锁
finally{
l.unlock(); //解锁
}
复制一份上面的Demo1,然后改一下Ticket类,去掉synchronized关键字:

点击ReentrantLock当中查看源码:
其构造方法当中可以传入boolean值,不传参的话默认是非公平锁,传参的话true是公平锁,反之是非公平锁:

公平锁
十分公平,先来后到
假如有两个线程A和B
A执行3小时,B执行3秒,假如A先拿到了锁,那么B就必须等待三个小时才能执行
非公平锁(默认)
可以插队
Lock测试代码
package com.qcby;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SynchronizedDemo02 {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(() -> { for (int i = 0; i < 60; i++) ticket.sale();},"A").start();
new Thread(() -> { for (int i = 0; i < 60; i++) ticket.sale();},"B").start();
new Thread(() -> { for (int i = 0; i < 60; i++) ticket.sale();},"C").start();
}
}
/**
* 1.new 一个 锁
* 2.加锁
* 3.解锁
*/
class Ticket2{
private int ticket = 50;
Lock l = new ReentrantLock();
public void sale(){
l.lock(); //加锁
try { //业务代码
if(ticket > 0){
System.out.println("线程" + Thread.currentThread().getName() + "卖出了第" + (50 - ticket + 1) + "张票" +
",剩余" + (ticket - 1) + "票");
ticket--;
}
} catch (Exception e) {
throw new RuntimeException(e);
}finally { //执行完之后解锁
l.unlock();
}
}
}
加锁之后的效果:

synchrnized 和 Lock 的区别
1.synchrnized 是 Java 内置的关键字;Lock是Java当中的一个类
2.synchrnized 无法判断锁的获取状态;Lock 可以
3.synchrnized 会自动释放锁 ;lock 必须手动释放锁 , 如果不释放锁:会引起死锁
4.synchrnized 假如线程一在获取锁之后阻塞了,线程二就会一直等下去;Lock当中有一个tryLock的方法可以尝试获取锁
5.synchrnized 可重入锁,不可以中断的,非公平;Lock 可重入锁,可以判断锁,可以自己设置公不公平(在构造器里面传参)
6.synchrnized 适合锁少量的同步代码问题;Lock 适合锁大量的同步代码
生产者和消费者问题
synchrnized 版本
package com.qcby.pc;
/**
* 线程之间的通信问题:生产者和消费者问题 等待唤醒,通知唤醒
* 两个线程交替执行 A B 线程操作同一变量 num
* A num++
* B num--
*/
public class Demo01 {
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 num = 0;
// +1
public synchronized void increment() throws InterruptedException {
if(num != 0){
//等待
this.wait();
}
num++;
//通知其他线程 操作完毕
System.out.println(Thread.currentThread().getName() + "=>" + num);
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
if(num == 0){
//等待
this.wait();
}
num--;
//通知其他线程 操作完毕
System.out.println(Thread.currentThread().getName() + "=>" + num);
this.notifyAll();
}
}

两个线程是安全的,那么更多线程呢?
假如我们有四个线程A B C D,A和C负责增加,B和D负责减少
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();
}

这是因为在方法当中我们写的是if判断,if只判断一次,产生了虚假唤醒
在 Condition 的 JavaDoc 中明确说明:
在等待条件时,必须使用循环来保护等待,以防止虚假唤醒。
虚假唤醒
假设 num=0 的初始状态:
场景1:多个生产者被唤醒导致 num > 1
1. 线程A(生产者)执行:num=0,通过if判断,开始生产 2. 线程A还没执行num++时,线程B(生产者)也进来 3. 线程B发现num=0,也通过if判断 4. 两个生产者都执行num++,结果num=2(违反约束)
场景2:虚假唤醒后的错误执行
1. 线程A(生产者)等待:num=1,进入等待 2. 线程B(消费者)消费:num变为0,唤醒一个线程 3. 线程A被唤醒(虚假唤醒可能发生) 4. 由于使用if,线程A不会重新检查条件 5. 线程A直接执行num++,但此时num可能已经被其他线程改变
现在不安全了,怎么解决呢?
把if改成while:

问题就解决了
juc 版本
package com.qcby.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo02 {
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{
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
private int num = 0;
// +1
public void increment() throws InterruptedException {
lock.lock();
try {
while(num != 0){
//等待
condition.await();
}
num++;
//通知其他线程 操作完毕
System.out.println(Thread.currentThread().getName() + "=>" + num);
condition.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
// -1
public void decrement() throws InterruptedException {
lock.lock();
try {
while(num == 0){
//等待
condition.await();
}
num--;
//通知其他线程 操作完毕
System.out.println(Thread.currentThread().getName() + "=>" + num);
condition.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
出现的问题

执行这么几行就没了
问题出现的原因
虽然我们使用了Condition的signal()方法,但这个方法只会随机唤醒一个等待线程,在多个生产者和消费者的情况下,可能会导致“虚假唤醒”或“信号丢失”的问题。
可能出现的死锁场景
-
当所有线程都在等待时,没有线程可以发送信号
-
生产者唤醒了生产者,消费者唤醒了消费者(同类唤醒)
-
信号可能被"错误"的线程接收,导致某些线程永远无法被唤醒
如何解决?
1.改成signalAll()


2.使用两个condition
package com.qcby.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo02 {
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{
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Condition con1 = lock.newCondition(); //生产者等待条件
Condition con2 = lock.newCondition(); //消费者等待条件
private int num = 0;
// +1
public void increment() throws InterruptedException {
lock.lock();
try {
while(num != 0){
//等待
con1.await();
}
num++;
//通知其他线程 操作完毕
System.out.println(Thread.currentThread().getName() + "=>" + num);
// condition.signal();
//唤醒消费者
con2.signal();
// condition.signalAll();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
// -1
public void decrement() throws InterruptedException {
lock.lock();
try {
while(num == 0){
//等待
// condition.await();
con2.await();
}
num--;
//通知其他线程 操作完毕
System.out.println(Thread.currentThread().getName() + "=>" + num);
//condition.signal();
// condition.signalAll();
con1.signal();//唤醒生产者
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}

condition的好处
精准地通知和唤醒线程
-
避免同类唤醒:生产者只唤醒消费者,消费者只唤醒生产者
-
提高效率:避免了不必要的线程唤醒和竞争
-
防止死锁:确保生产者和消费者交替执行
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)