多线程详解

多线程

一、程序、进程、线程

1.1 程序

使用某种计算机语言编写的,为了完成某些功能的静态代码(app)。

例如:QQ、微信、idea、、、、、、、

1.2 进程

进行中的应用程序,计算机分配资源的最小单位

只有程序在运行状态下,才称之为进程

(1)程序执行的动态过程。
请添加图片描述

(2)关闭程序,进程结束。

(3)进程生命周期:

开始运行一直到关闭或停止运行。

一个程序可以同时运行多次,每次运行都是一个进程。

1.3 线程

一个程序可以同时运行多个任务功能。

每一个独立运行的任务流程,称为一个线程。

线程的包含在进程之中的,属于计算机运算执行的最小单位

一个进程至少包含一个线程,否则无法运行

二、多线程使用场景

2.1 多线程特点

多个任务流程可以同时执行,互不影响。

2.2 并发和并行

所有的程序都运行在CPU上。

(1)并发

早期CPU是单核的,只能同时运行一个线程。

如果要同时运行多个线程,多个线程在CPU上交替执行。

我们将多个线程在CPU上交替执行的过程,称为并发执行。

并发:是指同时发生,轮流交替执行

(2)并行

我们现在的计算都是多核,可以同时运行多个线程。

每个核心上都可以运行一个线程。

我们将多个核心同时运行多个线程的过程,称为并行执行。

并行:真正意义上的同时执行

(3)并发和并行

我们计算机现在虽然是多核的,但是并行和并发都是存在的。

比如:

我们有16个线程同时执行,但是核心只有4个。

同时并行的是4个线程。

但是每个核心上有多个线程交替并发执行。

2.3 使用场景

当程序需要多个任务流程同时执行时。

2.4 多线程的优点

(1)减少了阻塞,程序的执行效率高。

(2)提高了CPU的利用率。

(3)增强了用户体验。

(4)改善了程序的结构。

三、线程分类

3.1 用户线程

我们程序的功能流程。

主线程:我们程序执行的入口,对应的就是主方法。

我们之前写的程序都是单线程的,只有一个主线程。

一个进程,有且仅有一个主线程。

3.2 守护线程

守护线程,也叫后台线程。

比如:

jvm的垃圾回收线程。

jvm的异常处理线程。

四、多线程实现方式

4.1 继承Thread类

1.继承Thread类

2.重写run方法

3.创建线程对象

4.启动线程调用start方法

开启线程–>在线程中调用run()方法—>run执行---->run执行完毕,销毁线程

package classlib.Day0310;

/**
 * @author 35125
 */
public class MyThread extends Thread{
    @Override
    public void run() {
        //run()方法线程体
        System.out.println("start---->");
        for (int i = 0; i < 100; i++) {
            System.out.println("线程"+i);
        }
        System.out.println("end");
    }


    public static void main(String[] args) {
        //main线程 主线程

        //创建线程对象mt1
        MyThread mt1 = new MyThread();

        //调用start()方法开启线程  同时(交替)执行
        mt1.start();
      
        for (int i = 0; i < 20; i++) {
            System.out.println("main线程"+i);
        }
    }
}

4.2 实现Runnable接口

1.实现Runnable接口

2.重写run方法

3.创建线程对象

4.开启新线程(start方法)

Runnable实现类可作为参数构造Thread实例

package classlib.Day0311;

/**
 * @author 35125
 */
public class TestRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("T1  Start");
        for (int i = 0; i < 100; i++) {
            System.out.println("线程1--->"+i);
        }
    }

    public static void main(String[] args) {
        //创建线程
        TestRunnable tr = new TestRunnable();
        //开启线程
        Thread tr2 = new Thread(tr);
        tr2.start();

        for (int i = 0; i < 50; i++) {
            System.out.println("main线程-->"+i);
        }
    }
}

4.3 两种创建方式的区别

继承Thread类

​ 编写简单,可直接操作线程

​ 适用于单继承

实现Runnable接口

​ 避免单继承局限性

​ 便于共享资源

推荐使用实现Runnable接口方式创建线程

4.4 调用start和run方法的区别

因为线程执行的决定权不在线程,而在CPU,所以线程只能向CPU发出准备就绪信号,等待被执行

所以调用start方法,就是发出信号,等待CPU开启新的线程

调用run方法,不会开启新的线程

调用start开启新线程,调用run不会开启新线程

五、线程生命周期

5.1 生命周期

在这里插入图片描述

Java线程具有五种基本状态

(1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

(2)(可运行)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

(3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

(4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

3.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

(5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

举个通俗一点的例子来解释上面五种状态,比如上厕所:

你平时去商城上厕所,准备去上厕所就是新建状态(new),上厕所要排队,排队就是就绪状态(Runnable),有坑位了,轮到你了,拉屎就是运行状态(Running),你拉完屎发现没有手纸,要等待别人给你送纸过来,这个状态就是阻塞(Blocked),等你上完厕所出来,上厕所这件事情结束了就是死亡状态了。

注意:便秘也是阻塞状态,你便秘太久了,别人等不及了,把你赶走,这个就是挂起,还有一种情况,你便秘了,别人等不及了,跟你说你先出去酝酿一下,5分钟后再过来拉屎,这就是睡眠。

5.2 CPU执行原理

(1)所有可运行的线程进入队列中等待执行。

(2)CPU每次会选择其中一个线程来执行。

(3)CPU会为本次执行该线程分配一个时间片。

(4)如果时间片到期,CPU会将该线程放入队列中,该线程继续等待执行。

(5)如果时间片没有到期,运行时出现阻塞,CPU会将该线程放入阻塞队列。

(6)如果时间片没有到期,线程代码执行完毕,该线程被销毁。

注意:

(1)CPU每次选择哪个线程来执行,我们不知道,随机。

(2)CPU每次分配的时间片大小,我们不知道,随机。

因此,多线程程序每次执行结果都可能不一样!!!!

六、线程方法

6.1 设置和获取名称

        Thread tr2 = new Thread(tr);
        System.out.println(tr2.getName());
        tr2.setName("线程2");
        System.out.println(tr2.getName());
        tr2.start();

6.2 设置和获取优先级

//线程优先级默认是5   优先级越高  先被CPU执行的概率就越高
//范围是1-10
        tr2.setPriority(7);
        System.out.println(tr2.getPriority());

6.3 线程阻塞:sleep()方法

当前线程退出CPU,进入阻塞队列,n毫秒后结束阻塞,进入CPU队列中等待执行。

    @Override
    public void run() {
        System.out.println("T1  Start");
        for (int i = 0; i < 100; i++) {
            if (i == 55){
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程1--->"+i);
        }

6.4 线程阻塞:join()方法

在一个线程中,调用了另一个线程的join方法。

当前线程阻塞,直到另一个线程全部代码运行完毕,结束阻塞,才能继续执行。

package classlib.Day0311;

/**
 * @author 35125
 */
public class MyThread2 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("线程Thread2---->"+i);
        }
        System.out.println("线程2end");
    }
}

package classlib.Day0311;

/**
 * @author 35125
 */
public class MyThread3 implements Runnable{
    private Thread t2;

    public MyThread3(Thread t2) {
        this.t2 = t2;
    }

  

    @Override
    public void run() {
        try {
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 30; i++) {
            System.out.println("线程3---->"+i);
        }
        System.out.println("线程3end");
    }
}

package classlib.Day0311;

/**
 * 测试join方法
 * @author 35125
 */
public class TestMain {
    public static void main(String[] args) {

        Thread t2 = new Thread(new MyThread2());
        //线程3
        Thread t3 = new Thread(new MyThread3(t2));
        //线程2

        t2.start();
        t3.start();

    }
}

七、多线程JVM内存分配

7.1 程序计数器

一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。

“线程私有”的内存。

7.2 Java虚拟机栈

用于存储局部变量表、操作栈、动态链接、方法出口等信息。

“线程私有”的内存。

7.3 本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的native 方法服务。

7.4 Java堆

各个线程共享的内存区域。

对于大多数应用来说,Java 堆(Java Heap)是Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

7.5 方法区

方法区(Method Area)与Java 堆一样,是各个线程共享的内存区域。

它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

7.6 运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

img

多线程内存分配:

多个线程共享堆内存和方法区,但是计数器和栈是每个线程私有的。

八、线程同步

8.1 多线程同步时,访问同一对象导致数据不一致的问题

多个线程操作同一个资源对象,会出现数据紊乱。

案例:三个人同时买票,总票数为100张。

package classlib.Day0311;

/**
 * @author 35125
 */
public class TestTicket implements Runnable{
 private int tickets = 100;
    @Override
    public void run() {
        //买票
        while (true){
            if (tickets > 0){
                System.out.println(Thread.currentThread().getName()+"--->买到第"+tickets--+"号票");
            }
        }
    }

    public static void main(String[] args) {
        TestTicket tt = new TestTicket();


        new Thread(tt,"xiaobai").start();
        new Thread(tt,"xiaohuang").start();
        new Thread(tt,"laoli").start();
    }
}

8.2 线程同步

(1)同步块

将一块代码锁起来,每次只能一个线程执行。

一个线程执行完毕同步块,下一个线程才能执行。

原理:

对象锁:一个对象对应一把锁。

通过该对象锁,锁住某块代码。

哪个线程获取到这个对象锁,哪个线程就可以执行同步块中的代码。其他线程阻塞。

该线程执行完同步块代码,主动释放锁。其他线程竞争这个对象锁。

synchronized(对象名){
	//同步的代码块
}
package classlib.Day0311;

/**
 * @author 35125
 */
public class TestTicket implements Runnable{
 private int tickets = 100;
 private Object  o = new Object();
    @Override
    public void run() {
        //买票
        while (true){
             try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        synchronized (o){
                if (tickets > 0){
                    System.out.println(Thread.currentThread().getName()+"--->买到"+tickets--+"号票");
                }else{
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        TestTicket tt = new TestTicket();


        new Thread(tt,"xiaobai").start();
        new Thread(tt,"xiaowang").start();
        new Thread(tt,"laozhou").start();
    }
}

(2)同步方法

通过当前对象this的锁,锁住整个方法。

this表示调用的方法所属的对象。

哪个线程获得this锁,哪个线程就能访问该方法。

[修饰符]  synchronized   返回值类型   方法名([参数]){
	方法体:同步代码
}

注意:多个线程的this必须指向同一个对象,否则没有同步效果。

package classlib.Day0311;

/**
 * @author 35125
 */
public class TestTicket2 implements Runnable{
    private int tickets = 100;

    @Override
    public void run() {
        //买票
        while(true){
            saleTicket();
            if (tickets<=0){
                break;
            }
        }
    }

    //同步方法
    //通过当前对象this的锁,锁住整个方法
    public synchronized void saleTicket(){
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (tickets>0){
            System.out.println(Thread.currentThread().getName()+"买到了"+tickets--+"号票。");
        }
    }

    //主方法
    public static void main(String[] args) {
        TestTicket2 tt2 =new TestTicket2();

        new Thread(tt2,"xiaoming").start();
        new Thread(tt2,"xiaozhang").start();
        new Thread(tt2,"xiaowangba").start();
    }
}

(3)lock方式

jdk1.5提供的一种同步方式。

Lock接口:java.util.concurrent.locks.Lock

void | lock() 获得锁

void | unlock() 释放锁

实现类:ReentrantLock

多个线程竞争同一把锁,实现线程同步。

和同步块效果一样。

package classlib.Day0314;

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

/**
 * @author : [Charles]
 * @version : [v1.8]
 * @description : [Lock锁]
 * @createTime : [2022/3/14 10:49]
 */
public class Tickets3 implements Runnable{
    private int ticket = 100;

    //创建一把锁
    private Lock lk = new ReentrantLock(true);
    @Override
    public void run() {
        while (true){
            //获取锁
            lk.lock();
            try{
                if (ticket>0){
                    System.out.println(Thread.currentThread().getName()+"卖出的票号为:"+ticket);
                    ticket--;
                }
            }catch (Exception e){
                e.printStackTrace();
            } finally {
                
                //释放锁
                if (lk != null){
                    lk.unlock();
                }
            }
            if (ticket<=0){
                break;
            }
      }
    }

    public static void main(String[] args) {
        Tickets3 t11 = new Tickets3();

        Thread t1 = new Thread(t11);
        Thread t2 = new Thread(t11);
        Thread t3 = new Thread(t11);

        t1.start();
        t2.start();
        t3.start();
    }
}

(4)lock和同步块比较

lock更直观,直接在代码中可以看见锁。

lock可以解决同步块中出现异常释放锁的问题。

九、线程通信:死锁处理

9.1 线程同步的问题:死锁

package classlib.Day0314;



/**
 * @author : [Charles]
 * @version : [v1.8]
 * @description : [线程同步问题:死锁]
 * @createTime : [2022/3/14 11:04]
 */
public class TestThread1 implements Runnable{
    private Object a;
    private Object b;

    public TestThread1(Object a, Object b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        synchronized (a){
            System.out.println(name+"获得了a锁");
            synchronized (b){
                System.out.println(name+"获得了b锁");
                System.out.println(name+"----------------------------------");
                System.out.println(name+"释放了b锁");
            }
            System.out.println(name+"释放了a锁");
        }
    }

    public static void main(String[] args) {
        Object a = new Object();
        Object b = new Object();
        TestThread1 tt1 = new TestThread1(a,b);
        TestThread1 tt2 = new TestThread1(a,b);

        //创建线程
        Thread t1 = new Thread(tt1,"线程1");
        Thread t2 = new Thread(tt2,"线程2");

        //开启线程
        t1.start();
        t2.start();
    }
}


class MyThread2 implements Runnable{
    private Object a;
    private Object b;

    public MyThread2(Object a, Object b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        synchronized (b){
            System.out.println(name+"获得了b锁");
            synchronized (a){
                System.out.println(name+"获得了a锁");
                System.out.println(name+"++++++++++++++++++++++++++++++++");
                System.out.println(name+"释放了a锁");
            }
            System.out.println(name + "释放了b锁");
        }
    }
}

线程1:获得了a锁
线程2:获得了b锁

9.2 死锁

两个线程各有一把锁,同时继续执行又需要对方的锁,出现相互阻塞的现象。

9.3 解决方案

通过线程通信的方式,让其中一方先放弃手中的锁,让对方先用完。

对方用完之后,在通知你来用。

对象名.wait() //释放对象锁,然后线程阻塞,直到被唤醒才能继续执行。

对象名.notify() //唤醒一个等待该对象锁的阻塞线程

对象名.notifyAll() //唤醒所有等待该对象锁的阻塞线程

package classlib.Day0314;


/**
 * @author : [Charles]
 * @version : [v1.8]
 * @description : [线程同步问题:死锁]
 * @createTime : [2022/3/14 11:04]
 */
public class TestThread1 implements Runnable{
    private Object a;
    private Object b;

    public TestThread1(Object a, Object b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        synchronized (a){
            System.out.println(name+"获得了a锁");
            synchronized (b){
                System.out.println(name+"获得了b锁");
                System.out.println(name+"----------------------------------");
                System.out.println(name+"释放了b锁");

                //唤醒等待b锁的一个线程
                b.notify();
                //唤醒等待b锁的所有线程
                //b.notifyAll();
            }
            System.out.println(name+"释放了a锁");
        }
    }

    public static void main(String[] args) {
        Object a = new Object();
        Object b = new Object();

        TestThread1 tt1 = new TestThread1(a,b);
        MyThread2 tt2 = new MyThread2(a,b);

        //创建线程
        Thread t1 = new Thread(tt1,"线程1:");
        Thread t2 = new Thread(tt2,"线程2:");

        //开启线程
        t1.start();
        t2.start();
    }
}

class MyThread2 implements Runnable{
    private Object a;
    private Object b;

    public MyThread2(Object a, Object b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        synchronized (b){
            System.out.println(name+"获得了b锁");
            try {
                //释放当前线程b锁  进入阻塞状态
                //需要被唤醒 才能结束阻塞   重新进入CPU队列排队
                b.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (a){
                System.out.println(name+"获得了a锁");
                System.out.println(name+"++++++++++++++++++++++++++++++++");
                System.out.println(name+"释放了a锁");
            }
            System.out.println(name + "释放了b锁");
        }
    }
}

线程1:获得了a锁
线程2:获得了b锁
线程1:获得了b锁
线程1:----------------------------------
线程1:释放了b锁
线程1:释放了a锁
线程2:获得了a锁
线程2:++++++++++++++++++++++++++++++++
线程2:释放了a锁
线程2:释放了b锁

9.4 wait和sleep

都是线程阻塞的方法。

区别:

wait方法是Object方法;sleep方法是Thread方法。

wait方法会释放锁;sleep方法不会释放锁。

wait阻塞,需要其他线程调用notify、notifyAll方法来唤醒;sleep阻塞,到时间自动结束阻塞。

十、线程池

10.1 使用Callable实现多线程

(1)Thread 也实现了Runnable

不管是继承Thread,还是实现Runnable,都要重写run方法。

run方法的缺陷:没有返回值。

​ 不能抛出异常。

(2)Callable接口:call方法有返回值,可以声明抛出异常。

(3)实现Callable接口创建线程

第一,实现Callable

第二,实现call方法(任务代码,和run一样):这个方法有返回值,还可以声明抛出异常。

第三,创建线程对象。

第四,开启线程。

package classlib.Day0314;

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

/**
 * @author : [Charles]
 * @version : [v1.8]
 * @description : [多线程:Callable接口]
 * @createTime : [2022/3/14 14:38]
 */
public class TestCallable1 implements Callable<String> {
    @Override
    public String call() {
        for (int i = 0; i < 100; i++) {
            System.out.println("Callable接口"+i);
        }
        return "END";
    }

    public static void main(String[] args) {
        //创建任务对象
        TestCallable1 t1 = new TestCallable1();
        //FutureTask = Runnable + Future
        FutureTask<String> task = new FutureTask<>(t1);

        //创建线程对象
        Thread tt = new Thread(task);

        //启动线程
        tt.start();

        for (int i = 0; i < 100; i++) {
            System.out.println("Main"+i);
        }
    }
}

(4)Future类

用来跟踪和存储Callable执行过程和结果。

V | get() 等待计算完成,然后检索其结果。

boolean | isDone() 返回 true如果任务已完成。

(5)FutureTask

实现了Runnable和Future。

        //跟踪线程是否执行完毕
        System.out.println(task.isDone());
        //get()方法得到call方法的返回值
        System.out.println(task.get());

10.2 线程池

线程池是我们创建线程的第四种方式。

线程池是一个线程的集合,包含多个线程。

我们在启动程序时,先创建好一些线程,保存在线程池中。

当我们需要执行任务代码时,从线程池中取出一条线程,然后在这个线程中执行该任务代码。

当任务代码执行完毕时,将线程在放入线程池中。

线程池的优点:

当任务到达时,可以不需要等待线程创建就能立即执行。

降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,监控和调优。

提高响应速度。

10.3 四种线程池

使用java.util.concurrent.Executors 创建线程池。

(1)单一线程池

static ExecutorService | newSingleThreadExecutor() 创建一个使用从无界队列运行的单个工作线程的执行程序。

使用场景:多个任务顺序执行

package classlib.Day0314;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


/**
 * @author : [Charles]
 * @version : [v1.8]
 * @description : [单一线程池]
 * @createTime : [2022/3/14 15:20]
 */
public class TestPool {
    public static void main(String[] args) {
        //创建单一线程池   有且仅有一个线程
        ExecutorService es = Executors.newSingleThreadExecutor();

        //创建任务
        Task1 t1 = new Task1();
        Task1 t2 = new Task1();

        Task2 t3 = new Task2();
        Task2 t4 = new Task2();

        //执行任务
        es.execute(t1);
        es.execute(t2);

        es.submit(t3);
        es.submit(t4);

        //关闭线程池
        es.shutdown();
    }
}

class Task1 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"Runnable"+i);
        }
    }
}

class Task2 implements Callable<String>{
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"Callable"+i);
        }
        return "end";
    }
}

(2)固定线程池

static ExecutorService | newFixedThreadPool(int nThreads) 创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。 |

使用场景:同时要执行的任务数量比较多的情况,控制线程的数量。

package classlib.Day0314;

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

/**
 * @author : [Charles]
 * @version : [v1.8]
 * @description : [固定线程池]
 * @createTime : [2022/3/14 15:32]
 */
public class TestFixedPool {
    public static void main(String[] args) {
        //固定线程池:有且仅有几个线程池
        ExecutorService fixed = Executors.newFixedThreadPool(2);

        //创建任务
        Task1 t1 = new Task1();
        //Runnable
        Task2 t2 = new Task2();
        //Callable
        Task1 t3 = new Task1();
        //Runnable
        Task2 t4 = new Task2();
        //Callable

        //执行任务
        fixed.submit(t1);
        fixed.submit(t2);

        fixed.submit(t3);
        fixed.submit(t4);

        //关闭线程池
        fixed.shutdown();
    }
}

(3)可变线程池

static ExecutorService | newCachedThreadPool() 创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。 |

初始时,线程数量为0.

当有任务时,创建线程,执行任务。

执行完毕后,放回线程池。

空闲线程空闲时间大于指定时间,销毁该线程。

使用场景:同时执行的任务比较少的时候。

package classlib.Day0314;

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

/**
 * @author : [Charles]
 * @version : [v1.8]
 * @description : [可变线程池]
 * @createTime : [2022/3/14 16:11]
 */
public class TestChangePool {
    public static void main(String[] args) {
        ExecutorService es = Executors.newCachedThreadPool();

        Task1 t1 = new Task1();
        //Runnable
        Task1 t2 = new Task1();
        //Runnable
        Task2 t3 = new Task2();
        //Callable
        Task2 t4 = new Task2();
        //Callable
        
        es.submit(t1);
        es.submit(t2);
        es.submit(t3);
        es.submit(t4);

        es.shutdown();
    }
}

(4)定时线程池(任务调度线程池)

static ScheduledExecutorService | newScheduledThreadPool(int corePoolSize) 创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行。

使用场景:延迟执行,或者定期执行。

package classlib.Day0314;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author : [Charles]
 * @version : [v1.8]
 * @description : [定时线程池]
 * @createTime : [2022/3/14 16:15]
 */
public class TestScheduledPool {

    public static void main(String[] args) {
        //定时线程池
        ScheduledThreadPoolExecutor ste = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(10);

        //执行任务
        Task1 t1 = new Task1();

        //立马执行
        //ste.submit(t1);
        //定时执行
        //ste.schedule(t1,10, TimeUnit.SECONDS);
        ste.scheduleAtFixedRate(t1,10,3,TimeUnit.SECONDS);


        //关闭线程池
        //不能关闭,因为关闭了线程也就销毁了
        //ste.shutdown();
    }
}

10.4 ExecutorService的submit和execute方法区别

submit() 执行完毕后有返回值。

execute() 执行完毕后没有返回值。

10.5 四种线程池比较

img

10.6 自定义线程池

以上4种线程池,都是使用下面这个构造器创建的。

我们可以使用这个构造器来自定义线程池。

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

参数说明:

1、corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)

2、maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。

3、keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。

4、unit 存活的时间单位

5、workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。

6、threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。

7、handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vx0WrfUQ-1647257468161)(C:\Users\35125\AppData\Roaming\Typora\typora-user-images\image-20220314163656504.png)]

img

package classlib.Day0314;

import java.util.concurrent.*;

/**
 * @author : [Charles]
 * @version : [v1.8]
 * @description : [手写线程池:自定义线程池]
 * @createTime : [2022/3/14 16:43]
 */
public class TestCustomPool {
    public static void main(String[] args) {
        //自定义线程池

        //阻塞队列
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);

        ThreadPoolExecutor tpe = new ThreadPoolExecutor(2,4,3, TimeUnit.SECONDS,
                queue,
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        //使用自定义线程池

        //创建任务
        Task1 t1 = new Task1();
        Task1 t2 = new Task1();
        Task2 t3 = new Task2();
        Task2 t4 = new Task2();

        //执行任务
        tpe.execute(t1);
        tpe.execute(t2);

        tpe.submit(t3);
        tpe.submit(t4);

        //关闭线程池
        tpe.shutdown();

    }
}

避免资源耗尽的风险,生产上的推荐解法:

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
//引入依赖包,创建线程池
private ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-call-runner-%d").build();
private ExecutorService taskExe = new ThreadPoolExecutor(10,20,200L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),namedThreadFactory);

```java

*/
public class TestCustomPool {
    public static void main(String[] args) {
        //自定义线程池

        //阻塞队列
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);

        ThreadPoolExecutor tpe = new ThreadPoolExecutor(2,4,3, TimeUnit.SECONDS,
                queue,
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        //使用自定义线程池

        //创建任务
        Task1 t1 = new Task1();
        Task1 t2 = new Task1();
        Task2 t3 = new Task2();
        Task2 t4 = new Task2();

        //执行任务
        tpe.execute(t1);
        tpe.execute(t2);

        tpe.submit(t3);
        tpe.submit(t4);

        //关闭线程池
        tpe.shutdown();

    }
}

避免资源耗尽的风险,生产上的推荐解法:

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
//引入依赖包,创建线程池
private ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-call-runner-%d").build();
private ExecutorService taskExe = new ThreadPoolExecutor(10,20,200L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),namedThreadFactory);

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值