一、多线程

l 每个线程都有自己独立的一块栈空间

1.1 并发和并行

  • 并发:指两个或多个事件在同一个时间段内发生。(交替执行)
  • 并行:指两个或多个事件在同一时刻发生(同时发生)。

1.2 线程和进程

  • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

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

线程调度:

  • 分时调度
    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
  • 抢占式调度
    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
    • 设置线程的优先级
      任务管理器->详细信息->右键->设置优先级
    • 抢占式调度详解
      大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。
      实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
      其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

1.3 创建线程类

创建Thread类的子类,重写run方法 -> 线程任务

//1.创建一个Thread类的子类
public class MyThread extends Thread{
    //2.在Thread类的子类中重写Thread类中的run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i <20 ; i++) {
            System.out.println("run:"+i);
        }
    }
}
public class Demo01Thread {
    public static void main(String[] args) {
        //3.创建Thread类的子类对象
        MyThread mt=new MyThread();
        //4.调用Thread类中的start方法,开启新的线程
        mt.start();

        //主线程会继续执行主方法中的代码
        for (int i = 0; i <20 ; i++) {
            System.out.println("main:"+i);
        }
    }
}
输出结果:
run:0
main:0
main:1
run:1
main:2
run:2
main:3
run:3
.... //CPU随机选择 main线程和新线程

二、Thread方法和Runnable接口

2.1 Thread常用方法

获取线程名称

public String getName() :获取当前线程名称。

public static Thread currentThread() :返回对当前正在执行的线程对象的引用

public class MyThread extends Thread {
    @Override
    public void run() {
//        String name=getName();
//        System.out.println(name);

//        Thread t=Thread.currentThread();
//        System.out.println(t);
//        String name=t.getname();
//        System.out.println(name);

        //链式编程
        System.out.println(Thread.currentThread().getName());
    }
}
/*
    线程的名称:
        主线程:main
        新线程:Thread-0,Thread-1,Thread-2 ....
*/
public class Demo01GetThreadName {
    public static void main(String[] args) {
        //创建对象
        MyThread mt=new MyThread();
        //调用start方法开启线程
        mt.start();//Thread-0
        new MyThread().start();//Thread-1

        //获取主线程名称(只能先获取当前线程 currentThread 才能调用getName)
        System.out.println(Thread.currentThread().getName());//main
    }

设置线程名称

public Thread(String name) :分配一个指定名字的新的线程对象

public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字

public void setName(String name) : 为线程对象设置名字.

public class MyThread extends Thread{
    public MyThread(){};

    public MyThread(String name){
        super(name);//调用父类带参构造方法,使父类给其起名字 -> 007
    }

    @Override
    public void run() {
        //获取线程名称
        System.out.println(Thread.currentThread().getName());
    }
}
public class Demo01SetThreadName {
    public static void main(String[] args) {
        //开启多线程
        MyThread mt=new MyThread();
        mt.setName("001");//第一种方法
        mt.start();//001

        new MyThread("007").start();//007
    }
}

Sleep方法

public static void sleep(long millis):使当前正在执行的线程以指定的毫秒暂停

// public static void sleep(long millis):使当前正在执行的线程以指定的毫秒暂停
public class Demo01Sleep {
    public static void main(String[] args) {
        //模拟秒表
        for (int i = 1; i <=60 ; i++) {
            System.out.println(i);

            //使用sleep方法让程序睡眠一秒钟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2.2 Runnable

开启线程的第二种方法

2.3 Runnable和Thread的区别

实现Runnable接口创建多线程程序的好处:

1.避免了单继承的局限性

 一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类

 实现了Runnable接口,还可以继承其他的类,实现其他的接口

2.增强了程序的扩展性,降低了程序的耦合性(解耦)

 实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)

 实现类中,重写了run方法: 用来设置线程任务

 创建Thread类对象,调用start方法: 用来开启新线程

传递不同实现类,完成不同线程任务(接口)

2.4 匿名内部类实现

/*
      匿名内部类方法实现线程创建
*/
public class Demo01InnerClassThread {
    public static void main(String[] args) {
        //线程的父类Thread
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i <5 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+i);
                }
            }
        }.start(); //开启多线程

        //线程的接口Runnable
        //Runnable r =new RunnableImpl();//多态
        Runnable r = new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i <5 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+i);
                }
            }
        };
        new Thread(r).start();

        //简化接口的方式
        new Thread(new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i <5 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+i);
                }
            }
        }).start();
    }
}

三、线程安全

3.1 售票问题

问题背景:一家电影院三个售票窗口同时售卖 1-100张票,会出现同时卖同样的票

public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private int ticket=100;

    //设置线程任务 -> 卖票
    @Override
    public void run() {

        //使用循环让卖票重复进行
        while(true){
            if (ticket>0){

                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //票存在 -> 出售
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
            }
        }
    }
}

测试类:

/*
     模拟卖票案例:
        创建3个线程,同时开启,对共享的票进行出售
*/
public class Demo01Ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl run=new RunnableImpl();//只创建一个实现类实现共享票源
        //创建Thread对象,构造方法中传递Runnable接口的实现类对象
        Thread t0=new Thread(run);
        Thread t1=new Thread(run);
        Thread t2=new Thread(run);
        //开启三个线程
        t0.start();
        t1.start();
        t2.start();
    }
}
结果:(将ticket改为2之后)
Thread-2-->正在卖第2张票
Thread-0-->正在卖第2张票
Thread-1-->正在卖第2张票
Thread-2-->正在卖第-1张票

分析结果:

三个线程抢夺CPU执行权,谁抢到就执行

出现卖同一张票的原因

多个线程同时执行至 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); -> 此时还没有执行到ticket--;所以输出的是同一张票

出现正在卖-1张票原因

线程进入if语句判断后 然后进入睡眠 -> 例如0号线程和1号线程先结束睡眠进入执行卖票后执行

ticket--; ,此时ticket为1,两个线程都卖出第1张票后,ticket--;执行了两次变成-1;而此时2号线程也进入了循环还在睡眠中,那么他就会执行正在卖-1张票的操作

在现实操作时,我们要保证始终只有一个线程在卖票

3.2 线程同步

同步代码块

格式:

synchronized(锁对象){
         可能会出现线程安全问题的代码(访问了共享数据代码)
     }

注意:
1.通过代码块的所对象,可以使用任意的对象
2.必须保证多个线程使用的锁对象是同一个
3.锁对象作用:
把同步代码块锁住,只让一个线程在同步代码块中执行

改良卖票程序:

public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private int ticket=100;

    //创建一个锁对象
    Object obj=new Object();

    //设置线程任务 -> 卖票
    @Override
    public void run() {

        //使用循环让卖票重复进行
        while(true){
            //同步代码块
            synchronized (obj){
                if (ticket>0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //票存在 -> 出售
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                }
            }
        }
    }
}

同步锁原理

多个线程抢夺cpu执行权时,当某个线程抢夺到了执行权

执行遇到 synchronized代码块,检查发现存在锁对象,则会获取锁对象,运行代码块内容

当其他CPU进入代码发现锁对象被占用,则会进入堵塞状态,直到其他线程执行完代码块归还锁对象

同步保证了只有一个线程在同步中执行共享数据保证了安全,程序频繁的判断锁,获取锁,释放锁,程序的效率会降低。

3.3 同步方法

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

使用同步方法:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2.在方法上添加synchronized修饰符

格式:定义方法的格式
修饰符 synchronized 返回值类型 方法名(参数列表)

public synchronized void method(){
        可能会产生线程安全问题的代码
}
public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private int ticket=2;

    //设置线程任务 -> 卖票
    @Override
    public void run() {
        System.out.println("this:"+this);
        //使用循环让卖票重复进行
        while(true){
            payTicket();//调用同步方法
        }
    }

    /*
        定义一个同步方法
        同步方法也会把方法内部的代码锁住
        只让一个线程执行
        同步方法的锁对象是谁?
        就是实现类对象 new RunnableImpl()
        也就是 this (调用方法的参数即为同步锁对象)
    */
    public synchronized void payTicket(){
        if (ticket>0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //票存在 -> 出售
            System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
            ticket--;
        }
    }
}

3.4 Lock锁

第三种方法:Lock锁:
方法:
1.void lock() 获取锁
2.void unlock() 释放锁
ReentrantLock implements Lock接口

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

格式:

lock();
try{
    //可能出现问题的代码
}catch(e){
    ...
}finally{
    unlock();
}
public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private int ticket=2;

    //创建ReentrantLock对象
    Lock l = new ReentrantLock();

    @Override
    public void run() {

        while(true){
            //在 可能出现问题的代码前获取锁
            l.lock();

            if (ticket>0){
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //在会出现安全问题的代码后释放锁
                    l.unlock();
                }


            }


        }
    }
//    @Override
//    public void run() {
//
//        while(true){
//            //在 可能出现问题的代码前获取锁
//            l.lock();
//
//            if (ticket>0){
//                try {
//                    Thread.sleep(10);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//
//                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
//                ticket--;
//            }
//
//            //在会出现安全问题的代码后释放锁
//            l.unlock();
//        }
//    }
}

四、线程状态

4.1 线程状态概述

当线程被创建并启动后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,APIjava.lang.Thread.State中有6种状态:

线程状态 导致状态发生条件
NEW(新建) 线程刚被创建,但是并未启动。还没调用start方法。
Runnable(可运行) 线程可以在java虚拟机中运行的状态,可能正在运行代码,也可能没有,取决于操作系统处理器。
Blocked(锁阻塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态;
Wating(无限等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Wating状态,进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
TimedWaiting(计时等待) 同wating状态,有几个方法有超时参数,调用他们进入TimedWaiting状态,这一状态将一直保持到超时期满或者接受到唤醒通知。带有超时参数的常用方法有Thread.sleepObject.wait
Teminated(被死亡) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

4.2Timed Waiting(计时等待)

4.3 BLOCKED(锁阻塞)

4.4 Waiting(无限等待)

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

买包子和卖包子案例

package code.day06Thread.Demo10Wating;
/*
* 等待唤醒案例:线程之间的通信
*   创建一个顾客线程(消费者): 告诉老板需要什么包子,调用wait方法,放弃cpu的执行,进入到WAITING状态
*   创建一个老板线程(生产者): 花了5秒制作包子,做好包子后,调用notify方法,唤醒顾客吃包子
*
*   注意:
*       顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只有一个在执行
*       同步使用的锁对象必须保证唯一
*       只有锁对象此啊能调用wait和notify方法
*
*   Object类方法:
*       void wait()
*           在其他线程调用此对象notify()方法或者notifyAll()方法前,导致当前线程等待
*       void notify()
*           唤醒再次对象监视器上等待的单个线程
*           会继续执行wait方法之后的代码
* */
public class Demo01WaitAndNotify {
    public static void main(String[] args) {
        //创建锁对象,保证唯一
        Object obj=new Object();
        //创建顾客线程
        new Thread(){
            @Override
            public void run() {
                //一直买包子
                while(true){
                    //保证等待和唤醒的线程只能有一个执行,->同步
                    synchronized (obj){
                        System.out.println("告知老板要的包子的种类和数量");
                        //调用wait方法,放弃cpu的执行,进入到WAITING状态
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //唤醒之后执行的代码
                        System.out.println("顾客吃到了包子");
                        System.out.println("----------------------");
                    }
                }
            }
        }.start();

        //创建老板线程
        new Thread(){
            @Override
            public void run() {
                //一直卖包子
                while(true){
                    //花五秒钟做包子
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //同步
                    synchronized (obj){
                        System.out.println("老板五秒做好包子,告知顾客");
                        //notify唤醒顾客吃包子
                        obj.notify();//obj对象调用的wait方法来等待就要用obj对象调用notify来唤醒

                    }
                }
            }
        }.start();
    }
}


结果:
告知老板要的包子的种类和数量
老板五秒做好包子,告知顾客
顾客吃到了包子
----------------------
告知老板要的包子的种类和数量
老板五秒做好包子,告知顾客
顾客吃到了包子
----------------------
/*
*   进入到TimeWaiting(计时等待)有两种方式:
*       1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
*       2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
*
*   唤醒到方法:
*       void notify() 唤醒在此对象监视器上等待到单个线程
*       void notifyAll() 唤醒在此对象监视器上等待到所有线程。
*/

【线程池】

一、等待唤醒机制

1.1 线程间通信

概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)不同

比如:A线程生产包子,B线程吃包子,包子可以理解为一种资源

为什么要处理线程通信

多个线程并发执行时,在默认情况下,cpu是随机切换线程的,当我们需要多个线程共同来完成一个目标时,就需要一些协调通信,以此帮助多个线程共同操作一份数据

如何保证线程间通信有效利用资源

多个线程在操作同一份数据时,避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各线程能有效的利用资源。这种手段即 ——等待唤醒机制

1.2等待与唤醒机制

等待与唤醒机制:线程之间的通信

重点:有效的利用资源(生产一个包子,吃一个包子,再生产一个包子…..)

通信:对包子的状态进行判断

等待唤醒中的方法

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

这时该线程的状态时WAITING。它还要等着别的线程执行一个特别的动作,也就是通知(notify)在这个对象上等待的线程从wait set中释放出来,重新进入到调度队列(ready queue)中

2.notify:选取所通知对象的wait set中的一个线程释放;

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

注意: 哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。 总结如下:

  • 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
  • 否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态

注意事项

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

1.3 生产者与消费者案例

等待唤醒机制其实就是经典的“生产者与消费者”的问题。

就拿生产包子消费包子来说等待唤醒机制如何有效利用资源:

包子铺线程生产包子,吃货线程消费包子。当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。接下来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。

代码演示:

包子类

public class Baozi {
    //皮
    String pi;
    //馅
    String xian;
    //包子的状态: true  false  初始值 false
    boolean flag= false;
}

包子铺线程类

/*
*   注意:
*       包子铺线程和吃货线程关系-->通信(互斥)
*       必须使用同步技术保证两个线程只有一个在执行
*       锁对象必须保证唯一,使用包子对象作为锁对象
*       包子铺类和吃货类需要把包子对象作为参数传递进来
*           1.成员位置创建包子变量
*           2.使用带参构造,为包子变量赋值
*/
public class BaoZiPu extends Thread{
    //1.成员位置创建包子变量
    private Baozi bz;

    //2.使用带参构造,为包子变量赋值
    public BaoZiPu(Baozi bz){
        this.bz=bz;
    }

    @Override
    public void run() {
        int count=0;
        while(true){
            //同步代码块
            synchronized (bz){
                if (bz.flag==true)
                {
                    //已有包子,则等待
                    try {
                        bz.wait(); //使用锁对象调用方法
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //被唤醒之后执行,包子铺生产包子
                if (count%2==0)
                {
                    //生产 A包子
                    bz.pi="A皮";
                    bz.xian="A馅";
                }else {
                    //生产B包子
                    bz.pi="B皮";
                    bz.xian="B馅";
                }
                count++;
                System.out.println("包子铺正在生产:"+bz.pi+bz.xian+"包子.");
                //生产包子 等待3秒
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //修改包子状态
                bz.flag=true;

                //唤醒吃货线程
                bz.notify();
                System.out.println("包子铺以生产:"+bz.pi+bz.xian+"包子,顾客可以吃了");
            }
        }

    }
}

吃货线程类

public class ChiHuo extends Thread{
    private Baozi bz;

    public ChiHuo(Baozi bz){
        this.bz=bz;
    }

    @Override
    public void run() {
        while(true){
            //同步
            synchronized (bz){
                if(bz.flag==false)
                {
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //线程被唤醒之后执行的代码
                System.out.println("吃货正在吃"+bz.pi+bz.xian+"包子");
                //修改包包子状态
                bz.flag=false;


                //唤醒包子铺线程
                bz.notify();
                System.out.println("吃货吃完了"+bz.pi+bz.xian+"包子,包子铺开始生产包子");
                System.out.println("------------------------");
            }
        }
    }
}

测试类

public class Demo {
    public static void main(String[] args) {
        Baozi bz=new Baozi();

        new BaoZiPu(bz).start();

        new ChiHuo(bz).start();
    }
}

结果:
包子铺正在生产:A皮A馅包子.
包子铺以生产:A皮A馅包子,顾客可以吃了
吃货正在吃A皮A馅包子
吃货吃完了A皮A馅包子,包子铺开始生产包子
------------------------
包子铺正在生产:B皮B馅包子.
包子铺以生产:B皮B馅包子,顾客可以吃了
吃货正在吃B皮B馅包子
吃货吃完了B皮B馅包子,包子铺开始生产包子
------------------------
包子铺正在生产:A皮A馅包子.
包子铺以生产:A皮A馅包子,顾客可以吃了
吃货正在吃A皮A馅包子
吃货吃完了A皮A馅包子,包子铺开始生产包子
------------------------

二、线程池

我们使用线程的时候,就直接创建一个线程,但是会有一个问题:

如果并发的线程很多,并且每个线程都是执行一个很短的任务就结束了,这样频繁的创建线程会大大降低系统的效率,因为频繁的创建和销毁线程需要时间

而java中的线程池可以使得线程能够复用,执行完任务不被销毁而继续执行其他任务

2.1线程池概念

  • 线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

线程池:容器—->(ArrayList,HashSet,LinkedList,HashMap)

合理利用线程池能够带来三个好处:

  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

2.3线程池的使用

Java里面线程池的顶级接口是java.util.concurrent.Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService

Executors类中有个创建线程池的方法如下:

  • public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)

获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:

  • public Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行

    Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。

使用线程池中线程对象的步骤:

  1. 创建线程池对象。
  2. 创建Runnable接口子类对象。(task)
  3. 提交Runnable接口子类对象。(take task)
  4. 关闭线程池(一般不做)。
public class RunnableImpl implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"创建来一个新的线程执行");
    }
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
1. 创建线程池对象。
2. 创建Runnable接口子类对象。(task)
3. 提交Runnable接口子类对象。(take task)
4. 关闭线程池(一般不做)。
* */
public class Demo01ThreadPool {
    public static void main(String[] args) {
        //1
        ExecutorService es= Executors.newFixedThreadPool(2);

        //3
        es.submit(new RunnableImpl());
        es.submit(new RunnableImpl());
        es.submit(new RunnableImpl());
    }
}

pool-1-thread-1创建来一个新的线程执行
pool-1-thread-2创建来一个新的线程执行
pool-1-thread-1创建来一个新的线程执行
//两个线程执行三个任务