Java多线程基础:线程创建、线程控制、线程安全、线程间通信、单例模式、阻塞式队列、线程池

1.初识多线程

1.1 概念
  • 进程是系统分配资源的最小单位
  • 线程是系统调度的最小单位
  • 一个进程内的线程之间是可以共享资源的,每个进程至少有一个线程存在,即主线程。
  • java级别的主线程:自己写的入口函数main方法(可有没有这个线程)
  • java进程来说,至少有一个非守护线程还没有终止,进程就不会结束。
1.2 多线程使用场景
  • 并发并行的执行,来提高性能,效率

    • 并发:多个进程在一个CPU下采用时间片轮转的方式,在一段时间之内,让多个进程都得以推进,称之为并发(假同时)。
    • 并行:多个进程在多个CPU下分别同时进行运行,称之为并行(真同时)。
  • 阻塞代码,让后续代码能够执行,不受阻塞代码的影响

1.3 jconsole

—— java 监视和管理控制台
使用jconsole查看线程运行状态:
在java的jdk的bin目录下或者在idea的Terminal中输入jconsole打开
在这里插入图片描述

1.4 线程的状态
1.4.1 线程所有状态
    public static void main(String[] args) {
        for(Thread.State state : Thread.State.values()){
            System.out.println(state);
        }
    }

运行结果:

NEW  创建状态
RUNNABLE 可运行状态
BLOCKED 阻塞态
WAITING 等待状态
TIMED_WAITING 超时等待
TERMINATED 终止态
1.4.2 状态转移图

在这里插入图片描述

2. 线程控制

2.1 创建线程
2.1.1 使用Thread类重写run方法创建

代码示例:

    public static void main(String[] args) throws InterruptedException {

        //使用Thread类,重写run方法
        //main线程中,new了线程对象(匿名内部类Thead子类重写run方法)
       Thread thread = new Thread(){
           @Override
           public void run() { //线程进入运行态之后执行
               while(true) {
                   
               }
           }
       };
       //启动线程,要启动线程必须调用start()方法,告诉操作系统调度本线程
       //申请系统调用,线程由创建态转变为就绪态,由系统决定什么时候成为运行态
       thread.start();
    }

使用jconsole观察:
在这里插入图片描述

2.1.2 实现Runnable接口

代码示例:

 class MyRunnable implements Runnable{
        //实现Runnable接口
        @Override
        public void run() {

        }
    }
    
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
        Thread t = new Thread(mr);
        t.start();
    }
2.1.3 其他变形

代码示例:

public static void main(String[] args) {
        //使用匿名类创建Runnable子类对象
        Runnable r = new Runnable() { //任务描述的可执行类,作为线程对象的构造方法中传入
            @Override
            public void run() {

            }
        };
        Thread t = new Thread(r);
        t.start();

        //合并代码
        //使用匿名内部类,创建线程并启动
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("使用匿名内部类创建Runnable子类对象");
            }
        }).start();


        //使用lambda表达式创建Runnable子类对象
        Thread t = new Thread(()->{
            System.out.println("使用匿名内部类创建Runnable子类对象");
        });
        t.start();


    }
2.1.4 解读start() 和 run()

基础认识:代码行在哪个线程执行,就是由哪个线程执行代码行,启动线程是必须要调用start()方法的。

  • start():会向系统申请启动某个线程,如果该线程处于运行态,会自动执行run()
  • run():通过Thread或Runnable类,定义要执行的任务代码(线程运行态执行的代码)——调用run(),相当于java对象直接调用普通实例方法
2.2 Thread类属性及常用方法
2.2.1 Thread类常见构造方法
  • Thread():创建线程对象
  • Thread(String name):创建线程对象,并命名
  • Thread(Runnable target):使用Runnable对象创建线程对象
  • Thread(Runnable target,String name):使用Runnable对象创建线程对象,并命名
2.2.2 Thread类常见属性

在这里插入图片描述

2.2.3 Thread类的常用方法介绍
静态方法:作用在当前代码行即当前线程
  • static int activeCount():获取当前线程组中,还存活的线程数量
  • static Thread currentThread():获取代码行所在的当前线程
  • static boolean interrupted():测试当前线程是否中断
  • static void sleep(long millis):让当前线程休眠给定的时间(毫秒)
  • static void yield():当前线程让步,即从运行态转变为就绪态
实例方法:作用在调用的线程对象上
  • long getId():返回此线程标识符
  • String getName():返回此线程的优先级
  • Thread.State getState():返回此线程的状态
  • int getPriority():返回此线程的优先级
  • boolean isDaemon():测试这个线程是否是守护线程
  • boolean isAlive():测试这个线程是否或者存活
  • boolean isInterrupted():测试这个线程是否被中断
  • void interrupt():中断这个线程
  • void join():无条件等待:当前线程阻塞并等待,一直等到调用线程执行完毕
  • void join(long millis):限时等待:当前线程阻塞并等待,直到调用线程执行完,或时间到了,再往下执行。
  • void run(): 定义线程的任务
  • void setDaemon(boolean on): 设置这个线程为守护线程
  • void setUncaughtExceptionHandler: 线程中处理异常的方式
  • static void sleep(long millis):使当前正在执行的线程以指定的毫秒数休眠(暂时停止执行)
  • void start():启动线程开始执行
2.3 线程等待

有时候,需要等待一个线程完成它的工作后,才能进行自己的下一步工作,所以这时候需要一个方法明确等待线程的结束。

  • 无条件等待:void join()
    代码示例:
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {

                Thread.sleep(4000);//工作线程休眠4s打印
                System.out.println("I am workthread");
            }
        });
        t.start();
        //main线程阻塞等待,直到当前线程对象执行完,在往后执行
        t.join();
        System.out.println("I am mainthread");
    }

运行结果:
调用join前:
在这里插入图片描述
调用join后:
在这里插入图片描述
可以看到没有调用join前,是先打印mainthread然后打印workthread,调用join之后,主线程无条件等待工作线程打印完后在打印

  • 限时等待:void join(long millis)
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {

                Thread.sleep(4000);
                System.out.println("I am workthread");
            }
        });
        t.start();
        //main线程限时等待,直到t线程执行完毕,或者给定时间已到
        //等待2s向下执行
        t.join(2000);

        //先等待t线程执行完,系统调度t由就绪态转变为运行态的时间,加上t的运行时间,
        //不是给多少s等多少s
        //t.join(6000);
        System.out.println("I am mainthread");
        
    }

运行结果:

  • 等待的时间少于工作线程运行的时间
    在这里插入图片描述
    等待2s打印mainthread,到4s再打印workthread

  • 等待的时间大于工作线程运行的时间
    在这里插入图片描述
    等待4s之后,并发执行,并没有等够6s。

2.4 线程中断

目前常见的有以下两种方式:

  • 通过共享的标记来进行沟通
  • 调用interrupt()方法来通知:void interrupt()

使用:

  • Thread.currentThread().isInterrupted():当前线程调用
  • 线程对象.isInterrupted():其他线程调用

代码示例:

中断后不会清除中断标志:

  • 中断以后立即停止执行
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
              
         // isInterruptted() 获取标志位,是否被中断,初始值为false,中断以后中断标志位被改为true
                //中断以后停止执行
                try {
                    while(true&& !Thread.currentThread().isInterrupted()){
                        System.out.println("i am workthread"+Thread.currentThread().getName());
                        Thread.sleep(1000);
                    }
                }catch (InterruptedException e){
                    e.printStackTrace();
                }

        System.out.println("t  start");

        Thread.sleep(5000);
        //告诉t线程,要中断(将t线程的中断标志位设置为true) 由t的代码自行决定是否中断
        // 如果t线程处于阻塞状态,会抛出InterruptedException,并且重置t线程的中断标志位
        t.interrupt();
        System.out.println("t stop");
        // main
        while(true){
            System.out.println("i am mainthread");
            Thread.sleep(1000);
        }

运行结果:
在这里插入图片描述

  • 中断之后,抛出异常继续运行
 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                // isInterruptted() 获取标志位,是否被中断

                //中断以后,抛出异常后,继续执行
                while(true&& !Thread.currentThread().isInterrupted()){
                    System.out.println("i am workthread"+Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start(); // 线程启动,中断标志位为false

        System.out.println("t  start");

        Thread.sleep(5000);
        //告诉t线程,要中断(将t线程的中断标志位设置为true) 由t的代码自行决定是否中断
        // 如果t线程处于阻塞状态,会抛出InterruptedException,并且重置t线程的中断标志位
        t.interrupt();
        System.out.println("t stop");

        while(true){
            System.out.println("i am mainthread");
            Thread.sleep(1000);
        }

    }

在这里插入图片描述
注意:如果t线程调用了wait/join/sleep处于阻塞状态,会抛出InterruptedException异常,并且会重置t线程的中断标志位

中断后清除中断标志:

  • static boolean interrupted()
    Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志

代码示例:

    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {

                for(int i=0;i<10000;i++){
                    System.out.println(i+":"+Thread.interrupted());
                }
            }
        });
        t.start();// 线程启动,中断标志位为false
        System.out.println("t start");

        t.interrupt(); //线程中断,中断标志位为true

        System.out.println("t stop");
    }

运行结果:
在这里插入图片描述

3. 线程安全

3.1 线程不安全的原因
3.1.1 原子性

多行指令(某些代码,看着是一行,其实会分解为多条指令执行)。如果指令前后有依赖关系,不能插入其他影响执行结果的指令,如果能够插入就是没有原子性,不能插入就是有原子性。

  • 不能切分的最小单位(多行指令)

  • 看似一行其实多个执行:

    方法A(new 对象()).方法B() ===> 方法A的返回对象调用方法B

  • 特殊的一行代码:

    • n++,n–,++n,–n,分解为三条指令

      • 从内存把数据读到CPU
      • 进行数据更新
      • 把数据写回到内存
    • new对象:分解为三条指令

3.1.2 可见性

为了提高效率,JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,共享变量在多线程之间不能及时看到改变,这个就是可见性问题。

  • 主存:线程共享
  • 工作内存:线程私有内存+CPU高速缓存/寄存器
  • 对主存中共享数据的操作,存在主存到工作内存 <===> 从主存读取(拷贝)、工作内存修改,写回主存。
    在这里插入图片描述
3.1.3 顺序性

单个CPU执行某个线程内的多行指令1、2、3,如果2、3指令没有依赖关系,并且3、2的顺序效率更高,可能产生指令重排序。
在这里插入图片描述

  • 单个线程站在自己的视角看代码执行,总是有序的
  • 其他线程的视角看,总是无序的(指令重排序优化)
3.2 synchronized 关键字

—— 线程安全,同步关键字

3.2.1 初识synchronized

实现:

  • synchronized的底层是使用操作系统的mutex lock实现的。

    • 当线程释放锁时,JMM会把该线程对应的工作内存中的共享变量刷新到主内存中
    • 当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量

作用:

  • 对一段代码加锁操作,让某段代码满足三个特性:原子性,可见性,有序性

  • synchronized同步快对同一条线程来说是可重入的,不会出现自己把自己锁死的问题

    • 可重入性:同一个线程可以对一个对象多次申请,基于重入计数器来实现,申请获取+1,释放-1
  • 同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入。

语法:

  • 同步代码块:synchronized(某个对象){…}
  • 同步方法:实例同步方法(this加锁)、静态同步方法(当前类对象加锁)
    注意:只有对同一个对象加锁,才会让线程产生同步互斥的作用。

代码示例:
以SynchronizedTest类为例

  • 锁的SynchronizedTest对象
//对临界区代码加锁
synchronized (SynchronizedTest.class){ //加锁
		//对SynchronizedTest对象加锁
}//解锁


    //静态同步方法 对类对象加锁
public synchronized static void method(){
	//临界代码块
}
  • 锁的this对象
public class SynchronizedTest {
    public void method(){
        synchronized (this){
            //进入代码会锁this指向对象的锁
        }
    }
    public static void main(String[] args) {
        SynchronizedTest st = new SynchronizedTest();
        //对this对象加锁
        st.method();
    }
}

  • 锁的SynchronizedTest实例对象
public class SynchronizedTest {

    public static void method(){
        synchronized (new SynchronizedTest()){
            //进入代码会对new出来的实例对象加锁
        }
    }

    public static void main(String[] args) {
        method();
    }
}
3.2.2 synchronized多个线程同步互斥
  • 一个时间只有一个线程执行(同步互斥)
  • 竞争锁失败的线程,不停会在阻塞态与运行态切换(用户态与内核态切换)
  • 同步线程数量越多,性能越低

线程执行同步代码块,同步方法(synchronized代码行)示例图:
在这里插入图片描述

3.2.3 代码示例

模拟抢票场景:定义若干个黄牛线程,进行抢票,并对每个黄牛抢票数进行限制

public class ThreadTestDemo {


    //票数量
    private static int TICKET = 50;

    private static class MyThread implements Runnable{
        private int num; //限制每个黄牛可以抢票的数量
        public MyThread(int num){
            this.num = num;
        }
        @Override
        public void run() {
            while(TICKET>0){
                synchronized (MyThread.class){ //对MyThread对象加锁

                    if(TICKET>0&&num>0){

                        num--;
                        System.out.println(Thread.currentThread().getName()+"抢到票:"+TICKET+" 还可以抢:"+num+"张");
                        TICKET--;
                    }
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                } //释放锁

            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new MyThread(20),"黄牛A");
        Thread t2 = new Thread(new MyThread(10),"黄牛B");
        Thread t3 = new Thread(new MyThread(20),"黄牛C");
       //启动线程
        t1.start();t2.start();t3.start();
        //main阻塞等待线程退出
        t1.join();t2.join();t3.join();
        //main

    }
}

运行结果:
在这里插入图片描述

3.3 volatile关键字
  1. 保证可见性
  2. 禁止指令重排序,建立内存屏障(即有volatile修饰的变量,这行指令禁止重排序)
  3. 不保证原子性

常见的使用场景:
一般是读写分离的操作,提供性能:

  1. 写操作不依赖共享变量,赋值是一个常量(依赖共享变量赋值不是原子性操作)
  2. 作用在读,写操作依赖其他手段(加锁)

4. 线程间通信

图示:
在这里插入图片描述

4.1 wait()方法
  • 方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法是用来将当前线程置入“预执行队列”中,并且在wait()所在的代码处停止执行,直到接到通知或被中断为止。
  • wait()方法只能在同步方法中或同步块中调用。如果调用wait()时,没有持有适当的锁,会抛出异常。
  • wait()方法执行后,当前线程释放锁,线程与其它线程竞争重新获取锁。

代码示例:

    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        synchronized (object) {
            System.out.println("等待中...");
            object.wait();
            System.out.println("等待已过...");
        }
        System.out.println("main方法结束...");
    }

运行结果:
在这里插入图片描述
执行到到object.wait()之后就会一直等待下去,直到唤醒或被中断。

4.2 wait(long timeout) 方法

让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。

代码示例:

    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        synchronized (object) {
            System.out.println("等待中...");
            object.wait(2000);
            System.out.println("等待已过...");
        }
        System.out.println("main方法结束...");
    }

运行结果:
在这里插入图片描述
执行到到object.wait()之后不会一直等待下去,等待超过给定时间或者在等待期间被唤醒就会继续向下执行。

4.3 notify()方法

notify方法就是使停止的线程继续运行:

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

notify方法只是唤醒某一个等待线程,那么如果有多个线程都在等待中,这个时候就可以使用notifyAll方法可以一次唤醒所有的等待线程。

注意:
唤醒线程不能过早,如果在还没有线程在等待中时,过早的唤醒线程,这个时候就会出现先唤醒,在等待的效果了。这样 就没有必要在去运行wait方法了。

4.5 wait 和 sleep 的差别
  • wait 之前需要请求锁,而wait执行时会先释放锁,等被唤醒时再重新请求锁。这个锁是 wait 对像上的 monitor lock
  • sleep 是无视锁的存在的,即之前请求的锁不会释放,没有锁也不会请求。
  • wait 是 Object 的方法
  • sleep 是 Thread 的静态方法
4.6 使用wait()方法和notifyAll()方法综合示例

代码:

/**
 * 模拟面包店面包的生产与消费
 */
public class BreadShop {
    /**
     * 库存
     */
    private static volatile int COUNT = 0;
    /**
     * 消费者数量
     */
    private static int CONSUMER_COUNT = 20;
    /**
     * 每次消费的个数
     */
    private static int CONSUMER_NUM = 5;
    /**
     * 生产者数量
     */
    private static int PRODUCT_COUNT = 10;
    /**
     * 生产次数
     */
    private static int PRODUCT_COUNT_PROD = 5;
    /**
     * 每次生产个数
     */
    private static int PRODUCT_NUM = 10;
    /**
     * 记录生产的数量
     */
    private static int total_prod = 0;
    /**
     * 消费者能够消费的最大数量
     */
    private static int total_cons = PRODUCT_COUNT*PRODUCT_COUNT_PROD*PRODUCT_NUM;
    //消费者
    static class Consumer implements Runnable{

        @Override
        public void run() {
            //一直消费
            try {
                while(true){
                    synchronized (BreadShop.class){
                        //库存到达下限,不能继续消费
                        if(total_cons == 0){
                            break;
                        }
                        if(COUNT < CONSUMER_NUM){//如果要消费的数量小于当前库存,则进入阻塞队列
                            BreadShop.class.wait();
                        }else{
                            //允许消费
                            COUNT-=CONSUMER_NUM;
                            total_cons-=CONSUMER_NUM;
                            System.out.println(Thread.currentThread().getName()+"消费了"+CONSUMER_NUM+"个面包,库存COUNT:"+COUNT);

                            //通知 所有通过BreadShop.class.wait()进入阻塞的线程
                            BreadShop.class.notifyAll();
                            Thread.sleep(100);
                        }
                    }


                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //生产者
    static class Productor implements Runnable{

        @Override
        public void run() {
            //一直生产
            try {
                while(true){
                    //加锁
                    synchronized (BreadShop.class){
                        if(total_prod >= PRODUCT_COUNT*PRODUCT_COUNT_PROD*PRODUCT_NUM){
                            break;
                        }
                        if(COUNT>=98){
                            BreadShop.class.wait();
                        }else{
                            COUNT+=PRODUCT_NUM;
                            total_prod+=PRODUCT_NUM;
                            System.out.println(Thread.currentThread().getName()+"生产了"+PRODUCT_NUM+"个面包,库存COUNT:"+COUNT+"总量:"+total_prod);

                            //通知 所有通过BreadShop.class.wait()进入阻塞的线程
                            BreadShop.class.notifyAll();
                            Thread.sleep(100);
                        }
                    }

                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        Thread[] cons_thread = new Thread[CONSUMER_COUNT];
        Thread[] prod_thread = new Thread[PRODUCT_COUNT];
        //创建消费者线程
        for(int i = 0;i<CONSUMER_COUNT;i++){
            cons_thread[i] = new Thread(new Consumer(),"消费者"+i);
        }
        //创建生产者线程
        for(int i = 0;i<PRODUCT_COUNT;i++){
            prod_thread[i] = new Thread(new Productor(),"生产者"+i);
        }
        //启动 消费者线程
        for (Thread t:cons_thread
             ) {
            t.start();
        }
        // 启动 生产者线程
        for (Thread t:prod_thread
             ) {
            t.start();
        }

        //main
    }
}

运行结果:
在这里插入图片描述

  • 一般在写wait操作时,推荐使用while来包裹,因为wait执行时,到被其他线程通知,当前线程被唤醒,竞争到对象锁,恢复继续执行,这段时间,判定条件可能发生改变
  • 一般不推荐使用notify(),在极端情况下,可能导致所有线程wait阻塞等待

5. 多线程案例

5.1 单例模式
5.1.1 饿汉模式

在类加载的时候就new好对象,使用时直接return
代码:

    private static Singleton instance = new Singleton();
    public Singleton() {}
    public static Singleton getInstance(){
        return instance;
    }
5.1.2 懒汉模式—单线程版

代码:

    private static Singleton instance = null;
    private Singleton() {}
    public  static Singleton getInstance(){
        if(instance == null){
            //需要对象的时候在new对象return出去
            instance = new Singleton();
        }
        return instance;
    }
5.1.3 懒汉模式—多线程版—性能低

代码:

    private static Singleton instance = null;
    private Singleton() {}
    public  synchronized static Singleton getInstance(){
        if(instance == null){
            //需要对象的时候在new对象return出去
            instance = new Singleton();
        }
        return instance;
    }
5.1.4 懒汉模式—多线程版—双重校验锁—性能高

代码:

    private static volatile Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                //加锁后再次校验
                if(instance == null){
                    instance = new Singleton(); //在初始化对象操作完成以后,其他线程进入第一个if判断,同时volatile保证可见性
                }
            }
        }
        return instance;
    }
5.2 阻塞式队列

—— 生产者消费者模型

  • 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
  • 生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
  • 这个阻塞队列就是用来给生产者和消费者解耦的。

代码实现:

public class BlockQueue<T> {
    /**
     * 循环队列 使用数组实现
     */
    private Object[] queue;
    /**
     * 存放元素的索引
     */
    private int put_index;
    /**
     * 取元素的索引
     */
    private int take_index;
    /**
     * 当前存放元素的数量
     */
    private volatile int size;


    /**
     * 线程数量
     */
    private static int THREADCOUNT = 1;

    public BlockQueue( int capacity) {
        this.queue = new Object[capacity];
    }

    //放元素
    public synchronized void put(T e) throws InterruptedException {
            while(size==queue.length){

                this.wait();
            }
            queue[put_index] = e;
            size++;
            put_index = (put_index+1)%queue.length;

            //通知其他阻塞的线程
           this.notifyAll();
    }

    //取元素
    public synchronized T take() throws InterruptedException {
        while(size==0){
            this.wait();
        }
        T t = (T) queue[take_index];
        queue[take_index] = null;
        take_index = (take_index+1)%queue.length;
        size--;
        this.notifyAll();
        return t;
    }

    public synchronized int size(){
        return this.size;
    }

    public static void main(String[] args) {
        BlockQueue<Integer> queue = new BlockQueue<>(10);
        //生产者线程
        for(int i = 0;i<THREADCOUNT;i++){
            new Thread(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    int data = 0;
                    while(true){
                        queue.put(data);
                        System.out.println(Thread.currentThread().getName()+"存放了"+data);
                        data++;
                    }
                }
            }).start();
        }
		// 消费者线程
        for(int i = 0;i<THREADCOUNT;i++){
            new Thread(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    while(true){
                        Integer ret = queue.take();
                        System.out.println(Thread
                                .currentThread().getName()+"取出了"+ret);
                    }
                }
            }).start();
        }
    }
}

运行效果(部分):
在这里插入图片描述

5.3 线程池

线程池最大的好处就是减少每次启动、销毁线程的损耗。

创建线程池代码示例:

ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
                5,   //核心线程数 --> 正式员工
                10, //最大线程数 ---> 正式员工+临时工
                60,
                TimeUnit.SECONDS, //线程的空闲时间,临时工最大的存活时间,超过时间就解雇
                new LinkedBlockingQueue<>(),// 阻塞队列,任务存放的地方
                new ThreadFactory(){

                    @Override
                    public Thread newThread(Runnable r) { // 线程池中定义的任务类r
                    	//执行传递进来的任务
                        return new Thread(r);
                    }
                }, // 创建线程的工厂类,线程池创建线程时,调用该工厂的方法创建线程
                new ThreadPoolExecutor.AbortPolicy()
                /**
                 * 拒绝策略:达到最大线程数且阻塞队列已满,采取的拒绝策略
                 * AbortPolicy: 直接抛RejectedExecutionException(不提供handler时的默认策略)
                 *  CallerRunsPolicy:谁(某个线程)交给我(线程池)任务,我拒绝执行,由谁自己执行
                 *   DiscardPolicy:交给我的任务,直接丢弃掉
                 *   DiscardOldestPolicy:丢弃阻塞队列中最旧的任务
                 */
        );// 线程池创建以后,只要有任务就自动执行


        for(int i = 0;i<20;i++){
            final int j=i;
            //线程池执行任务,execute、submit--->提交执行一个任务
            poolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(j);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

运行结果:
在这里插入图片描述

  • 快捷创建线程池的方式(不推荐):

    • 单线程池:ExecutorService pool = Executors.newSingleThreadExecutor();
    • 缓存的线程池:ExecutorService pool = Executors.newCachedThreadPool();
    • 计划任务线程池:ExecutorService pool = Executors.newScheduledThreadPool(4);
    • 固定大小线程池: ExecutorService pool = Executors.newFixedThreadPool(4);
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值