前言

在拥有多核CPU的情况下,Java的线程可以被同时调度到不同的CPU上,同时执行;这是Python线程和Java线程的区别;

单线程相当于1个工人,多线程相当于雇佣了多个工人;

1.多线程的使用场景?

1.1.我们可以让多个工人一起处理一个相同的任务

2.2.我们也可以把多个工人分成2队,协作起来完成1个共同的任务;

以上是我们使用多线程的场景,在这2种使用场景下回遇到以下两种问题、

2.多线程模式下会引发哪些问题?

2.1.同一个争抢同一资源的问题的解决方案

使用同步代码块、锁可以解决同类线程争抢同一个资源的问题

2.2.如何保证执行不同线程任务协调完成同1个任务?

如果2个执行不同任务的线程 之间,要构成生产者消费者模式 ,就需要等待唤醒通知机制(消息队列);

这就又回到多个线程争抢同1个资源的问题上,所以锁是实现线程间通信的基础。 

 

一、多线程实现方式

Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例;

每个线程其实就是指定一段代码, 让这段代码和其他线程并行执行;

我们可以通过3种方式创建线程。

 

1.继承Thread类 

我们可以通过继承java.lang.Thread类,生成线程对象;

/1.让自定义的类继承java.lang.Thread类(该自定义的类具备了Thread类的特性,Java就会认可MyThread类的对象就是一条线程)
public class MyThread extends Thread {
    //2.自定义线程的目的:让Java虚拟机开辟1个线程,和其他线程一起并行执行
    //需要让自定义的线程类去重写Thread类中的run()方法:这个run()方法就是这个线程要执行的代码


    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("MyThread的run方法循环了" + i + "次!");
        }

    }
}
自定义线程类

------------------------------------------------------------------------------------------------

  public class MyThreadTest {
    public static void main(String[] args) {
        //3.创建自定义的线程类对象
        MyThread myThread = new MyThread();
        //4.启动线程:myThread.run();  /*调用run()方法也可以运行代码√,但是没有开辟线程去运行,就是单纯地对象方法调用 */
        myThread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("Main线程的for循环了第" + (i + 1) + "次---------");
        }
    }
}
创建自定义线程类对象(执行线程)

 

2.实现Runnable接口

实现Runnable接口完成了线程和线程任务的解耦,避免了单继承的局限性。

自定义类实现Runabke接口之后创建出来线程任务对象(弹夹);

将线程任务对象作为Thread类构造方法的参数创建线程对象(枪);

特点是:线程和线程任务解耦(分开)线程是线程,线程任务是线程任务;(枪和弹夹:枪是一把枪,弹夹可以随时更换)专注于线程任务。

继承Thread类和实现Runable接口实现多线程的区别?

  • 适合多个相同程序代码的线程去共享同一资源
  • 可以避免java中单继承的局限性
  • 实现解耦

 

package com.itheima.runable;

/*
1.让自定义的类实现Runnable线程任务接口;
当自定义的实现类实现了Runnable接口,该自定义的类就具备了作为线程任务的能力
*/
public class MyRunableTask implements Runnable {

    //2.实现run()方法:指定线程要执行的任务代码
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("MyRunableTask的run方法循环了第" + i + "次");
        }
    }
}
弹夹

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

package com.itheima.runable;

public class MyRunableTest {
    public static void main(String[] args) {
        //3.创建自定义的线程任务类对象(弹夹)
        MyRunableTask myRunableTask = new MyRunableTask();
        //4.创建线程对象,将线程任务对象作为构造方法的参数(枪)
        Thread thread = new Thread(myRunableTask);
        //5.启动线程对象,调用start方法
        thread.start();
    }
}

 

3.实现Calable接口

以上两种创建线程的方式,都需要有1个run(),run方法的不足之处就是无法有返回值也无法抛出异常

如果想要获取线程任务的返回结果,可以通过实现实现Calable接口的方式创建线程对象。

package Threads;

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class TestThread03 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return new Random().nextInt(100);
    }
}

public class CreateThread03 {
    public static void main(String[] args) throws Exception {
        //定义1个线程对象
        TestThread03 tr = new TestThread03();
        FutureTask ft = new FutureTask(tr);
        Thread t3 = new Thread(ft);
        t3.start();

        //获取线程得到的返回值
        Object obj = ft.get();
        System.out.println(obj);


    }
}

4.CompletableFuture

可以多实现线程,并且可以等待所有线程执行完毕之后再往下执行;

@Override
    public IndexBaseInfoVO getBaseInfo(String beginCreateTime, String endCreateTime) {
        //1)构建一个空的结果集对象
        IndexBaseInfoVO result = new IndexBaseInfoVO();
        //2 封装结果集属性
        String username = SecurityUtils.getUsername();
        try {
            // 2.2 开始查询第一个属性 线索数量
            CompletableFuture<Integer> clueNums = CompletableFuture.supplyAsync(() -> {
                return reportMpper.getCluesNum(beginCreateTime, endCreateTime, username);
            });
            // 2.3 开始查询第一个属性 商机数量
            CompletableFuture<Integer> bussinessNum = CompletableFuture.supplyAsync(() -> {
                return reportMpper.getBusinessNum(beginCreateTime, endCreateTime, username);
            });
            // 2.4 开始查询第一个属性 合同数量
            CompletableFuture<Integer> contractNum = CompletableFuture.supplyAsync(() -> {
                return reportMpper.getContractNum(beginCreateTime, endCreateTime, username);
            });
            // 2.5 开始查询第一个属性 销售金额数量
            CompletableFuture<Double> saleAmount = CompletableFuture.supplyAsync(() -> {
                return reportMpper.getSalesAmount(beginCreateTime, endCreateTime, username);
            });
            // 3 join等待所有线程全部执行完成
            CompletableFuture.allOf(
                    clueNums, bussinessNum, contractNum, saleAmount
            ).join();
            //4 封装结果集对象
            result.setCluesNum(clueNums.get());
            result.setBusinessNum(bussinessNum.get());
            result.setContractNum(contractNum.get());
            result.setSalesAmount(saleAmount.get());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        //4 返回结果集对象
        return result;
    }

 

 

 

5.线程的生命周期

 

二、线程的常用方法

线程创建完成之后,我们可以调用线程对象中封装的一些API对线程进行操作;

1.setPriority()

我们可以通过setPriority设置线程被CPU调度的优先级。

    * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

正常优先级 为5,最小优先级为1,最大优先级为10;

package Threads;

class Task01 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("task01--" + i);
        }
    }
}

class Task02 extends Thread {
    @Override
    public void run() {
        for (int i = 20; i < 30; i++) {
            System.out.println("task02--" + i);
        }
    }
}

public class ThreadMthods {
    public static void main(String[] args) {
        Task01 t1 = new Task01();
        //设置线程1的优先级别为1
        t1.setPriority(1);
        t1.start();

        //设置线程2的优先级别为10
        Task02 t2 = new Task02();
        t1.setPriority(10);
        t2.start();
    }
}

 

2.join()

当一个线程调用了join方法之后,这个线程就会优先被执行;

当它执行结束后,CPU才可以去执行其他线程;

注意:线程必须先执行start()再执行join(),才能生效;

package Threads;


class Task03 extends Thread {
    Task03() {
    }

    Task03(String name) {
        super.setName(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.getName() + i);
        }
    }
}


public class ThreadMthods {
    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 100; i++) {
            if (i == 6) {
                Task03 t3 = new Task03("子线程");
                t3.start();
                t3.join(); //半路杀出个程咬金

            }
            Thread.currentThread().setName("main线程");
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

 

3.sleep()

通过Thread.sleep()静态方法可以使当前线程阻塞N毫秒;

/*
睡眠排序法: 
给集合中每个需要排序的数字元素(N)都开启一个对应的线程睡N毫秒。
谁最后醒来谁就是老大?
*/
public class SleepSort {
    public static void main(String[] args) {
        int[] intArray = {3, 2, 9, 7};
        for (int i : intArray) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(i);
                }
            }).start();
        }

    }
}
睡眠排序法

 

4.setDaemon(true)

在古代皇帝死了,他的妃子通常会哭的很惨,因为接下来她也要殉葬;

在Python中子线程默认就是主线程的守护线程,一旦主线程提前结束,子线程即使没有结束也要强制其结束; 

而Java中的线程是跑在不同的CPU上的,主线程和子线程的地位是平等的;

在Java中,默认情况下即使主线程结束了,子线程也可以继续执行;

但是只要我们把子线程设置为守护线程,一旦主线程结束,子线程会立即结束;

package Threads;

class Task extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("子线程" + i);
        }
    }
}


public class ThreadMthods {
    public static void main(String[] args) throws Exception {
        Task t1 = new Task();
        t1.setDaemon(true);//注意先设置守护线程,在启动该线程
        t1.start();
        //在Python中子线程默认就是主线程的守护线程,一旦主线程提前与子线程结束,子线程即使没有结束也要强制其结束;
        //而Java的线程可以跑在不同的CPU上,主线程和子线程是平等的;默认情况下即使主线程结束了,子线程也可以继续执行;
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程" + i);
        }
    }
}

 

5.stop()

我们可以通过调用stop方法,来结束当前线程;

package Threads;


public class ThreadMthods {
    public static void main(String[] args) throws Exception {
        for (int i = 1; i < 11; i++) {
            if (i == 7) {
                Thread.currentThread().stop();
            }
            System.out.println("主线程" + i);
        }
    }
}

 

三、线程安全问题-内部矛盾

我这里所说的线程安全问题即 N个执行相同任务的线程,争抢同1个资源;

例如:100个人在北京西站去抢10张北京到哈尔滨的火车票;

在Java中我们可以通过同步代码块、静态同步方法、显示锁解决这种问题;

1.synchronized ()同步代码块

package Threads;


class TicketAgency extends Thread {
    static int tickets = 10;

    TicketAgency() {
    }

    TicketAgency(String name) {
        super.setName(name);
    }

    @Override
    public void run() {
        //确保每个线程对象,使用的都是同一把锁。不要使用this
        synchronized ("zhanggen") {
            for (int i = 0; i <= 100; i++) {
                if (tickets > 0) {
                    System.out.printf("我在%s,抢到了北京到哈尔滨的第%d张票!\r\n", super.getName(), tickets--);
                }
            }
        }
    }
}


public class ThreadMthods {
    public static void main(String[] args) throws Exception {
        TicketAgency agency01 = new TicketAgency("窗口1");
        agency01.start();
        TicketAgency agency02 = new TicketAgency("窗口2");
        agency02.start();
        TicketAgency agency03 = new TicketAgency("窗口3");
        agency03.start();

    }
}

 

2.静态同步方法

public static synchronized
package Threads;


class TicketAgency extends Thread {
    static int tickets = 10;

    TicketAgency() {
    }

    TicketAgency(String name) {
        super.setName(name);
    }

    @Override
    public void run() {
        //必须确保多个线程对象,使用的都是同1把锁。不要使用this
        for (int i = 0; i <= 100; i++) {
            buyTicket();
        }

    }

    //同步方法:增加static修饰符,确保锁住的不是this
    public static synchronized void buyTicket() {
        if (tickets > 0) {
            System.out.printf("我在%s,抢到了北京到哈尔滨的第%d张票!\r\n", Thread.currentThread().getName(), tickets--);
        }
    }
}


public class ThreadMthods {
    public static void main(String[] args) throws Exception {
        TicketAgency agency01 = new TicketAgency("窗口1");
        agency01.start();
        TicketAgency agency02 = new TicketAgency("窗口2");
        agency02.start();
        TicketAgency agency03 = new TicketAgency("窗口3");
        agency03.start();

    }
}

 

3.Lock类

synchronized是Java中的关键字,这个关键字的识别是依靠JVM来识别完成的,是虚拟机级别的;

在JKD1.5之后我们可以通过API级别的Lock显示锁(自己lock+unlock)实现同步,这种方式更加灵活。

package Threads;


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class TicketAgency extends Thread {
    static int tickets = 10;
    //拿来一把锁
    Lock lock = new ReentrantLock(); //多态 接口=实现类

    TicketAgency() {
    }

    TicketAgency(String name) {
        super.setName(name);
    }

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            lock.lock();
            if (tickets > 0) {
                System.out.printf("我在%s,抢到了北京到哈尔滨的第%d张票!\r\n", Thread.currentThread().getName(), tickets--);
            }
            lock.unlock();

        }

    }

}


public class ThreadMthods {
    public static void main(String[] args) throws Exception {
        TicketAgency agency01 = new TicketAgency("窗口1");
        agency01.start();
        TicketAgency agency02 = new TicketAgency("窗口2");
        agency02.start();
        TicketAgency agency03 = new TicketAgency("窗口3");
        agency03.start();

    }
}

 

 

四、线程通信问题

线程通信问题即生产者/消费者模型下,如何保证两端执行不同任务的多个线程协调一致,完成共同的任务;

 

1.生产者消费者模型

 

生产者:包子铺生产包子

消费者:消费者吃包子

 

生产者和消费者模型的要求:

  • 消费者没有包子可以消费时,等待生产者生产出包子再消费。
  • 生产者生产出包子之后,通知生产者来消费,避免生产过剩。
  • 到达按指定顺序依次执行线程的目的

 

2.死锁的产生

在生产者消费者模型中,当两个线程都持有对方线程所需要的锁,但本线程也不释放对方想要的锁时,两个线程全部进入死锁状态。

package com.itheima.lock;

public class DeadLockDemo2 {
    //死锁的前提:有2把以上的锁
    public static final Object LOCK_A = new Object();
    public static final Object LOCK_B = new Object();

    public static void main(String[] args) {
        //创建2个线程:
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "准备获取LOCK_A锁!");
                synchronized (LOCK_A) {
                    System.out.println(Thread.currentThread().getName() + "已经获取了LOCK_A锁,准备休眠100毫秒之后,再获取LOCK_B锁!");
                    try {
                        //线程休眠期间:让出CPU执行权,但是还在不会释放锁。
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "休眠100毫秒完成,开始获取LOCK_B锁!");
                    synchronized (LOCK_B) {
                        System.out.println(Thread.currentThread().getName() + "即获取了LOCK_A锁,也获取到了LOCK_B锁!");
                    }
                }

            }
        }, "线程A").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "准备获取LOCK_B锁");
                synchronized (LOCK_B) {
                    System.out.println(Thread.currentThread().getName() + "已经获取到了,LOCK_B锁,准备休眠100毫秒之后,再获取LOCK_A锁");
                    try {
                        //线程休眠期间:让出CPU执行权,但是不会释放锁。
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "休眠100毫秒完成,开始获取LOCK_A锁!");
                    synchronized (LOCK_A) {
                        System.out.println(Thread.currentThread().getName() + "即获取了LOCK_B锁,也获取到了LOCK_A锁!");
                    }
                }
            }
        }, "线程B").start();
    }
}

/*
死锁:两个线程都持有对方线程所需要的锁,但本线程也不释放对方想要的锁。

* 情况1:线程A先执行
* 线程A拿到第一把锁(LOCK_A)
*线程A进入休眠状态让出CPU执行权,但没有释放LOCK_A锁。
*
* 线程B开始执行
* 线程B获取到第一把锁(LOCK_B)
* 线程B进入休眠状态让出CPU执行权,但没有释放LOCK_B锁。
*
* 线程A休眠完成,开始获取第二把锁(LOCK_B),此时线程B还在休眠中无法释放LOCK_B;
*线程B休眠完成,开始获取第二把锁(LOCK_A),此时线程A还在等待线程B放LOCK_A,
* 线程A等待线程B只能等待线程
* */
死锁

 

3.等待唤醒机制(wait、notify、notifyAll )

Java的元类即Object类提供了3个方法,可以帮助我们实现等待唤醒机制,以解决死锁问题。

  • wait()方法:让调用者运行的线程进入无限等待状态。
  • notify()方法:随机唤醒1个进入无限等待状态的线程。
  • notifyAll()方法:唤醒所有进入无限等待状态的线程。

注意:

  • 以上的方法必须在同步代码块和同步方法中才能使用;
  • 锁对象内部包含一个等待区,锁对象只能唤醒 自己等待区中线程。
  • 如果线程获得锁成功之后,该线程就会沿着wait()方法之后的代码继续执行
  • wait()和notify()必须放在synchronized代码块或者由synchronized修饰的同步方法内; 
package com.itheima.pc2;

import java.util.Objects;

//包子类
public class Baozi {
    //标识包子的编号
    private int count;
    //表示有没有包子的成员
    private boolean flag;

    public Baozi() {
    }

    public Baozi(int count, boolean flag) {
        this.count = count;
        this.flag = flag;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Baozi)) return false;
        Baozi baoZi = (Baozi) o;
        return getCount() == baoZi.getCount() &&
                isFlag() == baoZi.isFlag();
    }

    @Override
    public int hashCode() {
        return Objects.hash(getCount(), isFlag());
    }
}
包子

--------------------------------------------

package com.itheima.pc2;

public class Producer implements Runnable {
    //
    private Baozi baozi;

    public Producer(Baozi baozi) {
        this.baozi = baozi;
    }

    @Override
    public void run() {
//        System.out.println("生产者线程启动了,包子共享对象是"+this.baozi);
        while (true) {
            //这里之所以选择Baozi作为锁对象是因为生产者和消费者拿到的是同一个包子
            synchronized (baozi) {
                try {
                    if (baozi.isFlag()) {
                        //如果执行到这里,说明当前有包子,需要被消费者消费,那么生产者就不要生产了
                        baozi.wait();
                    } else {
                        //执行到这来说明没有包子
                        baozi.setCount(baozi.getCount() + 1);
                        System.out.println(Thread.currentThread().getName() + "生产了第" + baozi.getCount() + "个线程!");
                        baozi.setFlag(true);
                        baozi.notifyAll();//唤醒消费者,前来消费
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }
}
生产者

--------------------------------------------

package com.itheima.pc2;

public class Consumer implements Runnable {
    private Baozi baozi;

    public Consumer(Baozi baozi) {
        this.baozi = baozi;
    }

    @Override
    public void run() {
//        System.out.println("消费线程启动了,包子共享对象是"+this.baozi);
        while (true) {
            //这里之所以选择Baozi作为锁对象是因为生产者和消费者拿到的是同一个包子
            synchronized (baozi) {
                //判断是否有包子可以消费
                try {
                    if (!baozi.isFlag()) {
                        //如果没有包子消费者进入无限等待状态
                        baozi.wait();
                    } else {
                        //消费者需要消费包子
                        System.out.println("消费者消费了第" + baozi.getCount() + "个包子。");
                        baozi.setFlag(false);
                        baozi.notifyAll();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();

                }
            }
        }
    }
}
消费者

--------------------------------------------

package com.itheima.pc2;

public class Test {
    public static void main(String[] args) {
        //创建包子对象并且初始化
        Baozi baozi = new Baozi(0, false);
        //创建消费者和生产者的消费任务,将同一个包子对象作为参数传递;
        Producer producer = new Producer(baozi);
        Consumer consumer = new Consumer(baozi);
        //创建生产者和消费者
        new Thread(producer,"生产者线程").start();
        new Thread(consumer,"消费者线程").start();
    }
}
Test.java

 

4.await

以上我们只是完成1个生产者线程和1个消费者线程之间1对1的顺序通信;

在JDK1.5之后我们可以使用await()和signal(),实现多个消费者线程 和多个生产者线程之间,多对多通信的顺序通信;

package zhanggen.com;


import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//商品类
public class Product {
    //品牌
    private String band;
    //名字
    private String name;

    //生产和消费指示灯:0正在生产,1可消费
    boolean flag = false;

    //声明1把Lock锁
    Lock lock = new ReentrantLock();
    //生产者的等待队列
    Condition producersCondition = lock.newCondition();
    //消费者的等待队列
    Condition consumersCondition = lock.newCondition();

    public Product() {
    }

    //set和get方法
    public Product(String band, String name) {
        this.band = band;
        this.name = name;
    }


    public String getBand() {
        return band;
    }

    public String getName() {
        return name;
    }

    public void setBand(String band) {
        this.band = band;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void make(int i) {
        //如果消费线程正在消费,生产线程进入生产者的等待池等待
        lock.lock();
        try {
            if (flag == true) {
                try {
                    //生产线程进入生产者的等待队列
                    producersCondition.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (i % 2 == 0) {
                this.setBand("巧克力");
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                this.setName("费列罗");


            } else {
                this.setBand("啤酒");
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                this.setName("哈尔滨");

            }
            System.out.printf("生产者生产出%s,品牌为%s\r\n", this.getBand(), this.getName());
            flag = true;
            // 唤醒消费队列中1个消费线程,来消费。
            consumersCondition.signal();
        } finally {
            lock.unlock();
        }
    }


    public void cost() {
        lock.lock();

        try {
            if (flag == false) {
                try {
                    //消费线程进入消费者的等待队列
                    consumersCondition.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            System.out.printf("消费者消费了%s,品牌为%s\n", this.getBand(), this.getName());
            flag = false;
            //唤醒生产者队列中的1个生产线程,去生产
            producersCondition.signal();

        } finally {
            lock.unlock();
        }
    }

}

 

五、线程池

线程是属于操作系统地宝贵资源,频繁地创建和销毁线程, 会降低程序执行,浪费系统资源,所以需要线程池;

线程池是负责管理多个固定线程的容器,我们可以从线程池中获取线程,程执行完任务可以返回到线程中,继续等待使用。

线程池可以完成对线程资源的管理和复用;

 

1.有界阻塞队

有界阻塞队列:指定了指定具体长度/容量的队列,例如 ArrayBlockingQueue

 

有界阻塞队列特点:

  • 提供了take和put这两个阻塞方法
  • 当有界阻塞队列塞满时,还继续往队列中put数据,队列会一直阻塞,直到数据成功take走为止:
  • 当有界阻塞队列中没有数据可以take是,还继续从队列中take数据,队列会一直阻塞,直到数据成功向队列中put数据之后:

 

应用场景

有界阻塞队列可在线程池中保存未及时处理的线程任务:

当线程池中核心线程和临时线程都处于工作状态时,多余的线程任务会被保存到有界阻塞队列中,当线程空闲了可以执行任务了,会自动从有界阻塞队列中take线程任务执行

 

2.无界阻塞队列 

无界阻塞队列:没有知道具体长度/容量的队列,例如: LinkedBlockingQueue

 

无界阻塞队列特点:

  • 不需要在创建的时候指定队列的长度/容量,
  • 只要想存储可以一直存储
  • 也提供了take和put阻塞方法

 

应用场景

无界阻塞队列可在线程池中保存未及时处理的线程任务:

使用无界阻塞队列,有可能出现任务的堆积,造成内存溢出

package com.itheima.pool;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;

public class PoolDemo3 {
    public static void main(String[] args) throws InterruptedException {
        /*
        *
        *
        *有界阻塞队列:指定了指定具体长度/容量的队列,例如 ArrayBlockingQueue
        * 阻塞队列:BlockingQueue:具有阻塞功能的队列
        * 特点:提供了take和put这两个阻塞方法
        *当有界阻塞队列塞满时,还继续往队列中put数据,队列会一直阻塞,直到数据成功take走为止:
        *当有界阻塞队列中没有数据可以take是,还继续从队列中take数据,队列会一直阻塞,直到数据成功向队列中put数据之后:

        *有界阻塞队列可在线程池中保存未及时处理的线程任务:
        *当线程池中核心线程和临时线程都处于工作状态时,多余的线程任务会被保存到有界阻塞队列中,当线程空闲了可以执行任务了,会自动从有界阻塞队列中take线程任务执行;
        *
        *
        * 无界阻塞队列:没有知道具体长度/容量的队列,例如: LinkedBlockingQueue
        * 特点:不需要在创建的时候指定队列的长度/容量,只要想存储可以一直存储,也提供了take和put阻塞方法
        *
        *
        */
        LinkedBlockingQueue<Integer> strBlockingQueue=new LinkedBlockingQueue<>();


        ArrayBlockingQueue<Integer> numberBlockingQueue=new ArrayBlockingQueue<>(1);
        numberBlockingQueue.put(10);
        System.out.println("添加了一个数据之后阻塞队列的内容是:"+numberBlockingQueue);
        Integer taked = numberBlockingQueue.take();
        System.out.println("从阻塞队列中获取了一个数据:"+taked);
        numberBlockingQueue.put(20);

    }
}
有/无界阻塞队列

 

3.使用Executors创建线程池

使用Executors工具类可以快速创建1个线程池,但是可配置性低,适用性低 ;

package com.itheima.pool;

import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class PoolDemo1 {
    public static void main(String[] args) {
        //Executors.newFixedThreadPool():获取一个具备指定数量线程的线程池
        ExecutorService executorService =Executors.newFixedThreadPool(10);

        //提交线程任务的方式
        for (int i = 0; i < 15; i++) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名称为"+Thread.currentThread().getName()+"的线程执行了线程任务run()方法");
                }
            });

        }
        //当线程池创建完成之后,默认处于阻塞状态,一直等待提交线程任务
        //关闭线程池:处理完所有线程任务之后
//        executorService.shutdown();
        //立即关闭线程池(没有处理完的线程任务会封装到一个List单列集合中返回)
        List<Runnable> runnables = executorService.shutdownNow();
        System.out.println("没有执行完的线程任务有"+ runnables.size());
    }
}
View Code

 

4.自定义线程池配置参数说明

通过ThreadPoolExecutor的构造方法可以自定义1个线程池,以下是ThreadPoolExecutor() 构造方法的7个配置参数说明

corePoolSize
核心数数量,建议:IO密集型任务:当前机器CPU核心数 * 2 | CPU密集型任务:当前机器核心数+1
 
maximumPoolSize
 
最大线程数=核心线程数+临时线程数,用于指定临时线程数量。
 
keepAliveTime
 
临时线程最大存活时间
 
TimeUnit unit
 
临时线程最大存活时间的时间单位,例如TimeUnit.SECONDS
 
BlockingQueue<Runnable> workQueue
 
用于存储核心线程+临时线程,暂时无法处理的线程任务;
如果想设置线程池处理任务的阈值:使用有界阻塞队列,线程池可处理任务=核心线程数+临时线程数+阻塞队列的长度,
如果不想设置阈值:使用无界阻塞队列,有可能出现任务的堆积,造成内存溢出。
 
ThreadFactory
 
线程工厂,线程池中的线程从哪里来,可以使用指定的线程工厂来创建。一般固定写法Executors.defaultThreadFactory()
 
RejectedExecutionHandler
 
默认拒绝处理策略:当线程任务数量 >(大于) 最大线程数量+阻塞队列长度,就会触发默认拒绝策略。
ThreadPoolExecutor.AbortPolicy:一旦线程池无法处理提交的线程任务立即抛出异常(运行期异常)
DiscardPolicy: 一旦线程池无法处理提交的线程任务不会抛出异常,丢弃多余任务(丢弃多余任务)
DiscardOldestPolicy:一旦线程池无法处理提交的线程任务,用无法处理的任务替换阻塞队伍中等待时间最长的任务
CallerRunsPolicy():一旦线程池无法处理提交的线程任务,让当前线程(main线程)处理。

 

5.自定义线程池

创建1个线程池如下:

  • 核心线程数为10
  • 临时线程数为10
  • 临时线程10秒内部工作就销毁
  • 有界阻塞队列为5
  • 默认拒绝策略为AbortPolicy;

使当前线程池最大能支持同时处理25个线程;

package com.itheima.pool;
//自定义线程池,避免资源耗尽

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class PoolDemo2 {
    public static void main(String[] args) {
        /*
         *ThreadPoolExecutor构造方法参数说明
         *corePoolSize:核心数数量,建议:IO密集型任务:当前机器CPU核心数 * 2 | CPU密集型任务:当前机器核心数+1
         *maximumPoolSize:最大线程数=核心线程数+临时线程数,用于指定临时线程数量。
         *keepAliveTime:临时线程最大存活时间
         *TimeUnit unit:临时线程最大存活时间的时间单位,例如TimeUnit.SECONDS
         *BlockingQueue<Runnable> workQueue:用于存储核心线程+临时线程,暂时无法处理的线程任务;
         * 如果想设置线程池处理任务的阈值:使用有界阻塞队列,线程池可处理任务=核心线程数+临时线程数+阻塞队列的长度,
         * 如果不想设置阈值:使用无界阻塞队列,有可能出现任务的堆积,造成内存溢出。
         *ThreadFactory:线程工厂,线程池中的线程从哪里来,可以使用指定的线程工厂来创建。一般固定写法Executors.defaultThreadFactory()
         *RejectedExecutionHandler:默认拒绝处理策略:当线程任务数量 >(大于) 最大线程数量+阻塞队列长度,就会触发默认拒绝策略。
         * ThreadPoolExecutor.AbortPolicy:一旦线程池无法处理提交的线程任务立即抛出异常(运行期异常)
         *DiscardPolicy:                   一旦线程池无法处理提交的线程任务不会抛出异常,丢弃多余任务(丢弃多余任务)
         *DiscardOldestPolicy:一旦线程池无法处理提交的线程任务,用无法处理的任务替换阻塞队伍中等待时间最长的任务
         *CallerRunsPolicy():一旦线程池无法处理提交的线程任务,让当前线程(main线程)处理。
         * */
        ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 20, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        //以上创建1个线程池:核心线程数为10 临时线程数为10 ,临时线程10秒内部工作就销毁,有界阻塞队列为5,默认拒绝策略为AbortPolicy
        //当前线程池最大能支持同时处理25个线程
        for (int i = 0; i <= 25; i++) {
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程名称为" + Thread.currentThread().getName() + "的线程执行了线程任务run()方法");
                }
            });
        }
    }
}
自定义1个线程池

 

 

 

参考

posted on 2021-11-24 15:56  Martin8866  阅读(97)  评论(0编辑  收藏  举报