掌握多线程的用法一篇就够了

线程与进程

进程

是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运

行多 个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个

进程从创 建、运行到消亡的过程。

线程

线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个

进程 中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程。

主线程:执行java的主(main)方法的线程

单线程程序:java程序中只有一个线程执行从main方法开始,从上到下依次执行,JVM执行main方法,main方

法会进入到栈内存,JVM会找操作系统开辟一条main方法通向cpu的执行路径,cpu就可以通过这个路径来执

行main方法,而这个路径有一个名字,叫main(主)线程

线程调度

分时调度 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

抢占式调度 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性)Java使用的为抢占式调度。

速度问题

进程只有一个,所以分配的CPU资源是一定的,多线程只不过是轮流抢占CPU而已,并不会真正提高处理速度 。意味着,多线程的作用主要在于提高了并发数量。多线程轮换使用CPU,造成单个线程的执行速度变慢。在时间片轮转的时候,频繁切换线程也会造成一定时间浪费在多CPU的服务器上,能提高并发数量,还能提高处理速度,因为其他线程可以被分配到其他的cpu身上。

创建线程的方式

在微观的角度来看main线程和另外的线程线程在抢cpu的执行权

每一个执行权就是一个时间片
每一个时间片是比较短的但是jvm的执行速度是比较快的

所以一个时间片内就会打印出多个数据

线程的启动就是调用start方法start方法会去调用当前Thread对象的run()方法
使用Runnable接口的形式创建的线程会传入target属性这个就是被调用run方法的线程对象

Thread的run()方法会判断是否执行target的run方法

用户线程:独立线程主线程结束之后当前线程依然会继续执行直到结束
守护线程:如果当前线程是某一个线程守护线程会随着那个线程的结束而结束

第一种:继承Thread类

1.继承Thread类
2.重写run()方法 run方法中的内容就是你要这个线程去执行的任务

3.创建子类对象(即创建了线程对象)调用start()方法启动run任务

//线程类
public class MyThread extends Thread {
//定义指定线程名称的构造方法
public MyThread(String name) {
//调用父类的String参数的构造方法,指定线程的名称
super(name);
  }
/**
* 重写run方法,完成该线程执行的逻辑
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在执行!"+i);
   }
  }
}


//测试类
public class Demo01 {
public static void main(String[] args) {
//创建自定义线程对象
MyThread mt = new MyThread("新的线程!");
//开启新线程
mt.start();
//在主方法中执行for循环
for (int i = 0; i < 10; i++) {
System.out.println("main线程!"+i);
   }
  }
}
原理分析

程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。

多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。

第二种:实现Runnable接口的形式

1.实现Runnable接口

2.实现run()方法run方法中的内容就是你要这个线程去执行的任务

3.创建子类对象通过子类对象去创建Thread类对象,使Thread 为这个任务分配线程

4.调用Thread类对象的start方法

public class UseRunnable {
public static void main(String[] args) {
System.out.println(1);
//注意,这里new的是Thread
new Thread(new Task()).start();
System.out.println(3);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
   }
System.out.println(4);
}
static class Task implements Runnable{
public void run() {
System.out.println(2);
    }
  }
}

第三种:有返回值的线程

1.创建任务: 创建类实现Callable接口
2.使用FutureTask获取Callable的返回值
3.使用Thread 为这个任务分配线程
4.调用线程的start方法

public class testThread {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<>(new Task());
        new Thread(futureTask).start();
        System.out.println("阻塞方法前面");
        int result = futureTask.get();
        System.out.println(result);
        System.out.println("阻塞方法后面");

    }
    static class Task implements Callable<Integer> {
        public Integer call() throws Exception {
            Thread.sleep(2000);
            return 10;
        }
    }
}

futureTask.get(); 这是一个阻塞的方法,意思就是,这个方法会一直等,主线程会一直等待,这个线

程执行完成之后并有了返回值,才会继续执行。

Thread类的方法

静态方法:
currentThread:获取当前正在运行的线程
yield():让步,让出当前的执行权然后当前线程依旧会去抢执行权

sleep(long mills):让当前线程进入睡眠,睡眠mills毫秒,是进入了阻塞状态

普通方法:
start():启动当前线程

interrupt():发起中断请求,请求中断当前线程,只有当线程进入阻塞状态时就会中断

isAlive():检测当前线程是否存活
setPriority():设置优先级 ,10最高,1最低,5为默认的,优先级高,不是绝对会先执行

setName():设置当前线程的名字
getName:获取当前线程的名字

join():等待当前线程死亡,等待当前这个线程执行完,会使调用这个方法的线程进入阻塞状态

setDaemon():设置当前线程是否为守护线程
getstate():获取当前线程的状态

Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runnable接口的话,则很容易的实现资源共享.
总结:实现Runnable接口比继承Thread类所具有的优势:
1.适合多个相同的程序代码的线程去共享同一个资源。

2.可以避免java中的单继承的局限性。
3.增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。

4.线程池只能放入实现Runnable或Callable类线程,不能直接放入继承Thread的类。

线程安全问题

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的

结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

举例

电影院要卖票,我们模拟电影院的卖票过程。假本次电影的座位共100个。 模拟电影院的售票窗口,实现多个窗口同时卖

票(多个窗口一起卖这100张票) ,采用线程对象来模拟,需要票,Runnable接口子类来模拟。

测试代码

class Ticket implements Runnable {
    private int ticket = 100;
    /*
     * 执行卖票操作
     */
    @Override
    public void run() {
     //每个窗口卖票的操作
     //窗口 永远开启
        while (true) {
            if (ticket > 0) {//有票 可以卖
     //出票操作
     //使用sleep模拟一下出票时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
      //获取当前线程对象的名字
                System.out.println(Thread.currentThread().getName() + "正在卖:" + ticket--);
            }
        }
    }
}
public class demo {
    public static void main(String[] args) {
        //创建线程任务对象
        Ticket ticket = new Ticket();
        //创建三个窗口对象
        Thread t1 = new Thread(ticket, "窗口1");
        Thread t2 = new Thread(ticket, "窗口2");
        Thread t3 = new Thread(ticket, "窗口3");
        //同时卖票
        t1.start();
        t2.start();
        t3.start();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dBYF5qhM-1687834326994)(E:\myapp\typora\笔记文件夹\java第二阶段\imgs\电影卖票安全问题.png)]

发现程序出现了问题:相同的票数,被卖了三回,几个窗口(线程)票数不同步了,这种问题称为线程不安全

线程安全问题分析

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而

无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线

程同步,否则的话就可能影响线程安全。

案例解决办法分析

窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3

才有机会进入代码去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待

修改完毕同步之后,才能去抢夺CPU 资源,完成对应的操作,保证了数据的同步性,解决了线程不安全

的现象。

为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。 那么怎么去使用呢?

有三种方式完成同步操作

方案一:使用同步代码块

同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥

访问。

格式:

synchronized(同步锁){
需要同步操作的代码
}

同步锁: 对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

  1. 锁对象 可以是任意类型。

  2. 多个线程对象 要使用同一把锁。

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着

(BLOCKED)。

测试代码
 class MyRunnable implements Runnable{
        //定义一个多个线程共享的票源
        private int ticket = 100;
        //创建一个锁对象
        Object obj = new Object();
        //设置线程任务:卖票
        @Override
        public void run() {
         //使用死循环,让卖票操作重复执行
            while(true){
                //同步代码块
                synchronized (obj){
                  //先判断票是否存在
                    if(ticket>0){
                 //提高安全问题出现的概率,让程序睡眠
                        try {
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                       //票存在,卖票 ticket--
                        System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                                ticket--;
                    }
                }
                try {
                  //睡眠一下让其他窗口有机会买票
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public class TicketTest2 {
        //测试类:
        public static void main(String[] args) {
           //创建线程任务对象
            MyRunnable ticket = new MyRunnable();
            //创建三个窗口对象
            Thread t1 = new Thread(ticket, "窗口1");
            Thread t2 = new Thread(ticket, "窗口2");
            Thread t3 = new Thread(ticket, "窗口3");
            //同时卖票
            t1.start();
            t2.start();
            t3.start();
        }
    }

运行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-thLvzRUZ-1687834326995)(E:\myapp\typora\笔记文件夹\java第二阶段\imgs-2\窗口卖票.png)]

同步锁是谁? 对于非static方法,同步锁就是this。对于static方法,我们使用当前方法所在类的字
节码对象(类名.class)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W9QZz5AD-1687834326996)(E:\myapp\typora\笔记文件夹\java第二阶段\imgs\同步锁.png)]

使用同步方法代码如下:

package com.ownStudy;
public class wayTest implements Runnable{
    private int ticket = 100;
    /*
     * 执行卖票操作
     */
    @Override
    public void run() {
       //每个窗口卖票的操作
       //窗口 永远开启
        while(true){
            sellTicket();
        }
    }
    /**
     * 锁对象 是 谁调用这个方法 就是谁
     * 隐含 锁对象 就是 this
     */
    public synchronized void sellTicket(){
             if(ticket>0){//有票 可以卖
            //出票操作
            //使用sleep模拟一下出票时间
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //获取当前线程对象的名字
            System.out.println(Thread.currentThread().getName()+"正在卖:"+ticket--);
        }
    }
}

代码块内的内容只能有一个线程进入

获取到o这个同步锁的线程进入
没有获取到锁的线程会进入阻塞状态

直到获取锁后再运行
sleep方法会进入阻塞状态不会释放锁
wait方法也是进图阻塞状态会释放当前线程持有的锁

wait方法
wait方法是0bject提供的方法
wait方法是使用同步锁对象的wait方法

wait方法可以不填时间,表示无限等待

notify方法
notify也是0bject提供的方法

随机唤醒一个wait的当前对象

notifyAll唤醒所有的wait对象

需要使用获取到了锁对象的线程去唤醒

方案二:使用同步方法

public synchronized void ticket(){}

这个方法就表示只有一个线程能去调用这个方法

成员同步方法的锁对象是当前对象
静态同步方法的锁对象是当前类对象

public synchronized void method(){
//可能会产生线程安全问题的代码
}

同步方法

同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在

方法外等着。

方案三:使用LOCk锁

java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定

操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

Lock锁也称同步锁,加锁与释放锁方法化了,如下:

public void lock() :加同步锁。

public void unlock() :释放同步锁

使用步骤

1.在成员位置创建一个ReentrantLock对象
2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁

死锁

死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线

程被无限期地阻塞,因此程序不可能正常终止。

Java 死锁产生的四个必要条件:

1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用

2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

3、请求和保持,即当资源请求者在请求其他资源的同时保持对原有资源的占有。

4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。下面用Java代码来模拟一下死锁的产生。解决死锁问题的方法是一种是用synchronized,一种是用Lock显式锁实现。

线程状态

1.出生,创建new 线程对象被创建时处于这个状态
2.就绪,线程对象调用start方式时的状态,可以被执行态

3.运行,cpu调度当前线程
4.阻塞, 因为获取不到锁或者调用sleep, wait, join等方法进入等待状态

5.销毁,线程执行完任务后自我毁灭

线程状态概述

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生

命周期中, 有几种状态呢?在API中 java.lang.Thread.State 这个枚举中给出了六种线程状态:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wJpHP5Ym-1687834326996)(E:\myapp\typora\笔记文件夹\java第二阶段\imgs\六种线程状态.png)]

Timed Waiting(计时等待)

Timed Waiting在API中的描述为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。

其实当我们调用了sleep方法之后,当前执行的线程就进入到“休眠状态”,其实就是所谓的Timed Waiting(计时等待)。

举例

实现一个计数器,计数到100,在每个数字之间暂停1秒,每隔10个数字输出一个字符串。

public class MyThread extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
if ((i) % 10 == 0) {
  System.out.println("‐‐‐‐‐‐‐" + i);
}
System.out.print(i);
try {
Thread.sleep(1000);
System.out.print(" 线程睡眠1秒!\n");
} catch (InterruptedException e) {
e.printStackTrace();
     }
   }
}
public static void main(String[] args) {
new MyThread().start();
   }
}

Sleep方法总结:

  1. 进入 TIMED_WAITING 状态的一种常见情形是调用的 sleep 方法,单独的线程也可以调用,不一定非要有协作关系。

  2. 为了让其他线程有机会执行,可以将Thread.sleep()的调用放到线程run()之内。这样才能保证该线程执行过程 中会睡眠

  3. sleep与锁无关,线程睡眠到期自动苏醒,并返回到Runnable(可运行)状态。

  4. sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始立刻执行。

BLOCKED(锁阻塞)

Blocked状态:一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态。

线程A与线程B代码中使用同一锁,如果线程A获 取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。

Waiting(无限等待)

Wating状态:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。

测试代码

public class waitingTest {
    public static Object obj = new Object();
    public static void main(String[] args) {
        // 演示waiting
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                  //有写while (true)一直执行,所以第二次 obj.wait(); 时候一直在等待
                    //唤醒线程只执行一次,第二次不会使其唤醒。
                    synchronized (obj){
                        try {
                            System.out.println( Thread.currentThread().getName()
                                    +"=== 获取到锁对象,调用wait方法,进入waiting状态,释放锁对象");
                            obj.wait(); //无限等待
                         //obj.wait(5000); //计时等待, 5秒 时间到,自动醒来
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println( Thread.currentThread().getName() +
                                "=== 从waiting状态醒来,获取到锁对象,继续执行了");
                    }
                }
            }
        },"等待线程").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
              //没有写while (true)只执行一次
                try {
                    System.out.println( Thread.currentThread().getName() +"‐‐‐‐‐ 等待3秒钟");
                            Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj){
                    System.out.println( Thread.currentThread().getName() +
                    "‐‐‐‐‐ 获取到锁对象,调用notify方法,释放锁对象");
                            obj.notify();
                }
            }
        },"唤醒线程").start();
    }
}

上述代码向我们阐述了:

一个调用了某个对象的 Object.wait 方法的线程会等待另一个线程调用此对象的Object.notify()方法 或Object.notifyAll()方法。

其实waiting状态并不是一个线程的操作,它体现的是多个线程间的通信,可以理解为多个线程之间的协作关系,

多个线程会争取锁,同时相互之间又存在协作关系。

当多个线程协作时,比如A,B线程,如果A线程在Runnable(可运行)状态中调用了wait()方法,

那么A线程就进入了Waiting(无限等待)状态,同时失去了同步锁。

假如这个时候B线程获取到了同步锁,在运行状态中调用了 notify()方法,那么就会将无限等待的A线程唤醒。

注意是唤醒,如果获取到锁对象,那么A线程唤醒后就进入 Runnable(可运行)状态;

如果没有获取锁对象,那么就进入到Blocked(锁阻塞状态)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jbvf5MnO-1687834326996)(E:\myapp\typora\笔记文件夹\java第二阶段\imgs\线程状态转换图.png)]

线程通信

为什么要处理线程间通信: 多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多

个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此

来帮我们达到多线程共同操作一份数据。

等待唤醒机制

如何保证线程间通信有效利用资源: 多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮

助解决线程之间对同一个变量的使用或操作。 就 是多个线程在操作同一份数据时, 避免对同一共享变

量的争夺。也就是我们需要通过一定的手段使各个线程能有效 的利用资源。而这种手段即—— 等待唤醒

机制。

等待唤醒机制:这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),

比如去争夺锁,但这并不是 故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你

们可能存在在晋升时的竞争,但更多时 候你们更多是一起合作以完成某些任务。

就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码

过后 再将 其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的

等待线程。

wait/notify 就是线程间的一种协作机制

等待唤醒中的方法

等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:

  1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争

​ 锁了,这时 的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知

​ (notify)”在这个对象 上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中

  1. notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先 入座。

  2. notifyAll:则释放所通知对象的 wait set上的全部线程。

注意: 哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同

步块内,而 此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功

后才能在当初调 用 wait 方法之后的地方恢复执行。

调用wait和notify方法需要注意的细节

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对

    ​ 象调用的wait方法后的线程。

  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继 承了Object类的。

  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

    加法器和减法器
    i到0时减法器,wait 无限等待,唤醒加法器

i到10时,加法器wait,无限等待,唤醒减法器

加法器减法器示例代码

import  com.Utils.ThreadUtils;

public class WaitTest {
    private static int num = 1;
    private static final Object MONITOR = new Object();
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true){
                minus();
            }

        });
        Thread t2 = new Thread(() -> {
                while (true){
                    plus();
                }
        });
        t1.start();
        t2.start();
    }
    public static void minus(){
        synchronized (MONITOR){
            if(num <= 0) {
                ThreadUtils.sleep(1000);
                MONITOR.notify();
            }if (num>0){
                num--;
                ThreadUtils.sleep(100);
                System.out.println("减法器--"+Thread.currentThread().getName()+":\t"+num);
            }else {
                try {
                    MONITOR.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void plus(){
        synchronized (MONITOR){
            if(num >=10) {
                ThreadUtils.sleep(1000);
                MONITOR.notify();
            }if (num<10){
                num++;
                ThreadUtils.sleep(100);
                System.out.println("加法器--"+Thread.currentThread().getName()+":\t"+num);
            }else {
                try {
                    MONITOR.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

线程池

为什么要使用线程池?

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

(2) 提高响应速度。 当任务到达时,任务可以不需要等到线程创建就能立即执行。

(3) 提高线程的可管理性。 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统

的稳定性,使用线程池可以进行统一的分配、调优和监控。

jdk自带的四种线程池

newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行。

newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工 作线程来执行任务,保证所有任务按照指定顺序执行。

ThreadPoolExecutor

无论是哪一个创建线程池的方法都是调用ThreadPoolExecutor 构造方法,只是所使用到的参数不同,所以出现了不同的几个方法。

public ThreadPoolExecutor
(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

参数的意义

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kbiZm0Dz-1687834326997)(E:\myapp\typora\笔记文件夹\java第二阶段\imgs\线程池创建的参数.png)]

线程池提供了四种拒绝策略

AbortPolicy:直接抛出异常,默认策略;

CallerRunsPolicy:用调用者所在的线程来执行任务;

DiscardOldestPolicy:丢弃阻塞队列中最靠前的任务,并执行当前任务;

DiscardPolicy:直接丢弃任务;

线程池测试代码

package com.ownStudy;

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

public class poolTest {
    public static void main(String[] args) {
         // 创建线程池对象
        ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
         // 创建Runnable实例对象
        MyRunnable2 r = new MyRunnable2();
         // 从线程池中获取线程对象,然后调用MyRunnable中的run()
        service.submit(r);
         // 再获取个线程对象,调用MyRunnable中的run()
        service.submit(r);
         // 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。
         // 将使用完的线程又归还到了线程池中
         // 关闭线程池
         service.shutdown();
    }
}
 class MyRunnable2 implements Runnable {
    @Override
    public void run() {
        System.out.println("我要一个教练");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("教练来了: " + Thread.currentThread().getName());
        System.out.println("教我游泳,交完后,教练回到了游泳池");
    }
}

在java中,每次程序运行至少启动2个线程
1.main线程
2.gc线程垃圾回收

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yjg_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值