• 多线程基础部分Part3


    目录

    线程间的等待与唤醒机制

    等待方法:wait

    a. 普通wait方法,痴汉方法,死等

    b.等待一段时间线程被唤醒就继续执行,否则不再继续等待,继续执行

    唤醒方法:notify

    a.普通notify() :随机唤醒一个处在等待状态的线程

    b.notifyAll() :唤醒所有处在等待状态的线程

    wait和notify方法的细节

     wait方法和sleep方法的区别

    引申:当前执行的线程数

    单例模式

    如何控制某个类只有一个对象

    单例三步走:

    饿汉式单例

    懒汉式单例

    懒汉式的线程安全问题

    解决懒汉式线程安全问题 (double-check)

    阻塞队列

    特性

    应用:生产消费者模型

    1.阻塞队列相当于缓冲区,平衡了消费者和生产者的处理能力。

    2.阻塞队列也能使生产者和消费者之间解耦

    JDK中的阻塞队列 BlockingQueue

    使用方法

    常用子类

    阻塞队列实现

    阻塞队列一般通过构造方法规定大小 

    定时器


    线程间的等待与唤醒机制

    wait和notify是Object类的方法,用于线程的等待与唤醒,必须搭配synchronized锁来使用

    等待方法:wait

    a. 普通wait方法,痴汉方法,死等

    进入此方法的线程会进入阻塞态(WATING),直到有其他线程调用notify方法唤醒此线程

    1. public void run() {
    2. synchronized (lock) {
    3. System.out.println(Thread.currentThread().getName()+"此线程准备进入等待方法");
    4. try {
    5. //等待其他线程将此线程唤醒
    6. lock.wait();
    7. } catch (InterruptedException e) {
    8. e.printStackTrace();
    9. }
    10. }
    11. }

    b.等待一段时间线程被唤醒就继续执行,否则不再继续等待,继续执行

    1. public void run() {
    2. synchronized (lock) {
    3. System.out.println(Thread.currentThread().getName()+"此线程准备进入等待方法");
    4. try {
    5. //等一秒,你不唤醒我我就继续了
    6. lock.wait(1000);
    7. } catch (InterruptedException e) {
    8. e.printStackTrace();
    9. }
    10. }
    11. }

    唤醒方法:notify

    a.普通notify() :随机唤醒一个处在等待状态的线程

    1. public void run() {
    2. synchronized (lock) {
    3. System.out.println(Thread.currentThread().getName()+"此线程准备唤醒其他线程");
    4. lock.notify();
    5. System.out.println("唤醒结束");
    6. }
    7. }

    b.notifyAll() :唤醒所有处在等待状态的线程

    1. public void run() {
    2. synchronized (lock) {
    3. System.out.println(Thread.currentThread().getName()+"此线程准备唤醒其他线程");
    4. lock.notifyAll();
    5. System.out.println("唤醒结束");
    6. }
    7. }

    wait和notify方法的细节

    对于wait和notify方法,不仅存在阻塞队列,还存在等待队列

    当我们线程进入wait方法之前是要获取锁的,获取锁的线程调用.wait方法会进入等待队列,并且将锁释放,其余线程在阻塞队列中等待获取锁。

    调用notify方法时,会将等待队列的线程唤醒,并将这个线程送回到阻塞队列,再次获取锁对象来进行线程wait之后的操作。

     wait方法和sleep方法的区别

    1.wait方法是Object类提供的方法,需要搭配synchronized锁来使用,调用wait方法会释放锁,线程进入WAITING状态,等待被其他线程唤醒或着超时自动唤醒,唤醒之后的锁需要再次竞争synchronized锁才能继续执行。

    2.sleep方法是Thread类提供的方法,调用sleep方法的线程进入TIMED_WAITING状态,不会释放锁,时间到自动唤醒。

    引申:当前执行的线程数

    Thread.activeCount()//包括一个隐藏的后台线程

    单例模式

    单例模式就是保证类在程序中只有一个唯一的对象

    如何控制某个类只有一个对象

    要创建类的对象,我们就要用到类的构造方法,所以我们将构造方法私有化,就无法在类的外部产生对象了。

    1. public class SingleTon {
    2. private SingleTon() {};
    3. }

    单例三步走:

    1.构造私有化(保证对象的产生个数)

    2.单例类的内部提供这个唯一对象(static)

    3.单例类提供返回这个唯一的对象的静态方法供外部使用

    饿汉式单例

    饿汉式单例采用直接在类中创建唯一静态对象,保证在JVM加载类的过程中创建这个唯一对象

    不管是否产生此类对象,只要类加载,唯一对象就会产生。

    所以,饿汉式单例的创建也满足天然线程安全,对象只在类加载时产生一次

    我们可以创建get方法来获取这个唯一的对象。

    1. public class SingleTon {
    2. //产生唯一对象
    3. public static SingleTon singleTon = new SingleTon();
    4. private SingleTon() {};
    5. //可通过方法获取此对象
    6. public static SingleTon getSingleTon() {
    7. return singleTon;
    8. }
    9. }

    懒汉式单例

    当第一次调用getSingleTon方法,表示外部需要获取这个单例对象时才产生对象

    在系统初始化时,外部并不需要这个单例对象,就先不产生,只有当外部需要此对象才实例化对象。

    这种操作称之为懒加载

    懒汉式的线程安全问题

    我们会用条件判断的方式来决定是否产生对象,而当多线程并行执行时,每条线程都产生了一个对象,结果就不对了

    1. public class LazySingleTon {
    2. private static LazySingleTon lazySingleTon;
    3. private LazySingleTon(){}
    4. public static LazySingleTon getLazySingleTon() {
    5. //第一次调用时产生对象
    6. if(lazySingleTon==null) {
    7. lazySingleTon=new LazySingleTon();
    8. }
    9. return lazySingleTon;
    10. }
    11. }

    解决懒汉式线程安全问题 (double-check)

    首先,当多线程出现线程安全问题时,最好的方式就是加锁,如果我们直接加在方法上,当然可以,但是锁的粒度太粗,体现不出多线程的快速。

    因此,我们需要优化方法锁

    尝试优化:

    首先,我们尝试在条件内部加锁:

     这样加锁,仍然无法阻止其他线程判断到对象未创建这一步,所有线程都会进到已经判断为null之后再进行加锁操作,这样没有意义。

    再次进行尝试,在锁的内部再次判断对象是否创建完成:

    进行到这一步时, 逻辑上已经没问题了,但是会出现这样一种问题:

    线程在创建对象的过程中,其他线程获取开始在外层条件判断对象是否已经创建,

     此时,虽然对象还未创建完成,但是外面已经显示不为空了,这样就会导致其他线程把没有创建好的对象获取。

    我们可以在对象处加上volatile关键字,保证当创建完对象后,才可进行return方法。

     这种解决懒汉式单例线程安全问题的步骤,我们称为double-check。

    阻塞队列

    特性

    阻塞队列也遵守先进先出的原则。

    队列满时,继续入队列就会阻塞,直到有其他线程从队列取走元素。

    队列空时,继续出队列就会阻塞,直到有其他线程往队列插入元素。

    应用:生产消费者模型

    1.阻塞队列相当于缓冲区,平衡了消费者和生产者的处理能力。

    当收到大量支付请求时,我们可以把这些支付请求放入缓冲区,也就是一个阻塞队列中,慢慢处理请求。

    2.阻塞队列也能使生产者和消费者之间解耦

    JDK中的阻塞队列 BlockingQueue

    使用方法

    入队方法 put()

    出队方法 take()

    常用子类

    ArrayBlockingQueue

    LinkedBlockingQueue

    阻塞队列实现

    阻塞队列一般通过构造方法规定大小 

    定时器

    JDK中使用Timer类描述定时器,核心方法就是schedule(指定时间到了要执行的任务,等待时间-ms)

    1. public static void main(String[] args) {
    2. Timer timer = new Timer();
    3. timer.schedule(new TimerTask() {
    4. @Override
    5. public void run() {
    6. System.out.println("hello timer");
    7. }
    8. //延时3秒开始,每1s执行一次
    9. },3000,1000);
    10. }

    可设置要执行的任务延迟多久开始,每隔多久执行一次

  • 相关阅读:
    利用 Forcing InnoDB Recovery 特性解决 MySQL 重启失败的问题
    多模块打包报错找不到包的问题
    0基础了解电商系统如何对接支付渠道
    将文件间的编译依赖关系(Compilation dependency)降至最低
    软路由和硬路由的区别是什么,性价比与可玩性分析
    Python笔记,面向对象(11)遍历操作与描述器
    【OpenCV】-图像的矩
    常用元器件使用方法35:SPI Flash芯片W25Q128JVSIQ
    分布式id的概述与实现
    gRPC之SAN证书生成
  • 原文地址:https://blog.csdn.net/weixin_65278827/article/details/125471715