目录

进程与线程

线程的六个状态

sleep()和wait()的区别

Lock锁

synchronized 关键字

Lock接口

公平锁

非公平锁(默认)

Lock测试代码

synchrnized 和 Lock 的区别

生产者和消费者问题

synchrnized 版本

两个线程是安全的,那么更多线程呢?

虚假唤醒

场景1:多个生产者被唤醒导致 num > 1

场景2:虚假唤醒后的错误执行

juc 版本

出现的问题

问题出现的原因

可能出现的死锁场景

如何解决?

condition的好处


进程与线程

  • 进程是资源分配的最小单位,它提供了隔离性和稳定性。

  • 线程是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. 当所有线程都在等待时,没有线程可以发送信号

  2. 生产者唤醒了生产者,消费者唤醒了消费者(同类唤醒)

  3. 信号可能被"错误"的线程接收,导致某些线程永远无法被唤醒

如何解决?

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的好处

精准地通知和唤醒线程

  1. 避免同类唤醒:生产者只唤醒消费者,消费者只唤醒生产者

  2. 提高效率:避免了不必要的线程唤醒和竞争

  3. 防止死锁:确保生产者和消费者交替执行

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐