多线程线程通信

本文详细介绍了Java中的线程通信机制,包括wait(), notify(), notifyAll()的使用,以及如何在保证安全和效率的前提下实现线程的顺序执行。此外,还探讨了单例模式在多线程环境下的实现,强调了线程安全的重要性。最后,提到了阻塞队列和线程池的概念,以及它们在生产者消费者模型和任务调度中的作用。
摘要由CSDN通过智能技术生成

线程通信

什么是线程通信?使用场景?

多线程优势是提高cpu利用率(通过并发并行)执行时间比较的长任务,可能存在线程安全问题,且线程之间抢占式执行,执行顺序难以预知(随机执行)。

如何让线程具有一定的顺序性,在保证安全和效率前提下?

线程间通信:一个线程以通知的方式,唤醒某些等待线程或在某些条件下让当前线程等待,通过这样让线程间通过通信方式满足一定顺序性。

方法:

  • wait() / wait(long timeout): 让当前线程进入等待状态.
  • notify() / notifyAll(): 唤醒在当前对象上等待的线程

wait,notify,notifyAll 都是Object类方法

Wait()方法

wait 执行:

  • 使当前执行代码的线程进行等待. (把线程放到等待队列中)
  • 释放当前的锁
  • 满足一定条件时被唤醒, 重新尝试获取这个锁.

注意:wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常.

wait 结束等待的条件:

  • 其他线程调用该对象的 notify 方法.
  • wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
  • 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.

notify()方法

notify 方法是唤醒等待的线程.

  • 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其 它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。(在同步块/方法中执行)
  • 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 "先来后到")(随机唤醒一个线程)
  • 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行 完,也就是退出同步代码块之后才会释放对象锁。(执行完才会释放对象锁)

notifyAll()方法

notifyAll()方法唤醒所有等待线程

唤醒的线程(处于等待状态的),及调用synchronized竞争锁失败而阻塞的线程,再次进程锁,但还是只有一个竞争成功

使用方式及案例

方式:

  • 同步块,同步方法中

需要在synchronized结束(当前线程释放锁)后,以通知的方式,唤醒之前调用wait等待的线程

  • notify随机唤醒一个
  • notifyAll 全部唤醒

简单使用

package 线程通信;

public class 简单使用 {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (lock) {
                        //先做一些事情
                        System.out.println("线程1:步骤1");
                        //在某些条件下,就需要等待
                        lock.wait();
                        //被唤醒,就做另一些事情
                        System.out.println("线程1:步骤2");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        /**
         * 执行顺序:
         * 线程1 synchronized -> wait
         * 线程2 synchronized -> notify
         * 线程1 wait 往下
         */
        Thread.sleep(100);
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    //做一些事情
                    System.out.println("线程2:步骤1");
                    //通知线程1:唤醒执行
                    lock.notify();
                    //做另一些事情
                    System.out.println("线程2:步骤2");
                }
            }
        }).start();
    }
}

多线程案例

单例模式

常考设计模式之一:设计模式面对一些问题场景的固定套路

概念:

保证某个类在程序中只存在唯一一份实例,(不会创建多个实例)

很多场景需要,例如JDBC中的DataSource,数据库连接池。

具体实现方式分两种:饿汉和懒汉

饿汉式

在类加载时,创建实例:利用static特性(类加载时,且只执行一次)

写法:静态变量初始化时赋值实例化

class Singleton{
    //类加载时创建
    private static Singleton instance = new Singleton();
    //构造函数
    private Singleton(){}
    //唯一实例,获取接口
    public static Singleton getInstance(){
        return instance;    
    }
}

注意:即使不使用,也会创建对象(占用内存空间)

懒汉式

写法:初始化时不赋值,使用时,如果没有初始化再初始化

单线程

class Singleton{
    private static Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance(){
        if(instance==null)
            instance = new Singleton();
        return instance;                            
    }
}

但再多线程模式下不安全,多个线程同时调用getInstance方法会创建出多个实例!!

多线程重点

//单例读多,写少,读volatile,写加锁
class Singleton{
    //instance:读操作,加volatile 保证安全性
    private static volatile Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance(){
        //一层读直接返回;若为写,加锁
        if(instance==null){
            //写操作,加锁
            synchronized(Singleton.class){
                //防止多线程,再次创建改变引用
                if(instance==null)
                    instance = new Singleton();
            }        
        }
        return instance;                            
    }
}

阻塞队列

阻塞队列(同样遵循“先进先出”)能是一种线程安全的数据结构, 并且具有以下特性:

  • 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
  • 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素

典型应用:生产者消费者模型(经典的开发模型)

生产者消费者模型

通过一个容器解决生产者和消费者的强耦合问题

生产者和消费者彼此之间不直接通讯,通过阻塞队列进行通讯;

生产者生产完数据后,直接给阻塞队列;消费者再阻塞队列中取数据;

  • 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.(削峰)

防止生产过快

  • 阻塞队列使生产者和消费者解耦(减弱依赖关系)

标准库中的阻塞队列BlockingQueue

  • BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.

  • put 方法用于阻塞式的入队列, take 用于阻塞式的出队列.
  • BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.

线程池

池:字符串常量池,数据库连接池等(类似缓存)

理解(暂时):初始化(new线程池)的时候,创建一定数量的线程(不停的在线程池内部的一个阻塞队列)取任务(消费者)。

我们可以在其他线程中提交任务(生产者)至线程池

优势:线程的创建及销毁存在一定代价(性能消耗),使用线程池可以重复使用线程执行多组任务。

jdk原生的线程池api

ThreadPoolExecutor( //即使空闲时仍保留在池中的线程数(正式工),除非设置 allowCoreThreadTimeOut 
                    int corePooSize, 
                    //池中允许的最大线程数(正式工+临时工)
                    int maximumPoolSize,
                    //当线程数大于内核(正式工)时,这是多余的空闲线程在终止前等待新任务的最大时间。
                    long keepAliveTime,
                    //keepaliveTime的时间单位,是秒,分钟,或其他值
                    TimeUnit unit,
                    //传递任务的阻塞队列,这个队列将仅保存execute方法提交的Runnable任务
                    BlockingQueue<Runnable> workQueue,
                    //执行程序创建新线程时使用的工厂
                    ThreadFactory threadFactory,
                    //拒绝策略,(工作任务超出负荷,如何处理)
                    RejectedExecutionHandler handler)
//creates a new ThreadPoolExecutor with the given inital parameters

拒绝策略:(一般需要自己实现拒绝策略)

  • AbortPolicy():超出负荷,直接抛出异常(默认的拒绝策略)
  • CallerRunsPolicy():调用者负责处理(那个线程提交的任务,那个线程去执行)
  • DiscardOldestPolicy():丢弃队列中最老的任务
  • DiscardPolicy():丢弃新来的任务

线程池使用:

  1. new线程池
  2. 提交任务
    1. 线程池对象.execute(Runnable task)
    2. 线程池对象.submit(Runnable tack)

线程池的工作流程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值