一文了解java线程

线程

概述

进程是程序的基本执行实体
线程是操作系统能够运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。也是软件中互相独立,也可以同时运行的功能

image.png

image.png

应用场景

软件的耗时操作,拷贝,迁移大文件,加载大量的资源文件(背景动图,音乐,加载栏),后台服务器

只要你想让多个事情同时运行就需要多线程

作用

  1. 有了多线程,我们可以让程序同时做多件事情
  2. 提高执行效率,比如正在拷贝文件,不能干其他事,这时就可以加入多线程,这样就可以边拷贝边执行其他任务

并发与并行

并发:在同一时间,多个指令在单个cpu上交替运行
并行: 在同一时间,多个指令在多个cpu上同时运行

并发并行.gif

多线程的实现方式

  1. 继承Thread类的方式进行实现
  2. 实现Runnable接口的方式进行实现
  3. 利用Callable接口和Future接口实现

1. 继承Thread类

image.png|1275

2. 实现Runnable接口

当前执行的线程对象: 可以通过currentThread()来获取

image.png|1450

3. 利用Callable与Future接口

image.png|500

image.png

实现线程3种方式的区别

  1. 第一种线程的方式比较简单,直接定义一个类继承Thread类,重写run()方法里面放任务,再测试类里创建该类的对象,调用方法start()即可 ,缺点是Java只能单继承,所以第一种就不能再继承类了
  2. 第二种是将创建的类实现Runable接口,在重写的run()方法里放线程任务,在测试类里创建任务类对象,将该对象传入新创建的线程类里,线程类再调用其start()方法,这个可以继承类
  3. 第三种很复杂,但可以获得返回值,首先创建一个类实现Callable接口对返回的值进行泛型限制,里面重写call()方法 ,里面放着线程任务,在测试类里创建任务对象,再创建FuterTasker类的对象,将任务对象传入 ,再创建Thread类对象,将FuterTasker对象传入,再调用start()方法开始执行线程,最后再获取返回值通过FuterTasker类的对象.get()方法
    image.png

构造器与方法

这里Runable参数是任务对象

image.png

  1. 默认线程优先级是5,最小是1,最大是10,优先级越大,抢占cpu越高
  2. 守护线程也被称为备胎线程

image.png

1. 方法setname与构造器

image.png

如果没有给线程设置名字,那么线程会有个初始名Thread-X(0->无线) ,当然也可以让继承类创建有参构造器

image.png

这里继承关系,不能继承父类的构造器,所以得创建本类的有参构造器

image.png

2. 方法currentThread

image.png

  1. 谁调用了这个getName方法,就是谁
    1. 当然main是一个线程,一般都是main运行代码
  2. 当JVM虚拟机启动后,会自动的启动多条线程,其中一条就是main线程,他的作用就是调用main方法,执行里面的代码
// 2. 测试currentThread方法  
Thread m = Thread.currentThread();  
 String name = m.getName();  
 System.out.println(name); //main

3. sleep

image.png

  1. 哪条线程执行到了这个方法,哪条线程就会在这里停留对应的时间
  2. 方法的参数:
  3. 表示睡眠的时间,单位毫秒 1s=1000毫秒

这里System.out.println("你好");main线程不一定最后运行 ,但可强制睡眠来改变顺序,就算释放了线程资源,这还得看程序执行的速度
image.png

4 . 线程的调度等级

4.1线程的调度

分为 抢占式调度 和 非抢占式调度
抢占式调度: 多个线程抢夺系统cpu的执行权,cpu在什么时候执行哪个线程也是不确定的,执行时间也是不确定的,所以充满随机性
非抢占式调度: 多个线程轮流执行,每个执行的时间都差不多

image.png|400

4.2线程的等级

优先级越大,抢占的cpu的执行权就越强

image.png

这里通过Ctrl+Alt+SHift+12,查看到Thread的源码里的常量最大优先级为10,最小为0,默认为5
image.png|575

线程的执行顺序不是绝对受线程优先级影响
image.png

5. 守护线程Daemon

守护线程也叫备胎线程当其他非守护线程执行完毕后,守护线程会陆续结束(可能任务执行完,可能没执行完),一般用在比如杀毒软件正在杀毒,用户结束了这个软件,杀毒进程被迫停止 ,而杀毒线程就是守护线程,软件就是正常线程;还比如打王者的过程中水晶破碎,其他英雄的动作进程在那一刻也会加载几秒,结束进程

image.png

image.png

6. 出让线程yield

出让线程,也被礼貌线程,尽可能平均调用CPU

image.png

7. 插让线程

也被称为插入线程,插队线程,设置后将当前线程插入到所在位置的线程之前运行结束

package com.itheima.pack4All_Method.p4;  
public class Test {  
    public static void main(String[] args) throws InterruptedException {  
  
        MyThread t1 = new MyThread("汽车");  
  
        //执行线程  
        t1.start();  
         //将线程t1插入main线程之前  
         t1.join();  
  
        //这里是main线程  
        for (int i = 0; i < 10; i++) {  
            System.out.println("@main线程: " + i);  
        }  
    }  
}
package com.itheima.pack4All_Method.p4;  
public class MyThread extends Thread {  
  
/*这里构造方法不能被继承,所以MyThread类没有MyThread(name)这个构造器,  
    所以需要创建这个构造器  
*/  
    public MyThread(String name) {  
        super(name);  
    }  
  
    //线程任务是从输出1-100  
    @Override  
    public void run() {  
        for (int i = 0; i <10; i++) {  
            System.out.println( "@"+getName()+": "+i);  
        }  
    }  
}

image.png|525

注意

1. 子类抛出异常

父类Thread类没有抛出异常,所以子类不能在run方法内抛出异常,所以在里面的出现的所有异常,只能捕获异常

image.png

线程的生命周期

人生的各种状态
image.png|350

线程的状态

  1. 创建线程对象,执行start方法,这样线程就有了执行资格,变为就绪状态,开始和其他线程抢cpu执行权,如果抢到了,则执行对应的任务,完毕后线程死亡,一直这样重复,直到线程任务完全结束。 如果中途没抢到,就为等待状态
  2. 如果有执行权,有执行资格,当遇到睡眠方法sleep( ),那么就会立马停止运行,会先去就绪状态等待抢夺资源,继续操作剩下的代码

image.png|800

线程代码模板

image.png|725

线程的安全问题

这里卖票会有异常,比如:线程1先进入睡眠了一会,线程2也进来睡了一会,线程3也睡了一会,这时线程1醒来开始执行ticket++刚结束 ,这时线程2也醒了, 抢占了cpu的执行权,也 开始票数自加ticket++了,线程2继续执行在卖 第2张票,这时线程1又抢回了cpu的执行权,也开始卖第2张票,这时线程3醒了后也抢到了cpu的执行权,这时开始自加ticket++ ,刚完事,这时线程1睡眠休息醒来了,又抢了cpu的执行权,这时执行了ticket++ ,开始卖第4张票,这时线程3抢回了cpu的执行权,开始卖第5张票..................................

而对于getName()的调用者和后面字段 正在卖第"+(i+1)+"张票"拼接一定是不匹配的毕竟cpu的执行权随时有可能被其他线程抢走

image.png

package com.itheima.pack4;  
  
public class MyThread extends Thread {  
    // Thread的构造器不能被继承,所以自己设置个,好传线程名  
    public MyThread(String name) {  
        super(name);  
    }  
  
    /*这里卖票会有异常,getName()的调用者和后面字段正在卖第"+(i+1)+"张票"不匹配,  
       毕竟线程执行的顺序是随机性的  */    //int count = 0; //定义票数 这里这样定义会出错,相当于每个对象都有一份票变量  
static  int count = 0; //随MyThread类创建,加载一次count,全局只有一份  
  
    @Override  
    public void run() {  
  
        while (true) {  
  
            //让卖票效果更真实,这里停顿100ms  
            try {  
                Thread.sleep(100);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
  
            if (count<100) {  
                count++;  
                System.out.println(getName() + "正在卖第" + count + "张票");  
  
            }else {  
                break;  
            }  
        }  
    }  
}
package com.itheima.pack4;  
//测试线程安全的问题  
public class Test {  

    public static void main(String[] args) throws InterruptedException {  
        //    1. 创建3个售票员小张,小芳,小刚  
        MyThread t1 = new MyThread("小张");  
        MyThread t2 = new MyThread("小芳");  
        MyThread t3 = new MyThread("小刚");  
  
    //    2. 执行这些线程看结果  
        t1.start();  
        t2.start();  
        t3.start();  
    }  
}

image.png|475

1. 线程的上锁(同步代码块)

将执行的代码进行上锁,就算其他线程抢到了cpu控制权,也不能进入厕所,只有等里面线程上完厕所,才能把门打开,后面等待的线程才能上厕所
线程上锁.gif

格式与特点

锁对象可以是任意任何类型 ,但得保证锁对象是唯一的, 可以定义对象时前面加个 static 静态修饰(一般都是写 本类的字节码文件对象MyThread.class) ,为什么要这样呢,你可以这样想若锁对象有多个,那么每个线程在查看不同的锁状态上厕所,反而引发异常,当上锁对象1其默认是开锁的,假如线程1进入上厕所,那么该锁对象1为关闭状态,而锁对象2是开启状态,这时线程2也会上厕所,这就引发数据异常。

image.png

这里需要注意每次上厕所上锁(同步代码块)在外排队的需要sleep睡一会 ,不然就是一个人一直卖票

package com.itheima.pack4;  
  
public class MyThread extends Thread {  
    // Thread的构造器不能被继承,所以自己设置个,好传线程名  
    public MyThread(String name) {  
        super(name);  
    }  
    /*这里卖票会有异常,getName()的调用者和后面字段正在卖第"+(i+1)+"张票"不匹配,  
       毕竟线程执行的顺序是随机性的  */    //int count = 0; //定义票数 这里这样定义会出错,相当于每个对象都有一份票变量  
  
    //随MyThread类创建,加载一次count,全局只有一份  

static  int count = 0;  
//    定义唯一的锁对象  
//static  Object o = new Object();  
    @Override  
    public void run() {  
  
        while (true) {  
            try {  
                Thread.sleep(100);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            synchronized (MyThread.class) {  
                //让卖票效果更真实,这里停顿100ms  
                try {  
                    Thread.sleep(100);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                if (count<100) {  
                    count++;  
                    System.out.println(getName() + "正在卖第" + count + "张票");  
  
                }else {  
                    break;  
                }  
            }  
            System.out.println("hello,world");
        }  
    }  
}
package com.itheima.pack4;  
  
//测试线程安全的问题  
public class Test {  
    public static void main(String[] args) throws InterruptedException {  
  
        //    1. 创建3个售票员小张,小芳,小刚  
        MyThread t1 = new MyThread("小张");  
        MyThread t2 = new MyThread("小芳");  
        MyThread t3 = new MyThread("小刚");  
  
    //    2. 执行这些线程看结果  
        t1.start();  
        t2.start();  
        t3.start();  
    }  
}

image.png

2. 同步方法

image.png
将 同步代码块进行抽取成同步方法,这里共享变量不用设置静态,毕竟只会创建一次myRunable对象
image.png

这里的同步块方法是非静态,所以该锁对象是this ,全程指的是MyRunable对象

package com.itheima.同步方法;  
  
import com.itheima.pack4.MyThread;  
  
//这里使用线程第二个方式  
public class MyRunable  implements  Runnable{  
  
   //1.卖票,这里票数必须是唯一的  
  int count = 0;  
    @Override  
    public void run() {  
  
     //   循环卖票  
     while (true){  
             while (true) {  
                 if (method()) break;  
             }  
         }  
     }  
  
    //设置同步方法  
    private synchronized boolean method() {  
        if (count == 100){  
            return true;  
        }  
        count++;  
        System.out.println(Thread.currentThread().getName()+"正在卖第"+count+"张票");  
        return false;    }  
}
package com.itheima.同步方法;  
  
public class Test {  
    public static void main(String[] args) {  
  
        //创建任务对象  
        MyRunable myRunable = new MyRunable();  
  
        //创建线程对象  
        Thread t1 = new Thread(myRunable);  
        Thread t2 = new Thread(myRunable);  
        Thread t3 = new Thread(myRunable);  
  
        t1.setName("小张");  
        t2.setName("小王");  
        t3.setName("小李");  
  
        t1.start();  
        t2.start();  
        t3.start();  
  
    }  
}

线程安全StringBuilder与StringBuffer

StringBuilder的源码比StringBuffer一样,但StringBuffer类的方法前面都声明了synchronized,但StringBuilder没StringBuffer线程安全

image.png

3. Lock锁

image.png|525

  1. 这里lock锁,需注意如果是直接继承了Thread的方式,在测试类创建了了多个线程,那么创建锁对象得保证唯一性,前面用static修饰,static Lock lock = new ReentrantLock()关于共用的数据在多线程面前也得保证其唯一性,设置静态 static int count = 0;
  2. 上锁就得关锁,不然其他线程一直会在门口等待,陷入死循环
  3. 当然可以不用写两遍关闭lock锁 ,直接try catch()finally(){这里关锁 lock.unlock(); } finally区域,代码一定会执行到

这个方式不建议

package com.itheima.lock锁;  
  
import java.util.concurrent.locks.Lock;  
import java.util.concurrent.locks.ReentrantLock;  
  
public class MyThread extends Thread {  
  
    static int count = 0;//因为测试类创建了两个对象,那么count为了保证只有一份,设置为静态  
    //创建锁对象,这里测试类创建了多个个线程类,所以锁对象不唯一了,加上static  
   static Lock lock = new ReentrantLock();  
  
    //这里使用Lock锁,来保护线程数据问题  
    @Override  
    public void run() {  
        //卖票  
        while (true) {  
  
            //上锁  
            lock.lock();  
  
            /*这里的if在线程1=100时进入后先上锁后判断是否是100,这时会跳出while循环,而线程2,线程3还在第18行继续等待,  
                所以得在退出的时候解开锁 ,不然死循环*/  
            if (count == 100) {  
                lock.unlock();  
                break;            } else {  
                count++;  
                System.out.println(getName() + "正在卖第" +count+ "张票");  
            }  
            //解锁  
            lock.unlock();  
        }  
    }  
}

这种方式更好

package com.itheima.lock锁;  
  
import java.util.concurrent.locks.Lock;  
import java.util.concurrent.locks.ReentrantLock;  
  
public class MyThread extends Thread {  
  
    static int count = 0;//因为测试类创建了两个对象,那么count为了保证只有一份,设置为静态  
    //创建锁对象,这里测试类创建了多个个线程类,所以锁对象不唯一了,加上static  
   static Lock lock = new ReentrantLock();  
  
    //这里使用Lock锁,来保护线程数据问题  
    @Override  
    public void run() {  
        //卖票  
        while (true) {  
  
            //上锁  
            lock.lock();  
  
            try {  
            /*这里的if在线程1=100时进入后先上锁后判断是否是100,这时会跳出while循环,而线程2,线程3还在第18行继续等待,  
                所以得在退出的时候解开锁 ,不然死循环*/  
                if (count == 100) {  
  
                    break;  
                } else {  
                    count++;  
                    System.out.println(getName() + "正在卖第" +count+ "张票");  
                }  
            } catch (Exception e) {  
                e.printStackTrace();  
            } finally {  
                //解锁  
                lock.unlock();  
            }  
        }  
    }  
}
package com.itheima.lock锁;  
  
public class Test {  
    public static void main(String[] args) {  
  
  
  
        //     创建多个线程  
        MyThread t1 = new MyThread();  
        MyThread t2 = new MyThread();  
        MyThread t3 = new MyThread();  
  
  
        t1.setName("小张");  
        t2.setName("小李");  
        t3.setName("小赵");  
  
        t1.start();  
        t2.start();  
        t3.start();  
  
    }  
}

4. 死锁

当出现锁的嵌套,就可能出现死锁 ,所以避免嵌套锁
image.png|825

这里 线程1假如先抢到cpu执行权,那么会先将A锁拿到,进行锁起,这时线程2抢到cpu执行权,他开始拿B锁,进行锁起;
这时候线程1会卡死在15行,因为线程1拿不到b锁,等着线程2释放,但线程2等着线程1释放A锁,才能释放B锁,这时候就死锁了

image.png

生产者和消费者(等待唤醒机制)

多个线程执行,会发生随机性,如果碰到等待唤醒机制,会让多个线程轮流执行

理想状态

生产者查看桌子没有面条,就立马做一碗面条,吃货看到桌子上有面条就去吃,吃完后就等着厨师做,厨师又看到桌子上没食物就去做,吃货看到有就去吃........................

等待唤醒机制的理想状态.gif

生产者和消费者等待

消费者等待:吃货先抢到cpu的执行权,但这时桌子没有食物,则进入等待状态(wait) ,这时厨师就抢到cpu的执行权,看到桌子上没有食物,则开始做食物,等食物上桌子就唤醒( notify)吃货,吃货这时就开吃,吃完就唤醒厨师做饭
生产者等待: 生产者做好面条后,放在桌子上,看到桌子仍然有食物,就会进入等待状态,这时cpu执行权就会被吃货抢走,这时吃货看到桌子有食物就会开吃,然后又发现桌子没食物又开始进入等待状态,这时cpu的执行权就会被厨师抢走,他发现桌子没食物,就开始做食物。然后唤醒吃货开吃

image.png

线程方法

这里notify随机唤醒单个线程,一般使用notifyAll唤醒所有线程
image.png

吃饭例子

  1. 定义吃货只能吃十次,每吃一次,将桌子饭的状态置为0,让吃货次数-1次, 让吃货等待,让厨师上岗,唤醒吃货;
  2. 厨师看桌子上没有饭则开始做饭,将桌子饭的状态置为1,让厨师等待,让吃货开吃,唤醒厨师开做
package com.itheima.等待唤醒实现;  
  
//食物类  
public class Foodie extends Thread {  
    public Foodie(String name) {  
        super(name);  
    }  
  
    //创建吃货名字构造器  
  
    @Override  
    public void run() {  
        //    1.循环  
        //    2.同步代码块  
        //    3. 判断共享数据是否到达末尾,到达末尾怎么办  
        //    4. 判断共享数据是否到达末尾,没到达末尾怎么办(执行核心代码)  
  
        while (true) {  
            //这里同步锁,锁多个食客同时吃  
            synchronized (Desk.lock) {  
                //当10个食物都被吃完了就退出  
                if (Desk.count == 0) {  
                    break;  
                } else {  
                    //表示桌子上没食物  
                    if (Desk.foodFlag == 0) {  
  
                        //    没有食物吃货就等待,这时cpu执行权会被厨师抢走  
                        try {  
                            Desk.lock.wait();//让当前线程跟锁进行绑定  
                        } catch (InterruptedException e) {  
                            e.printStackTrace();  
                        }  
                        Desk.lock.notifyAll();//唤醒所有该锁的所有线程,这里唤醒的是吃货,让他去抢cpu执行权去吃饭  
  
                        //如果食物还有就吃  
                    } else {  
                        Desk.count--;//食物-1  
                        System.out.println(getName() + "在吃食物,还能吃" + Desk.count + "个食物");  
                        //   这里只要吃货吃了一碗,厨师就开始做  
                        Desk.lock.notifyAll(); //唤醒厨师 ,在上面代码说明厨师进入等待了,当吃货吃完就唤醒厨师做饭  
                        Desk.foodFlag = 0;//吃完食物,将食物状态变成0  
                    }  
                }  
            }  
        }  
    }  
}
package com.itheima.等待唤醒实现;  
  
//厨师类  
public class Cook extends Thread {  
  
    public Cook(String name) {  
        super(name);  
    }  
  
    @Override  
    public void run() {  
        //1. 循环  
        //2. 锁  
        //3. 共享数据到了末尾怎么办  
        //4. 共享数据没到末尾怎么办 核心代码  
  
        while (true) {  
            //如果食物已经有十份就不做了  
            //这里上锁,如果碰到多个厨师,访问共享数据  
            synchronized (Desk.lock) {  
                //表示吃货10碗饭已经吃完了  
                if (Desk.count== 0) {  
                    break;  
                    //   吃货还能吃  
                } else {  
                    //如果桌子上有食物,厨师就等待  
                    if ( Desk.foodFlag == 1) {  
                        //将锁绑定这个等待厨师线程 ,这时吃货抢到cpu执行权开吃  
                        try {  
                            Desk.lock.wait();  
                        } catch (InterruptedException e) {  
                            e.printStackTrace();  
                        }  
                        //这里让出cpu执行权给吃货  
                        //  上面代码表示吃货已经吃了一碗,  唤醒厨师  
                        Desk.lock.notifyAll();  
  
                        //桌子上没食物  
                    } else {  
                        // 做食物,没做好食物,不唤醒吃货  
                        System.out.println(getName() + "做好了食物");  
                        //  唤醒吃货   上面代码表示饭已经做好了,这时吃货还在等待唤醒。  
                        //将桌子状态为1  
                        Desk.foodFlag = 1;  
                        //唤醒吃货开吃  
                        Desk.lock.notifyAll();  
                    }  
                }  
            }  
  
        }  
  
    }  
}
package com.itheima.等待唤醒实现;  
  
//桌子  
public class Desk {  
// 1. 定义桌子上是否有面条 ,这里不用boolean,若多个线程就不行了  
 public static    int  foodFlag = 0; //0表示没有面条,1表示有面条  
// 2. 限制一个吃货只能吃10次食物  
public  static  int count = 10;  
  
//3. 定义一个锁对象  
 public  static  Object lock = new Object();  
}
package com.itheima.等待唤醒实现;  
  
public class Test {  
    public static void main(String[] args) {  
        Cook c = new Cook("魏大厨");  
        Foodie f = new Foodie("小方");  
        c.start();  
        f.start();  
    }  
}

堵塞队列

每一个饭属于队列的一个元素
取不到数据/数据超出队列的数量时都会阻塞
take取数据,put放数据
image.png

堵塞队列的继承结构

阻塞队列实现了以下接口iterable,Collection,Queue,BlockingQueue,而其实现类 ArrayBlockingQueueLinkedBlockQueue

  1. iterable表示阻塞队列可以通过迭代器/增强for来遍历的
  2. Collection表示阻塞队列是一个单列集合
  3. Queue 表示队列
  4. BlockingQueue 表示阻塞队列

image.png

底层

  1. 会将put数据里面先创建一个锁,然后获取锁, 循环判断队列的长度与目前存在的队列个数是否相等,相等则进入等待状态,try-catch-finally里面放入 开锁代码,无论怎么样,都会执行到finally区域
    image.png
    image.png

结果出现连续的输出语句,是因为输出语句没有放在锁内,放在锁外就会出现这种情况 , 这是因为没锁保护的代码会被其他线程执行,而被锁的代码,线程只能等待唤醒执行 ,这样就出现了阻塞队列打印两次的结果

package com.itheima.阻塞队列;  
  
import java.util.concurrent.ArrayBlockingQueue;  
//创建继承线程Thread类  
public class Cook extends Thread {  
//创建有参阻塞队列的构造器  
      ArrayBlockingQueue<String> queue; //这里定义的变量不需要静态,因为测试类传参始终是那个类  
  
    public Cook(ArrayBlockingQueue<String> queue) {  
        this.queue = queue;  
    }  
//    重写任务方法  
  
    @Override  
    public void run() {  
  
        //这里循环放面  
        while (true){  
            try {  
                queue.put("日式拉面");  
                System.out.println("厨师放了一碗面条");  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
}
package com.itheima.阻塞队列;  
  
import java.util.concurrent.ArrayBlockingQueue;  
//继承线程Thread类  
public class Fooding extends Thread {  
    //创建有参阻塞队列的构造器  
    ArrayBlockingQueue<String> queue; //这里定义的变量不需要静态,因为测试类传参始终是那个类  
  
    public Fooding(ArrayBlockingQueue<String> queue) {  
        this.queue = queue;  
    }  
  
// 重写任务方法  
  
    @Override  
    public void run() {  
        while (true) {  
            try {  
                //打印拿到的数据  
                String food = queue.take();  
                System.out.println("吃了一碗"+food);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
}
package com.itheima.阻塞队列;  
  
import java.util.concurrent.ArrayBlockingQueue;  
  
public class Test {  
    public static void main(String[] args) {  
    //    1. 创建一个阻塞队列对象实现等待唤醒机制 ,这里泛型限制阻塞队列集合的数据,里面队列的个数为1  
    /*    因为阻塞队列相当于厨师和吃货在放餐和取餐时,是相相对应的,所以必须保证阻塞队列对象唯一性,  
            所以可以通过构造器传参将这个对象传入  */        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(1);  
  
        Cook c= new Cook(queue);  
        Fooding f = new Fooding(queue);  
  c.start();  
        f.start();  
    }  
}

线程的七大状态

新建-> 就绪(start)-> 运行(抢到了cpu的执行权) -> 死亡(线程执行结束) -> 计时等待( sleep( )的线程) -> 等待(wait()) -> 阻塞( 没获得锁)

  1. Thread.sleep()会让当前线程暂时放弃CPU执行权,从而让其他线程有机会执行。
  2. wait() 会让当前线程等待(沉睡),一直放弃争抢CPU执行权,只能被唤醒后才会去争抢cpu的执行权
  3. 没获得锁,就没办法执行锁里面代码,只能等锁释放

通过Thread.state 可以查看这个线程的各个状态

image.png

Java的Api为什么没有运行状态,那是因为 当线程抢到cpu执行权的时候,他会把当前的线程交给操作系统去管理,JVM虚拟机就不管了

image.png|500
image.png|575

例题

练习1

image.png|475

第一种实现

package com.itheima.练习.练习4.第三种实现;  
  
import java.util.Random;  
  
//定义一个线程实现类  
public class RobRedTest extends Thread {  
  
    static double   money = 100;  
    static int count = 3;// 抢红包的次数  
    static  final double MIN = 0.01; //最小红包金额  
  
  /* public RobRedTest(String name) {  
        super(name);    }*/  
    @Override  
    public void run() {  
  
  
        synchronized (RobRedTest.class) {  
  
            if (count == 0) {   //    判断抽完时,共享数据是否到了末尾  
                System.out.println(getName() + "没有抢到红包");  
            } else {  
                //中奖的金额,重置  
                double prize = 0;  
                if (count == 1) {//如果最后一个人抽到  
                    prize = money;  
  
  
                } else {  
                    Random r = new Random();  
  
  
                    //第一次抽红包的最大金额,99.98,第二次: money-第一次随机的金额  
                    double bounds = money - (count - 1) * MIN;  
                    prize = r.nextDouble(bounds);//0.1~99.98  
                    //若第一次抽红包随机为0.01以下,则红包变为0.01  
                    if (prize<MIN){  
                       prize=MIN;  
                    }  
                }  
                //迭代红包金额和红包的个数  
                money-=prize;  
                count--;  
                System.out.println(getName()+"抢到了"+prize+"元");  
            }  
  
        }  
  
    }  
}
package com.itheima.练习.练习4.第四种实现;  
  
public class Test {  
  
    public static void main(String[] args) {  
  
       /* RobRedTest t1 = new RobRedTest("小丽");  
        RobRedTest t2 = new RobRedTest("小飞");  
        RobRedTest t3 = new RobRedTest("小张");  
        RobRedTest t4 = new RobRedTest("小赵");  
        RobRedTest t5 = new RobRedTest("小磊");*/  
  
        RobRedTest t1 = new RobRedTest();  
        RobRedTest t2 = new RobRedTest();  
        RobRedTest t3 = new RobRedTest();  
        RobRedTest t4 = new RobRedTest();  
        RobRedTest t5 = new RobRedTest();  
  
        t1.setName("小丽");  
        t2.setName("小飞");  
        t3.setName("小灶");  
        t4.setName("蛋蛋");  
        t5.setName("笑笑");  
  
        t1.start();  
        t2.start();  
        t3.start();  
        t4.start();  
        t5.start();  
    }  
}

第二种实现

这里使用了BigDecimal,加减乘除运算都是方法,
double->BigDecimal BigDecimal.valueof() BigDecimal->double double.doubleValue()

package com.itheima.练习.练习4.第四种实现;  
  
import java.math.BigDecimal;  
import java.math.RoundingMode;  
import java.util.Random;  
  
//改成规范的BigDecimal格式  
  
//定义一个线程实现类  
public class RobRedTest extends Thread {  
  
    static BigDecimal money = BigDecimal.valueOf(100.0);  
    static int count = 3;// 抢红包的次数  
    static final BigDecimal MIN = BigDecimal.valueOf(0.01); //最小红包金额  
  
    /* public RobRedTest(String name) {  
          super(name);      }  */    @Override  
    public void run() {  
  
        synchronized (RobRedTest.class) {  
  
            if (count == 0) {   //    判断抽完时,共享数据是否到了末尾  
                System.out.println(getName() + "没有抢到红包");  
            } else {  
                //中奖的金额,重置  
                BigDecimal prize;  
                if (count == 1) {//如果最后一个人抽到  
                    prize = money;  
  
                } else {  
                    Random r = new Random();  
  
                    //第一次抽红包的最大金额,99.98,第二次: money-第一次随机的金额  
                    double bounds = money.subtract(BigDecimal.valueOf(count - 1).multiply(MIN)).doubleValue();  
                    prize = BigDecimal.valueOf(r.nextDouble(bounds));//0.1~99.98  
  
                }  
  
                //设置抽中红包,保留两位,四舍五入  
                prize=prize.setScale(2, RoundingMode.HALF_UP);  
  
                //迭代红包金额和红包的个数  
                money = money.subtract(prize);  
                count--;  
                System.out.println(getName() + "抢到了" + prize + "元");  
            }  
  
        }  
  
    }  
}
package com.itheima.练习.练习4.第四种实现;  
  
public class Test {  
  
    public static void main(String[] args) {  
  
       /* RobRedTest t1 = new RobRedTest("小丽");  
        RobRedTest t2 = new RobRedTest("小飞");  
        RobRedTest t3 = new RobRedTest("小张");  
        RobRedTest t4 = new RobRedTest("小赵");  
        RobRedTest t5 = new RobRedTest("小磊");*/  
  
        RobRedTest t1 = new RobRedTest();  
        RobRedTest t2 = new RobRedTest();  
        RobRedTest t3 = new RobRedTest();  
        RobRedTest t4 = new RobRedTest();  
        RobRedTest t5 = new RobRedTest();  
  
        t1.setName("小丽");  
        t2.setName("小飞");  
        t3.setName("小灶");  
        t4.setName("蛋蛋");  
        t5.setName("笑笑");  
  
        t1.start();  
        t2.start();  
        t3.start();  
        t4.start();  
        t5.start();  
    }  
}

练习2

实现1

package com.itheima.练习.练习5.第二种实现;  
  
import java.util.*;  
import java.util.stream.Collectors;  
  
public class PrizePools extends Thread {  
    ArrayList<Integer> pools;  
  
    public PrizePools(ArrayList<Integer> pools) {  
        this.pools = pools;  
    }  
  
    /*  
     * 抽奖池,两个抽奖盒,每次随机抽奖  
     * */  
  
    @Override  
    public void run() {  
  
        //循环  
        while (true) {  
            //同步代码块  
            synchronized (PrizePools.class) {  
  
                //        共享数据到了末尾  
                if (pools.size() == 0) {  
  
                    break;  
                    //        共享数据没到末尾  
                } else {  
                    //随机集合的数据  
                    //  pools.stream().collect(Collectors.toList());  
                    Collections.shuffle(pools);  
                    //返回集合随机后的第一个元素,并删除他  
                    int pool = pools.remove(0);  
                    System.out.println(getName() + "产生了一个" + pool + "奖项");  
                }  
            }  
            //线程走完睡一会  
            try {  
                sleep(100);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
  
        }  
  
    }  
}
package com.itheima.练习.练习5.第二种实现;  
  
import java.util.ArrayList;  
import java.util.Collections;  
import java.util.LinkedHashSet;  
  
public class Test {  
    public static void main(String[] args) throws InterruptedException {  
        //这里要是为了打乱获取集合的第一个元素,还是换成ArrayList集合比较好  
        ArrayList<Integer> pools = new ArrayList<>();  
        Collections.addAll(pools, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);  
  
        PrizePools p1 = new PrizePools(pools);  
        PrizePools p2 = new PrizePools(pools);  
        p1.setName("抽奖箱1");  
        p2.setName("抽奖箱2");  
        p1.start();  
        p2.start();  
        p2.join();//在main线程之前执行  
        System.out.println("抽奖已谢幕");  
  
  
    }  
}

实现2

package com.itheima.练习.练习5.第一种实现;  
  
import java.util.*;  
  
public class PrizePools extends Thread {  
    Random random = new Random();  
    /*  
     * 抽奖池,两个抽奖盒,每次随机抽奖,抽到一个抽奖盒-1个这个奖项  
     * */    //共享数据是奖池[10,5,20,50,100,200,500,800,2,80,300,700]  
    static LinkedHashSet<Integer> pools;  
  
    static {  
        pools = new LinkedHashSet<Integer>();  
        Collections.addAll(pools, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);  
    }  
  
    @Override  
    public void run() {  
  
        while (true) {  
            try {  
                sleep(100);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            synchronized (PrizePools.class) {  
                int count = 0;  
                if (pools.isEmpty()) { //表示集合为空  
                    break;//这里表示抽完就结束所有线程运行  
                } else {  
                    //随机的索引  
                    int index = random.nextInt(pools.size());//重置每次线程进来找索引取值  
                    for (Integer money : pools) {  
                        if (count == index) {  
                            System.out.println(getName() + "又产生了一个" + money + "元大奖");  
                            //    删除当前元素  
                            pools.remove(money);  
                            break;                        }  
                        count++;  
                    }  
  
                }  
            }  
        }  
    }  
}

package com.itheima.练习.练习5.第一种实现;  
  
public class Test {  
    public static void main(String[] args) throws InterruptedException {  
        PrizePools p1 = new PrizePools();  
        PrizePools p2 = new PrizePools();  
        p1.setName("抽奖箱1");  
        p2.setName("抽奖箱2");  
        p1.start();  
        p2.start();  
        p2.join();//在main线程之前执行  
        System.out.println("抽奖已谢幕");  
  
  
    }  
}

练习3

实现1

package com.itheima.练习.练习6;  
  
import java.util.ArrayList;  
import java.util.Comparator;  
import java.util.Optional;  
  
public class Test {  
    public static void main(String[] args) throws InterruptedException {  
        PrizePools p1 = new PrizePools();  
        PrizePools p2 = new PrizePools();  
        p1.setName("抽奖箱1");  
        p2.setName("抽奖箱2");  
        p1.start();  
        p2.start();  
        p2.join();//在main线程之前执行  
        System.out.println("抽奖已谢幕");  
  
        ArrayList<Integer> box1 = PrizePools.box1;  
        ArrayList<Integer> box2 = PrizePools.box2;  
  
        //这里流的最大值只能是排序好的集合(这个说法是错误的stream的max里面已经排好序了)  
        Optional<Integer> box1Max = box1.stream().max(Integer::compare);//  box1.stream().max((o1,o2)-> Integer.compare(o1, o2));  
        Optional<Integer> box1Min = box1.stream().min(Integer::compare);//  box1.stream().min((o1,o2)-> Integer.compare(o1, o2));  
        Optional<Integer> box2Max = box2.stream().max(Integer::compare);//  box1.stream().max((o1,o2)-> Integer.compare(o1, o2));  
        Optional<Integer> box2Min = box2.stream().min(Integer::compare);//  box1.stream().min((o1,o2)-> Integer.compare(o1, o2));  
            System.out.println("抽奖箱1总共有"+box1.size()+"个奖项"+"分别为:"+box1+"最高奖项为"+box1Max.get()+"最低奖项为"+box1Min.get());  
            System.out.println("抽奖箱2总共有"+box2.size()+"个奖项"+"分别为:"+box2+"最高奖项为"+box2Max.get()+"最低奖项为"+box2Min.get());  
  
    }  
}
package com.itheima.练习.练习6;  
  
import java.util.ArrayList;  
import java.util.Collections;  
import java.util.LinkedHashSet;  
import java.util.Random;  
  
public class PrizePools extends Thread {  
    Random random = new Random();  
    /*  
     * 抽奖池,两个抽奖盒,每次随机抽奖,抽到一个抽奖盒-1个这个奖项  
     * 每次不打印,抽完一次性打印,统计各自的箱子最高/最低的奖项  
     * */    //共享数据是奖池[10,5,20,50,100,200,500,800,2,80,300,700]  
    static LinkedHashSet<Integer> pools;  
    static ArrayList<Integer> box1 = new ArrayList<>();  
    static ArrayList<Integer> box2 = new ArrayList<>();  
  
    static {  
        pools = new LinkedHashSet<Integer>();  
        Collections.addAll(pools, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);  
  
    }  
  
    @Override  
    public void run() {  
  
        while (true) {  
       /*     try {  
                sleep(10);            } catch (InterruptedException e) {                e.printStackTrace();            }*/            synchronized (PrizePools.class) {  
                int count = 0;  
                if (pools.isEmpty()) { //表示集合为空  
                    break;//这里表示抽完就结束所有线程运行  
                } else {  
                    //随机的索引  
                    int index = random.nextInt(pools.size());//重置每次线程进来找索引取值  
                    for (Integer money : pools) {  
                        if (count == index) {  
  
                            if (getName().equals("抽奖箱1")) {  
                                box1.add(money);  
                               // System.out.println(getName() + "又产生了一个" + money + "元大奖");  
  
                            } else if(getName().equals("抽奖箱2")) {  
                                box2.add(money);  
                              //  System.out.println(getName() + "又产生了一个" + money + "元大奖");  
  
                            }  
                            //    删除当前元素  
                            pools.remove(money);  
                            break;                        }  
                        count++;  
                    }  
  
                }  
            }  
        }  
    }  
}

实现2

这个方式有个弊端,当添加很多线程时,需要在线程类里创建多个集合,做多个线程名匹配判断

package com.itheima.练习.练习6.第二种实现;  
  
import java.util.ArrayList;  
import java.util.Collections;  
  
public class PrizePools extends Thread {  
    ArrayList<Integer> pools;  
  
    public PrizePools(ArrayList<Integer> pools) {  
        this.pools = pools;  
    }  
  
    /*  
     * 抽奖池,两个抽奖盒,每次随机抽奖  
     * */  
      //定义两个抽奖盒用于收集  
   static    ArrayList<Integer>  box1 =  new ArrayList<>();  
   static    ArrayList<Integer>  box2 =  new ArrayList<>();  
  
    @Override  
    public void run() {  
  
        //循环  
        while (true) {  
            //同步代码块  
            synchronized (PrizePools.class) {  
  
                //        共享数据到了末尾 打印每个奖箱  
                if (pools.size() == 0) {  
  
                    //判断是否是线程1  
                    if (getName().equals("抽奖箱1")){  
                        System.out.println(getName()+box1);  
                    }else{  
                        System.out.println(getName()+box2);  
                    }  
  
                    break;  
                    //        共享数据没到末尾  
                } else {  
                    //随机集合的数据  
                    //  pools.stream().collect(Collectors.toList());  
                    Collections.shuffle(pools);  
                    //返回集合随机后的第一个元素,并删除他  
                    int pool = pools.remove(0);  
  
  
                    //判断是否是线程1  
                    if (getName().equals("抽奖箱1")){  
                        box1.add(pool);  
                    }else{  
                        box2.add(pool);  
                    }  
  
                }  
  
            }  
            //线程走完睡一会  
            try {  
                sleep(100);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
  
        }  
  
    }  
}
package com.itheima.练习.练习6.第二种实现;  
  
import java.util.ArrayList;  
import java.util.Collections;  
  
public class Test {  
    public static void main(String[] args) throws InterruptedException {  
        //这里要是为了打乱获取集合的第一个元素,还是换成ArrayList集合比较好  
        ArrayList<Integer> pools = new ArrayList<>();  
        Collections.addAll(pools, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);  
  
        PrizePools p1 = new PrizePools(pools);  
        PrizePools p2 = new PrizePools(pools);  
        p1.setName("抽奖箱1");  
        p2.setName("抽奖箱2");  
        p1.start();  
        p2.start();  
        p2.join();//在main线程之前执行  
        System.out.println("抽奖已谢幕");  
  
  
    }  
}

实现3优化2


package com.itheima.练习.练习6.实现3;  
  
import java.util.ArrayList;  
import java.util.Collections;  
  
public class PrizePools extends Thread {  
    ArrayList<Integer> pools;  
  
    public PrizePools(ArrayList<Integer> pools) {  
        this.pools = pools;  
    }  
  
    /*  
     * 抽奖池,两个抽奖盒,每次随机抽奖  
     * */  
/*      //定义两个抽奖盒用于收集  
   static    ArrayList<Integer>  box1 =  new ArrayList<>();   static    ArrayList<Integer>  box2 =  new ArrayList<>();*/  
    @Override  
    public void run() {  
        //每个线程都有自己的集合box,然后多个线程对同一个集合pools删减,直到集合为空,结束多个线程的循环  
        ArrayList<Integer>  box =  new ArrayList<>();  
        //循环  
        while (true) {  
            //同步代码块  
            synchronized (PrizePools.class) {  
  
                //        共享数据到了末尾 打印每个奖箱  
                if (pools.size() == 0) {  
  
                        System.out.println(getName()+box);  
  
                    break;                    //        共享数据没到末尾  
                } else {  
                    //随机集合的数据  
                    //  pools.stream().collect(Collectors.toList());  
                    Collections.shuffle(pools);  
                    //返回集合随机后的第一个元素,并删除他  
                    int pool = pools.remove(0);  
  
  
                    //判断是否是线程1  
  
                        box.add(pool);  
  
                }  
  
            }  
            //线程走完睡一会  
            try {  
                sleep(100);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
  
        }  
  
    }  
}

package com.itheima.练习.练习6.实现3;  
  
import java.util.ArrayList;  
import java.util.Collections;  
  
public class Test {  
    public static void main(String[] args) throws InterruptedException {  
        //这里要是为了打乱获取集合的第一个元素,还是换成ArrayList集合比较好  
        ArrayList<Integer> pools = new ArrayList<>();  
        Collections.addAll(pools, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);  
  
        PrizePools p1 = new PrizePools(pools);  
        PrizePools p2 = new PrizePools(pools);  
        PrizePools p3 = new PrizePools(pools);  
        PrizePools p4 = new PrizePools(pools);  
  
        p1.setName("抽奖箱1");  
        p2.setName("抽奖箱2");  
        p3.setName("抽奖箱3");  
        p4.setName("抽奖箱4");  
        p1.start();  
        p2.start();  
        p3.start();  
        p4.start();  
  
        //在线程p1,p2,p3,p4之后执行main线程  
        Thread[] threads = {p1,p2,p3,p4};  
        for (Thread thread : threads) {  
            thread.join();  
        }  
  
        System.out.println("抽奖已谢幕");  
  
  
    }  
	}

线程的内存图

若有多个线程,每个线程都会执行run方法,而且每个线程都有自己的栈空间,也有各自的堆空间
image.png

练习4

image.png

实现1

package com.itheima.练习.练习7;  
  
import java.util.ArrayList;  
import java.util.Collections;  
import java.util.LinkedHashSet;  
import java.util.Random;  
  
public class PrizePools extends Thread {  
    Random random = new Random();  
    /*  
     * 抽奖池,两个抽奖盒,每次随机抽奖,抽到一个抽奖盒-1个这个奖项  
     * 每次不打印,抽完一次性打印,统计各自的箱子最高/最低的奖项  
     * */    //共享数据是奖池[10,5,20,50,100,200,500,800,2,80,300,700]  
    static LinkedHashSet<Integer> pools;  
  
    static ArrayList<Integer> box1;//抽奖盒1  
    static ArrayList<Integer> box2;  
    static int sumbox1 = 0;  
    static int sumbox2 = 0;  
  
    static {  
        pools = new LinkedHashSet<Integer>();  
        Collections.addAll(pools, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);  
        box1 = new ArrayList<>();  
        box2 = new ArrayList<>();  
  
    }  
  
    @Override  
    public void run() {  
  
        while (true) {  
       /*     try {  
                sleep(10);            } catch (InterruptedException e) {                e.printStackTrace();            }*/            synchronized (PrizePools.class) {  
                int count = 0;  
                if (pools.isEmpty()) { //表示集合为空  
                    break;//这里表示抽完就结束所有线程运行  
                } else {  
                    //随机的索引  
                    int index = random.nextInt(pools.size());//重置每次线程进来找索引取值  
                    for (Integer money : pools) {  
                        if (count == index) {  
  
                            if (getName().equals("抽奖箱1")) {  
                                box1.add(money);  
                                sumbox1+=money;  
                                // System.out.println(getName() + "又产生了一个" + money + "元大奖");  
  
                            } else if (getName().equals("抽奖箱2")) {  
                                box2.add(money);  
                                sumbox2+=money;  
                                //  System.out.println(getName() + "又产生了一个" + money + "元大奖");  
  
                            }  
                            //    删除当前元素  
                            pools.remove(money);  
                            break;                        }  
                        count++;  
                    }  
  
                }  
            }  
        }  
    }  
}
package com.itheima.练习.练习7;  
  
import java.util.ArrayList;  
import java.util.Optional;  
  
public class Test {  
    public static void main(String[] args) throws InterruptedException {  
        PrizePools p1 = new PrizePools();  
        PrizePools p2 = new PrizePools();  
        p1.setName("抽奖箱1");  
        p2.setName("抽奖箱2");  
        p1.start();  
        p2.start();  
        p2.join();//在main线程之前执行  
        System.out.println("抽奖已谢幕");  
  
        ArrayList<Integer> box1 = PrizePools.box1;  
        ArrayList<Integer> box2 = PrizePools.box2;  
  
        //这里流的最大值只能是排序好的集合(这个说法是错误的stream的max里面已经排好序了)  
        Optional<Integer> box1Max = box1.stream().max(Integer::compare);//  box1.stream().max((o1,o2)-> Integer.compare(o1, o2));  
        Optional<Integer> box1Min = box1.stream().min(Integer::compare);//  box1.stream().min((o1,o2)-> Integer.compare(o1, o2));  
        Optional<Integer> box2Max = box2.stream().max(Integer::compare);//  box1.stream().max((o1,o2)-> Integer.compare(o1, o2));  
        Optional<Integer> box2Min = box2.stream().min(Integer::compare);//  box1.stream().min((o1,o2)-> Integer.compare(o1, o2));  
 System.out.println("抽奖箱1总共有"+box1.size()+"个奖项"+"分别为:"+box1+"最高奖项为"+box1Max.get()+"最低奖项为"+box1Min.get()+"总和"+PrizePools.sumbox1);  
System.out.println("抽奖箱2总共有"+box2.size()+"个奖项"+"分别为:"+box2+"最高奖项为"+box2Max.get()+"最低奖项为"+box2Min.get()+"总和"+PrizePools.sumbox2);  
  
        System.out.println(box1Max.get()>box2Max.get()?  
                "抽奖箱1产生了最高奖项"+box1Max.get()+"元":"抽奖箱2产生了最高奖项"+box2Max.get()+"元");  
    }  
}

实现2

获取两个线程运行完的最大值结果 ,每个线程使用Collections.max()方法来判断该抽奖盒的集合中的最大值

image.png

image.png|725

线程池

创建一个线程池,当任务提交时,线程池会创建一个线程用于执行这个任务,执行结束,线程会回到线程池。
然而 当任务大于线程数量时,其余的任务只能等着

image.png

线程池的执行原理

  1. 创建一个池子,池子里面是空的
  2. 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子下回再执行,就不需要创建新的线程了,直接使用线程池里已经有的线程。
  3. 一般来说,当所有线程任务执行完毕后,理想状态是将线程池关闭,实际是不会关闭服务器的线程池的

方法

image.png

无上限的线程池

  1. 这里先创建一个空的线程池 Execusors.newCachedThreaadPool()
  2. 提交任务给线程池 pool.submit(Runable实现类)
  3. 销毁线程池 pool.shutdown() 一般不写 ,这样就不会关闭线程池,他会处于一直运行状态,等待任务派发
package com.itheima.线程池;  
  
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.util.concurrent.ThreadFactory;  
  
public class Test {  
    public static void main(String[] args) {  
  
    //    1. 创建一个线程池对象  
        ExecutorService pool =     Executors.newCachedThreadPool();  
  
    //    2.提交任务 ,这里可以提交 实现了runable接口的对象  
        pool.submit(new MyRunnable());  
  
    //    3. 销毁线程池  
     //   pool.shutdown();  
  
  
    }  
}
package com.itheima.线程池;  
  
public class MyRunnable implements Runnable {  
    @Override  
    public void run() {  
        for (int i = 1; i <= 100; i++) {  
            System.out.println(Thread.currentThread().getName()+"---"+i);  
        }  
    }  
}

image.png|850

这里线程池发现有任务,就创建了一个线程,每当执行一个任务,main线程沉睡1s,这时线程已经执行完任务,就回到了线程池,所以至始至终线程池只创建了一个线程

image.png
image.png

有上限的线程池

这里限制了线程总量为3 ,而且没有关闭线程池,他会处于一直运行状态,等待任务派发

package com.itheima.线程池.Pack2;  
  
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
  
public class Test {  
    public static void main(String[] args) {  
  
    //    1. 创建一个空的有上限的线程池  
        ExecutorService pool = Executors.newFixedThreadPool(3);  
  
    //    2. 提交任务  
        pool.submit(new MyRunable());  
        pool.submit(new MyRunable());  
        pool.submit(new MyRunable());  
        pool.submit(new MyRunable());  
        pool.submit(new MyRunable());  
  
    }  
}
package com.itheima.线程池.Pack2;  
  
public class MyRunable  implements Runnable{  
    @Override  
    public void run() {  
  
        for (int i = 1; i < 100; i++) {  
            System.out.println(Thread.currentThread().getName()+"---"+i);  
        }  
    }  
}

image.png

自定义线程池

image.png|625
image.png|700

当核心线程用完时,排队序列占满时,才会创建临时线程

  1. 这里碰到这种情况,会先创建3个核心线程执行任务
  2. 会将3个任务排队等待
  3. 发现还有任务,创建3个临时线程执行任务
  4. 核心线程+临时线程+队伍线程的数量<任务数量,那么就会触发拒绝策略

|675

拒绝策略

这里的第三种策略会将排队时间最久的任务4抛弃掉,并将新的任务10加入排队序列中
image.png|800
线程池的第三种策略.gif|625

这里为什么拒绝策略采用的是内部类这个方式,内部类只会依赖外部类而存在的,相当于外部类的一个功能,内部类单独出现没有意义,而且内部类本身就是一个独立的个体

这里提交了4个线程任务,而核心线程就3个,所以另一个会到等待序列队伍去等待。若给的任务减去核心线程数且超出等待序列的长度,那么这时会启动空闲线程去执行剩下的任务,如果还超出空闲线程的数量,那么就得看拒绝策略是怎么设置的。

package com.itheima.线程池.自定义线程池;  
  
import java.util.concurrent.ArrayBlockingQueue;  
import java.util.concurrent.Executors;  
import java.util.concurrent.ThreadPoolExecutor;  
import java.util.concurrent.TimeUnit;  
  
public class Test {  
    public static void main(String[] args) {  
        ThreadPoolExecutor poor = new ThreadPoolExecutor(  
                3,//核心线程数量  
                7,//最大线程数  
                1,// 空闲线程等待时间  
                TimeUnit.SECONDS, //空闲线程等待时间的单位  
                new ArrayBlockingQueue<>(5),//任务队列的个数  
                Executors.defaultThreadFactory(),//底层也是创建了一个Thread类,设置了些参数  
  
                //这里任务的拒绝策略是ThreadPoolExecutor的静态内部类  
              //  new ThreadPoolExecutor.AbortPolicy()  //抛弃任务,并抛出异常  
              //   new ThreadPoolExecutor.DiscardPolicy() //丢弃任务,不抛出异常,这是不推荐的  
              //   new ThreadPoolExecutor.DiscardOldestPolicy()// 丢弃等待时间长的等待序列中的任务,并将当前任务加入等待序列任务的末尾  
                new ThreadPoolExecutor.CallerRunsPolicy()//直接调用 run方法绕过线程池执行  
  
        );  
  
        //提交4个任务  
        poor.submit(()-> {  
            for (int i = 1; i <= 100; i++) {  
                System.out.println(Thread.currentThread().getName()+"--"+i);  
            }  
        });  
        poor.submit(()-> {  
            for (int i = 1; i <= 100; i++) {  
                System.out.println(Thread.currentThread().getName()+"--"+i);  
            }  
        });  
        poor.submit(()-> {  
            for (int i = 1; i <= 100; i++) {  
                System.out.println(Thread.currentThread().getName()+"--"+i);  
            }  
        });  
        poor.submit(()-> {  
            for (int i = 1; i <= 100; i++) {  
                System.out.println(Thread.currentThread().getName()+"--"+i);  
            }  
        });  
    }  
}

最大并行数

4核8线程 : 4个大脑,通过虚拟(影分身)分出8个线程

所以 最大并行数是8

线程池的大小?

线程池的大小的决定分两种情况: cpu密集型运算,I/O密集型运算

  1. cpu密集型运算 : 设置线程池大小为 最大并行数+1,后面+1防止前面线程有个有异常缺页问题,可用及时补充位置
  2. I/O密集型运算: 对于本地文件处理/数据库数据操作比较多,一般是电脑的最大并行数x2

这里thread dump 可以计算cpu计算等待时间

image.png

posted @ 2024-06-24 20:41  加油酱  阅读(4)  评论(0编辑  收藏  举报