Java核心类库——多线程

多线程技术概述

线程与进程

进程

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

线程

  • 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程

  • 线程实际上是在进程基础之上的进一步划分,一个进程启动后,里面的若干执行路径又可以划分成若干个线程

线程调度

调度——CPU的分配方式(比如i7系统的CPU有8个核)

分时调度

  • 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间(1s分为几千份分配的时候人感受不到先后等待)

抢占式调度

  • 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),java使用抢占式调度
  • CPU使用抢占式调度模式在多个线程间进行着高度的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而CPU的多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。其实,多线程程度并不能提高程序的运行速度,但能提高程度运行效率,让CPU的使用率更高

线程调度排队做比同时做速度快?

同步与异步

同步:排队执行,效率低但是安全

异步:同时执行,效率高但是数据不安全

并发与并行

并发:指两个或多个事件在同一时段内发生

并行:指两个或多个事件在同一时刻发生(同时发生)

在Java中实现多线程

一个简单例子

在这里插入图片描述

public class Test {
    /**
     * 多线程技术
     */

    public static void main(String[] args) {
        //Thread
        /**
         * 汗滴禾下土是主线程
         * 锄禾日当午是分线程
         * 两个线程并发执行
         */
        MyThread m = new MyThread();
        m.start();  
        for(int i = 0;i<10;i++){
            System.out.println("汗滴禾下土"+i);
        }
    }
}

public class MyThread extends Thread {

    /**
     * run方法就是线程要执行的任务方法
     */

    @Override
    public void run() {
        //这里的代码就是一条新的执行路径
        //这个执行路径的触发方式,不是调用run方法,而是通过Thread对象的start()来启动任务
        for(int i = 0;i<10;i++){
            System.out.println("锄禾日当午"+i);
        }
    }
}

每个线程都拥有自己的栈空间,共用一份堆内存

由一个线程所调用的方法,方法也执行在线程里面

在这里插入图片描述

实现多线程的常用方法

通过实现Runnable

public class Test {
    /**
     * 多线程技术
     * 实现Runnable 与 继承Thread 相比有如下的优势:
     * 1.  通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况
     * 2.  可以避免单继承所带来的局限性
     * 3.  任务与线程本身是分离的,提高了程序的健壮性
     * 4.  后序学习的线程池技术,只接受Runnable类型的任务,而不接收Thread类型的线程
     */

    public static void main(String[] args) {
        //实现Runnable
        //1.       创建一个任务对象
        MyRunable r = new MyRunable();
        //2.       创建一个线程,并为其分配一个任务
        Thread t = new Thread(r);
        t.start();
        for(int i=0;i<10;i++){
            System.out.println("疑是地上霜"+i);
        }
    }
}

public class MyRunable implements Runnable {
    /**
     * 用于给线程执行的任务
     */

    @Override
    public void run() {
        //线程的任务
        for(int i=0;i<10;i++){
            System.out.println("窗前明月光"+i);
        }

    }
}

通过继承Thread

public class Demo2 {
    public static void main(String[] args) {
        //匿名内部类
        new Thread(){
            @Override
            public void run() {
                for(int i =0;i<10;i++){
                    System.out.println("一二三四五"+i);
                }
            }
        }.start();

        for(int i =0;i<10;i++){
            System.out.println("六七八九十"+i);
        }
    }
}

Thread类

静态变量

变量和类型字段描述
static intMAX_PRIORITY线程可以拥有的最大优先级。
static intMIN_PRIORITY线程可以拥有的最低优先级。
static intNORM_PRIORITY分配给线程的默认优先级。

常用构造方法

构造器描述
Thread()分配新的 Thread对象。
Thread(Runnable target)分配新的 Thread对象。
Thread(Runnable target, String name)分配新的 Thread对象。
Thread(String name)分配新的 Thread对象。

常用方法

量和类型方法描述
static intactiveCount()返回当前线程thread group及其子组中活动线程数的估计值。
voidcheckAccess()确定当前运行的线程是否具有修改此线程的权限。
protected Objectclone()抛出CloneNotSupportedException,因为无法有意义地克隆线程。
intcountStackFrames()不推荐使用,要删除:此API元素将在以后的版本中删除。 此调用的定义取决于suspend() ,已弃用。
static ThreadcurrentThread()返回对当前正在执行的线程对象的引用。
static voiddumpStack()将当前线程的堆栈跟踪打印到标准错误流。
static intenumerate(Thread[] tarray)将当前线程的线程组及其子组中的每个活动线程复制到指定的数组中。
static Map<Thread,StackTraceElement[]>getAllStackTraces()返回所有活动线程的堆栈跟踪映射。
ClassLoadergetContextClassLoader()返回此线程的上下文 ClassLoader
static Thread.UncaughtExceptionHandlergetDefaultUncaughtExceptionHandler()返回由于未捕获的异常而导致线程突然终止时调用的默认处理程序。
longgetId()返回此Thread的标识符。
StringgetName()返回此线程的名称。
intgetPriority()返回此线程的优先级。
StackTraceElement[]getStackTrace()返回表示此线程的堆栈转储的堆栈跟踪元素数组。
Thread.StategetState()返回此线程的状态。
ThreadGroupgetThreadGroup()返回此线程所属的线程组。
Thread.UncaughtExceptionHandlergetUncaughtExceptionHandler()返回此线程由于未捕获的异常而突然终止时调用的处理程序。
static booleanholdsLock(Object obj)当且仅当当前线程在指定对象上保存监视器锁时,返回 true
voidinterrupt()中断此线程。
static booleaninterrupted()测试当前线程是否已被中断。
booleanisAlive()测试此线程是否存活。
booleanisDaemon()测试此线程是否为守护程序线程。
booleanisInterrupted()测试此线程是否已被中断。
voidjoin()等待这个线程死亡。
voidjoin(long millis)此线程最多等待 millis毫秒。
voidjoin(long millis, int nanos)此线程最多等待 millis毫秒加上 nanos纳秒。
static voidonSpinWait()表示调用者暂时无法进展,直到其他活动发生一个或多个操作为止。
voidresume()已过时。 此方法仅适用于suspend() ,由于它易于死锁,因此已被弃用。
voidrun()如果此线程是使用单独的Runnable运行对象构造的,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回。
voidsetContextClassLoader(ClassLoader cl)为此Thread设置上下文ClassLoader。
voidsetDaemon(boolean on)将此线程标记为 daemon线程或用户线程。
static voidsetDefaultUncaughtExceptionHandler<br />(Thread.UncaughtExceptionHandler eh)设置当线程由于未捕获的异常而突然终止时调用的默认处理程序,并且没有为该线程定义其他处理程序。
voidsetName(String name)将此线程的名称更改为等于参数 name
voidsetPriority(int newPriority)更改此线程的优先级。
voidsetUncaughtExceptionHandler<br />(Thread.UncaughtExceptionHandler eh)设置当此线程由于未捕获的异常而突然终止时调用的处理程序。
static voidsleep(long millis)
假如想让程序每隔1s输出一次,那么就要设置每隔1s休眠一次
导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。
static voidsleep(long millis, int nanos)导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性。
voidstart()导致此线程开始执行; Java虚拟机调用此线程的run方法。
voidstop()已过时。 这种方法本质上是不安全的。
voidsuspend()已过时。 此方法已被弃用,因为它本身就容易出现死锁。
StringtoString()返回此线程的字符串表示形式,包括线程的名称,优先级和线程组。
static voidyield()向调度程序提示当前线程是否愿意产生其当前使用的处理器。

用户线程:自己决定自己的死亡

守护线程:当所有用户线程死亡之后自动死亡,依附于用户线程

设置和获取线程名称

public static void main(String[] args) {
    //如何获取线程名称
    System.out.println(Thread.currentThread().getName());
    new Thread(new MyRunnable(),"锄禾日当午").start();
}

static class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

//输出为:
main
锄禾日当午
public static void main(String[] args) {
    //如何获取线程名称
    System.out.println(Thread.currentThread().getName());  //获取当前正在进行的线程对象
    new Thread(new MyRunnable()).start();
    new Thread(new MyRunnable()).start();
    new Thread(new MyRunnable()).start();
}

static class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

//不指定线程名字的时候,输出为:
main
Thread-0
Thread-1
Thread-2

线程休眠

public static void main(String[] args) throws InterruptedException {
    //线程的休眠   sleep
    //指定线程的输出时间,每隔1s输出1次
    for(int i=0;i<5;i++){
        System.out.println(i);
        Thread.sleep(1000);  //1000毫秒=1秒
    }
}

线程阻塞

也称耗时操作,比较消耗时间的操作

  • 常见的有:接收用户输入;读写文件;

线程的中断

public static void main(String[] args) throws InterruptedException {
    //线程的中断
    //一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定

    Thread t1 = new Thread(new MyRunnable());
    t1.start();
    for(int i=0;i<5;i++){
        System.out.println(Thread.currentThread().getName()+":"+i);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //给线程t1添加中断标记
    t1.interrupt();

}

static class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                //e.printStackTrace();
                System.out.println("发现了中断标记,执行了直接返回操作,自行死亡");
                return;
            }
        }
    }
}

//输出为:
Thread-0:0
main:0
main:1
Thread-0:1
main:2
Thread-0:2
Thread-0:3
main:3
Thread-0:4
main:4
Thread-0:5
发现了中断标记,执行了直接返回操作,自行死亡
public static void main(String[] args) throws InterruptedException {
    //线程的中断
    //一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定

    Thread t1 = new Thread(new MyRunnable());
    t1.start();
    for(int i=0;i<5;i++){
        System.out.println(Thread.currentThread().getName()+":"+i);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //给线程t1添加中断标记
    t1.interrupt();

}

static class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                //e.printStackTrace();
                System.out.println("发现了中断标记,不执行任何操作,继续输出");
                //return;
            }
        }
    }
}

//输出为:
Thread-0:0
main:0
main:1
Thread-0:1
Thread-0:2
main:2
Thread-0:3
main:3
Thread-0:4
main:4
Thread-0:5
发现了中断标记,不执行任何操作,继续输出
Thread-0:6
Thread-0:7
Thread-0:8
Thread-0:9

守护线程

//线程: 分为守护线程和用户线程
//用户线程:当一个进程不包含任何的存活的用户线程时,进行结束
//守护线程:守护用户线程的, 当最后一个用户线程结束时,所有守护线程自动死亡

t1.setDaemon(true); //将t1设置为守护线程,当main线程结束后t1线程自动结束

线程安全问题

线程不安全的例子

public static void main(String[] args) throws InterruptedException {
    //线程不安全

    //用多态的实现Thread
    //用三个线程同时执行卖票的任务
    Runnable run = new Ticket();
    new Thread(run).start();
    new Thread(run).start();
    new Thread(run).start();

}

static class Ticket implements Runnable{
    //票数
    private int count = 10;
    @Override
    public void run() { //卖票操作
        while (count>0){
            //卖票
            System.out.println("正在准备卖票");
            try{ //制造休眠,拉成线程时间,使其出现问题
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            count--;
            System.out.println("出票成功,余票:"+count);
        }
    }
}

输出:
正在准备卖票
正在准备卖票
正在准备卖票
出票成功,余票:9
正在准备卖票
出票成功,余票:9
正在准备卖票
出票成功,余票:8
正在准备卖票
出票成功,余票:7
正在准备卖票
出票成功,余票:5
正在准备卖票
出票成功,余票:6
正在准备卖票
出票成功,余票:3
正在准备卖票
出票成功,余票:2
正在准备卖票
出票成功,余票:4
正在准备卖票
出票成功,余票:1
出票成功,余票:0
出票成功,余票:1

发现余票异常,线程不安全

线程不安全的解决方法

解决方案1-同步代码块

public static void main(String[] args) throws InterruptedException {
    //线程不安全
    //解决方案1 同步代码块
    //格式: synchronized(锁对象){   (Java中任何东西都可以作为锁存在)
    //
    //       }


    //用多态的实现Thread
    Runnable run = new Ticket();
    new Thread(run).start();
    new Thread(run).start();
    new Thread(run).start();

}

static class Ticket implements Runnable{
    //票数
    private int count = 10;
    //private Object o = new Object();  //锁放在这里时,每个线程都盯着统一把锁,可以保证安全,但是效率低
    @Override
    public void run() { //卖票操作
        Object o = new Object();  //锁放在这里表示每个线程都盯着自己的锁,也不安全
        while (true){
            synchronized (o){  //o作为锁在循环进行的时候被标记,其他线程只能等着看不被标记的时候再操作,也即排队操作
                if(count>0){
                    //卖票
                    System.out.println("正在准备卖票");
                    try{
                        Thread.sleep(1000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
                }else {
                    break;
                }

            }
        }

    }
}

解决方案2-同步方法

Ticket任务中的卖票操作抽成一个方法,加上synchronized修饰
同样使用了锁,它的锁是类名.class  Thread.class

static class Ticket implements Runnable{
    //票数
    private int count = 10;
    //private Object o = new Object();  //锁放在这里时,每个线程都盯着统一把锁,可以保证安全,但是效率低
    @Override
    public void run() { //卖票操作
        while (true){
            boolean flag = sale();
            if(!flag){
                break;
            }
        }

    }

    public synchronized boolean sale(){
        if(count>0){
            //卖票
            System.out.println("正在准备卖票");
            try{
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            count--;
            System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
            return true;
        }else {
            return false;
        }
    }
}

public static void main(String[] args) throws InterruptedException {
    
    {//用多态的实现Thread,用这种方法,是三个线程同时操作一个对象,共卖10张票
    Runnable run = new Ticket();
    new Thread(run).start();
    new Thread(run).start();
    new Thread(run).start();}
    
    {//用这种方法,三个线程操作三个类,共卖30张票
    new Thread(new Ticket()).start();
    new Thread(new Ticket()).start();
    new Thread(new Ticket()).start();}

}

解决方案3-显示锁Lock

在任务类中,创建一个Lock类的对象,执行循环的调用其lock方法标记锁,实现排队安全,if卖票语句执行结束后再调用unlock方法,释放锁

public static void main(String[] args) throws InterruptedException {
    //用多态的实现Thread
    Runnable run = new Ticket();
    new Thread(run).start();
    new Thread(run).start();
    new Thread(run).start();

}

static class Ticket implements Runnable{
    //票数
    private int count = 10;
    //显式锁
    Lock l = new ReentrantLock();
    @Override
    public void run() { //卖票操作
        while (true){
            l.lock();
                if(count>0){
                    //卖票
                    System.out.println("正在准备卖票");
                    try{
                        Thread.sleep(1000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
                }else {
                    break;
                }
            l.unlock();
        }
    }
}

显示锁和隐式锁的区别(网络整理)

隐式锁 synchronized显式锁 lock
含义不需要手动写代码去获取锁和释放锁需要手动写代码获取和释放锁,lock1.lock() lock1.unlock()
本质Java中的关键字,JVM层面的锁JDK5以后出现的类,API层面的锁
使用方法--------------------用于方法---------------------
@Override
public void run() { //操作

}
public synchronized boolean 方法名(){
需要排队的操作
}

--------------------用于语句块-------------------
private Object o = new Object();
@Override
public void run() { //操作
//o作为锁在循环进行的时候被标记
synchronized (o){
需要排队的操作
}
}
Lock l = new ReentrantLock();
@Override
public void run() { //操作
l.lock();
需要排队的操作
l.unlock();
}
等待是否可中断不可中断,除非抛出异常或者正常运行完成可以中断,中断方式:
1)调用设置超时方法tryLock(long timeout, timeUnit unit)
2) 调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断
加锁的时候是否可以公平非公平锁默认非公平,构造时传入true表示公平锁
锁绑定多个条件来condition没有,要么随机唤醒一个线程;要么时唤醒所有等待的线程用来实现分组唤醒需要唤醒的线程,可以精确的唤醒
从性能比较待补充……
使用锁的方式比较采用CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低采用乐观锁方式,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止

公平锁和不公平锁

公平锁

  • 先到先得,显式锁lock:fair参数为true 就表示是公平锁

不公平锁

  • 大家一块抢

线程死锁

c 和 p 互相在等待对方释放资源进行下一步使用,导致了死锁

public static void main(String[] args) throws InterruptedException {
    //线程死锁
    Culprit c = new Culprit();
    Police p = new Police();
    new MyThread(c,p).start();

    c.say(p);
}

static class MyThread extends Thread {
    private Culprit c;
    private Police p;

    public MyThread(Culprit c, Police p) {
        this.c = c;
        this.p = p;
    }

    @Override
    public void run() {
        p.say(c);
    }
}

static class Culprit{
    public synchronized void say(Police p){
        System.out.println("罪犯:你放了我,我放了人质");
        p.fun();
    }
    public synchronized void fun(){
        System.out.println("罪犯被放了,罪犯也放了人质");
    }
}
static class Police{
    public synchronized void say(Culprit c){
        System.out.println("警察:你放了人质,我放了你");
        c.fun();
    }
    public synchronized void fun(){
        System.out.println("警察救了人质,但罪犯跑了");
    }

}

多线程通信问题

public class Test {
    /**
     * 多线程通信问题,生产者与消费者问题
     *
     */

    public static void main(String[] args) throws InterruptedException {
        /*
        厨师做一份菜,服务员拿一份菜
        问题1  两个人协同进行的时候,发生了错乱,厨师的一整个工作没做完的时候,服务员就把菜拿走了
        问题2  加了不公平锁之后,假象A线程执行完之后,B再执行,但实际上A线程可能会连续执行,持续占用方法
        解决: A工作的时候,让B休眠,B工作的时候,让A休眠
         */
        Food f = new Food();
        Cook c = new Cook(f);
        Waiter w = new Waiter(f);

        c.start();
        w.start();
    }



    static class Cook extends Thread{
        private Food f;
        public Cook(Food f){
            this.f = f;
        }

        @Override
        public void run() {
            for(int i = 0;i<100;i++){ //厨师生成饭
                if(i%2==0){
                    f.setNameAndTaste("老干妈小米粥","香辣味");
                }else{
                    f.setNameAndTaste("煎饼果子","甜辣味");
                }
            }
        }
    }

    static class Waiter extends Thread{
        private Food f;
        public Waiter(Food f){
            this.f = f;
        }
        @Override
        public void run() {
            for(int i = 0;i<100;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    f.get(); //服务员取饭
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    static class Food{
        private String name;
        private String taste;

        //true表示可以生产
        private boolean flag = true;

        public synchronized void setNameAndTaste(String name,String taste){
            if(flag){
                this.name = name;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.taste = taste;
                flag = false;
                this.notify(); //菜做完了,唤醒服务员
                try {
                    this.wait();  //自己休眠
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }

        public synchronized void get() throws InterruptedException {
            if(!flag){
                System.out.println("服务员端走的菜的名称是:"+name+",菜的味道是:"+taste);
                flag = true;    //菜拿走了,给厨师指示可以做饭
                this.notify();  //唤醒厨师
                this.wait();    //自己休眠
            }

        }
    }
}

线程的六种状态

线程状态。线程可以处于以下状态之一:

  • NEW
    尚未启动的线程处于此状态。
  • RUNNABLE
    在Java虚拟机中执行的线程处于此状态。
  • BLOCKED
    被阻塞等待监视器锁定的线程处于此状态。
  • WAITING
    无限期等待另一个线程执行特定操作的线程处于此状态。
  • TIMED_WAITING
    正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。
  • TERMINATED
    已退出的线程处于此状态。

在这里插入图片描述

带返回值的线程Callable

  • 一种特殊的创建线程的方式

Runnable 与 Callable 的接口定义

//Callable接口
public interface Callable<v>{
    v call() throws Exception;
}

//Runnable接口
public interface Runnable{
    public abstract void run();
}

Callable使用步骤

1.  编写类实现Callable接口,实现call方法
2.  创建FutureTask对象,并传入第一步编写的Callable对象
3.  通过Thread,启动线程

public static void main(String[] args) throws InterruptedException, ExecutionException {
    Callable<Integer> c = new MyCallable();
    FutureTask<Integer> task = new FutureTask<>(c);
    //task.cancel(true);
    new Thread(task).start();
    
    //不使用task.get()函数的时候,两个线程是交替输出的
    //使用get()方法后,子线程全部输出完之后主线程再输出
    Integer j = task.get(); 
    
    System.out.println("返回值为:"+j);
    for(int i=0;i<10;i++){
        Thread.sleep(100);
        System.out.println(i);
    }

}
static class MyCallable implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {

        for(int i=0;i<10;i++){
            Thread.sleep(100);
            System.out.println(i);
        }
        return 100;
    }
}

Runnable 与 Callable 的相同点

  • 都是接口
  • 都可以编写多线程程序
  • 都采用Thread.start()启动程序

Runnable 与 Callable 的不同点

  • Runnable没有返回值;Callable可以返回执行结果
  • Callable接口的call()允许抛出异常;Runnable的run()不能抛出

Callable获取返回值

Callable接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞

线程池 Executors

概述

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间,线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源

线程池的好处

  • 降低资源消耗
  • 提高响应速度
  • 提高线程的客观理性

Java中的四种线程池 ExecutorService

1.缓存线程池

public class Test {
    /**
     * 缓存线程池
     * 长度无限制
     * 任务加入后的执行流程
     *          1.  判断线程池是否存在空闲线程
     *          2.  存在则使用
     *          3.  不存在,则创建线程 并放入线程池,然后使用
     */
    public static void main(String[] args) throws InterruptedException, ExecutionException {

        ExecutorService service = Executors.newCachedThreadPool();

        //指挥线程池执行新的任务
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"任务一");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"任务二");
            }
        });

        Thread.sleep(1000); //设置休眠,第三次任务的执行就可能用到之前使用过的但又一次空闲的线程
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"任务三");
            }
        });
    }
}
输出:
pool-1-thread-2任务一
pool-1-thread-1任务二
pool-1-thread-2任务三
    
    该程序中前两条任务被两个空闲线程(thread2,thread1)极快的打印出来,打印出来后线程休眠了1秒,导致两个被占用的线程又恢复空闲,因此第三条任务打印的时候用到的是又空闲了的thread2,而不是一直没用到的thread3

2.定长线程池

public class Test {
    /**
     * 定长线程池
     * 长度是指定的数值
     * 任务加入后的执行流程
     *          1.  判断线程池是否存在空闲线程
     *          2.  存在则使用
     *          3.  不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
     *          4.  不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
     */
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService service = Executors.newFixedThreadPool(2);
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"任务一");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"任务二");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"任务三");
            }
        });
    }
}
输出:
pool-1-thread-2任务二
pool-1-thread-1任务一
pool-1-thread-1任务三
    
    该程序中,任务一和任务二执行完都需要等待3秒,2个线程同时去执行任务一、二,因此任务一、二极快的打印出来,而任务三则在3秒后才等到空闲线程而被打印出来

3.单线程线程池

public class Test {
    /**
     * 单线程线程池
     *
     * 任务加入后的执行流程
     *          1.  判断线程池的那个线程是否空闲
     *          2.  空闲则使用
     *          3.  不空闲,则等待 池中的单个线程空闲后 使用
     */
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService service = Executors.newSingleThreadExecutor();
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"任务一");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"任务二");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"任务三");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"任务四");
            }
        });
    }
}
输出:
pool-1-thread-1任务一
pool-1-thread-1任务二
pool-1-thread-1任务三
pool-1-thread-1任务四
    
    该程序中每个任务都是由thread1执行的,原因是该线程池中只有一个线程

4.周期性任务定长线程池

public class Test {
    /**
     * 周期任务 定长线程池
     * 任务加入后的执行流程
     *          1.  判断线程池是否存在空闲线程
     *          2.  存在则使用
     *          3.  不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池,然后使用
     *          4.  不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
     *
     * 周期性任务执行时:
     *          定时执行,当某个时机触发时,自行执行某任务
     */
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
        /**
         * 1.    定时执行一次
         * 参数1: 定时执行的任务
         * 参数2: 时长数字
         * 参数3: 时长数字的时间单位,TimeUnit的常量指定
         */
        service.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务一");
            }
        },5,TimeUnit.SECONDS)
    }
}

输出:
任务一

该程序实现定时执行任务,5秒后打印了任务一

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

/**
 * 周期性执行任务
 * 参数1: 任务
 * 参数2: 延迟时长数字(隔多久开始执行)
 * 参数3: 周期时长数字
 * 参数4: 时长数字的单位
 */
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
service.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        System.out.println("任务二");
    }
},5,3,TimeUnit.SECONDS);

输出:
任务二
任务二
任务二
……
    
    该程序隔5秒开始打印任务二,每隔1秒打印一次,一直打印……

在这里插入图片描述

Lambda表达式

一些线程执行任务的例子,不同书写方法的区别

public static void main(String[] args) throws InterruptedException, ExecutionException {
    //冗余的Runnable代码
    MyRunnable r = new MyRunnable();
    Thread t = new Thread(r);
    t.start();
}

static class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("一个任务");
    }
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
    //匿名内部类的书写代码
   Thread t = new Thread(new Runnable() {
       @Override
       public void run() {
           System.out.println("一个任务");
       }
   });
   t.start();
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
    // Lambda表达式
    // 函数式编程思想
    Thread t = new Thread(() -> {
        System.out.println("一个任务");
    });
    t.start();
}

一个用常规方法调用接口中方法的例子

public class Test {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        print(new MyMath() {
            @Override
            public int sum(int x, int y) {
                return x+y;
            }
        },100,100);
        //上述步骤new了MyMath接口的对象,并重写了其sum方法,最后传入了参数100,100
        //该步骤可以用Lambda表达式进行简化
    }

    public static void print(MyMath m,int x,int y){
        int num = m.sum(x,y);
        System.out.println(num);
    }

    static interface MyMath{
        int sum(int x,int y);
    }
}

//使用Lambda表达式书写的简化形式
 print((int x, int y) -> {
       return x+y;
 },100,100);

【文章内容来源:开课吧课堂笔记】

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值