多线程笔记
多线程笔记

多线程笔记

本笔记参考黑马多线程教程:https://www.bilibili.com/video/BV1LG4y1T7n2/?spm_id_from=333.337.search-card.all.click

并发和并行

多线程的实现方式

1,继承Thread类的方式进行实现

创建新执线程有两种方法。一种方法是将类声明为 Thread 的子类。该子类重写 Thread 类法下来以分配并启动该子类的实例。例如计算大于某一规定值的质数的线程可以写成

然后,下列代码会创建并启动一个线程

PrimeThread p=new PrimeThread(143);
p.start() ;

实例:

class Solution{
    public void solve(){
        Thread mythread1=new MyThread();
        Thread mythread2=new MyThread();
        mythread1.start();
        mythread2.start();
        
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("线程启动");
        }
    }
}

但是这样不知道哪个线程先启动,可以使用线程的setName方法:

class Solution{
    public void solve(){
        Thread mythread1=new MyThread();
        Thread mythread2=new MyThread();
        mythread1.setName("线程1");
        mythread2.setName("线程2");
        mythread1.start();
        mythread2.start();
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.getName()+"启动");
        }
    }
}

输出:

线程2启动
线程2启动
线程1启动
线程2启动
线程1启动
线程2启动
线程1启动
线程1启动
线程1启动
线程1启动
线程1启动
线程2启动
线程1启动
线程2启动
线程1启动
线程2启动
线程2启动
线程1启动
线程2启动
线程2启动

可以看到,线程1和2随机顺序执行,二者交替抢占CPU资源

2,实现Runnable接口的方式进行实现

另一种方法来创建一个线程是声明实现类Runnable接口。重写run方法。然后创建改线程类的实例,并

将这个类的实例作为参数创建一个Thread类,然后调用Thread的run方法:

PrimeRun p = new PrimeRun(143);
new Thread(p).start();

实例:

class Solution{
    public void solve(){
        MyRun  myRun1=new MyRun();
        MyRun  myRun2=new MyRun();
        Thread t1=new Thread(myRun1);
        Thread t2=new Thread(myRun1);
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
    }
}
class MyRun implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            Thread thread=Thread.currentThread();
            System.out.println(thread.getName()+"启动");
        }
    }
}

注意,runable接口中没有getName方法,所以需要使用Thread thread=Thread.currentThread();来获取当前线程对象,进而调用getName方法,或者直接两个语句合起来写:

System.out.println(Thread.currentThread().getName()+"启动");

3,利用Callable接口和Future接口方式实现

前两种方法都有一个缺点:run方法没有返回值,不能获得线程执行的结果,所以有了第三种方法:

class Solution{
    public void solve() throws ExecutionException, InterruptedException {
        //创建MyCallable的对象 (表示多线程要执行的任务)
        MyCallable mc=new MyCallable();
        //创建FutureTask的对象 (作用管理多线程运行的结果)
        FutureTask<Integer> ft=new FutureTask<>(mc);
        //创建线程的对象
        Thread t1=new Thread(ft);
        t1.setName("线程1");
        //启动线程
        t1.start();

        //获取线程运行结果
        Integer result= ft.get();
        System.out.println(result);
    }
}
class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.print(Thread.currentThread().getName()+"返回值:");
        return 999;
    }
}

三种方式对比

常见成员方法

  1. 设置线程的名字

有两种方法:使用setName和构造方法设置,构造方法设置名字如下:

可以在api文档中看到,可以使用构造方法设置名字:

class Solution{
    public void solve(){
        Thread mythread1=new MyThread("线程1");
        Thread mythread2=new MyThread("线程2");
        mythread1.start();
        mythread2.start();
    }
}
class MyThread extends Thread{
    public MyThread() {}
    //TODO:子类构造方法使用super关键字向父类构造犯法传参设置线程名字public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.getName()+"启动");
        }
    }
}
  • 如果我们没有给线程设置名字,线程也是有默认的名字的格式: Thread-X (X序号,从e开始的)
  1. static Thread currentThread()

细节:

获取当前线程的对象

当JVM虚拟机启动之后,会自动的启动多条线程其中有一条线程就叫做main线程他的作用就是去调用main方法,并执行里面的代码在以前,我们写的所有的代码,其实都是运行在main线程当中,所以直接在main方法中调用这个方法返回的是main

  1. static void sleep(long time)

细节:

让线程休眠指定的时间,单位为毫秒

  1、哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间

  2、方法的参数:就表示睡眠的时间,单位毫秒

  3、当时间到了之后,线程会自动的醒来,继续执行下面的其他代码

  1. setPriority(int newPriority)和getPriority()

设置线程的优先级

默认是5,最小是1,最大是10,main方法优先级为5

优先级越高那么抢到cpu概率越大

  1. final void setDaemon(boolean on)

设置为守护线程(备胎线程)

当其他的非守护线程执行完毕之后,守护线程会陆续结束

通俗易懂解释:当女神线程结束了,那么备胎也没有存在的必要了

  1. public static void yield()

出让线程/礼让线程

可以通过yield方法出让cpu的执行权,重新让进程去争夺cpu,可以使多个进程尽可能均匀地执行

class Solution{
    public void solve(){
        Thread mythread1=new MyThread();
        Thread mythread2=new MyThread();
        mythread1.setName("飞机");
        mythread2.setName("坦克");
        mythread1.start();
        mythread2.start();
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.getName()+"@"+i);
            this.yield();
        }
    }
}
  1. public final void join()

插入线程/插队线程

作用:t.join表示等待线程t死亡,即执行结束

  • 原型:public final void join(long millis) throws InterruptedException
  • 等待这个线程死亡的时间最多为millis毫秒。0的超时意味着永远等待。
  • 此实现使用this.wait调用的循环,条件为this.isAlive 。当线程终止时,调用this.notifyAll方法。建议应用程序不使用waitnotify ,或notifyAllThread实例。
class Solution{
    public void solve() throws InterruptedException {
        Thread mythread1=new MyThread();
        Thread mythread2=new MyThread();
        mythread1.setName("飞机");
        mythread2.setName("坦克");
        mythread1.start();
        mythread1.join();
        mythread2.start();
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            System.out.println(this.getName()+"@"+i);
            this.yield();
        }
    }
}

上述代码实现先执行线程1,1执行完毕后才能执行线程2

线程生命周期

线程安全的问题

需求:

某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

如果直接使用静态变量:

class Solution{
    public void solve(){
        Thread mythread1=new MyThread("线程1");
        Thread mythread2=new MyThread("线程2");
        Thread mythread3=new MyThread("线程3");
        mythread1.start();
        mythread2.start();
        mythread3.start();
    }
}
class MyThread extends Thread{
    public static int ticket=0;
    public MyThread() {}
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        while (true){
            if(ticket<100){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                ticket++;
                System.out.println(this.getName()+"售出了第"+ticket+"张票");
            }else
                break;
        }
    }
}

可以看到出现了超卖问题,原因在于,当线程1执行时,比如ticket是99,满足if条件,然后睡10ms,期间线程2和3也满足判断条件,结果是三个线程同时对ticket进行++,最终ticket可能是101或者102。根本原因在于线程执行有随机性

解决这个问题的思路就是锁,使用synchronized对象

synchronized (锁){
    操作共享数据的代码
}
  • 锁默认打开,有一个线程进去了,锁自动关闭
  • 里面的代码全部执行完毕,线程出来,锁自动打开

实例

class Solution{
    public void solve(){
        Thread mythread1=new MyThread("线程1");
        Thread mythread2=new MyThread("线程2");
        Thread mythread3=new MyThread("线程3");
        mythread1.start();
        mythread2.start();
        mythread3.start();
    }
}
class MyThread extends Thread{
    public MyThread() {}
    public MyThread(String name) {
        super(name);
    }

    private static int ticket;
    static Object object=new Object();//为了保证锁的随想唯一,要使用static关键字

    @Override
    public void run() {
        //同步代码块
        synchronized (object){
            while (true){
                if(ticket<100){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    ticket++;
                    System.out.println(this.getName()+"售出了第"+ticket+"张票");
                }else
                    break;
            }
        }
    }
}

注意:锁的对象必须是唯一的,实际一般使用Mythread.class对象:

synchronized (Mythread.class)

问题来了,上述代码运行结果显示一直只有线程1在运行输出,线程2和3没有任何输出

这个可能与CPU性能有关

同步方法

就是把synchronized关键字加到方法上

格式:

修饰符 synchronized 返回值类型 方法名(方法参数) {...}
  • 特点1: 同步方法是锁住方法里面所有的代码
  • 特点2:锁对象不能自己指定
    • 静态:当前类的字节码文件对象
    • 非静态: this

同步方法写起来很简单,首先写正常的synchronized方法,然后把同步代码块内的代码抽取出来独立成方法,删掉原先的synchronized块,直接在循环汇总调用抽取的method即可,在idea中直接选中同步代码块中的方法然后按住ctrl+alt+m即可快捷抽取:

然后删除synchronized块,在抽取出来的方法前加上synchronized即可:

class MyRun implements Runnable{
    int ticket=0;
    @Override
    public void run() {
        while(true){
            if (method()) break;
        }
    }
    //同步方法的所对象是当前的run类,是唯一的
    private synchronized boolean method() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        if(ticket==100){
            return true;
        }else {
            ticket++;
            System.out.println(Thread.currentThread().getName()+"售出了第"+ticket+"张票");
        }
        return false;
    }
}

stringbuilder和stringbuffer的内部方法一模一样,但是stringbufferr是线程安全的,就是因为内部所有方法都是同步方法:

Lock接口

上面的多线程实现方式都有个缺点,就是不能手动释放和获得锁,为此,在jdk5的时候加了一个接口Lock以及他的实现类Reentrantlock ,提供了lock()和unlock()方法,支持手动加锁释放锁

class MyRun implements Runnable{
    static Lock lock=new ReentrantLock();
    static int ticket=0;
    @Override
    public void run() {
        while (true){
            lock.lock();
            if(ticket==100)
                break;
            else {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                ticket++;
                System.out.println(Thread.currentThread().getName()+"售出了第"+ticket+"张票");
            }
            //TODO:这种写法,会导致业务正常结束,但是锁无法释放,程序无法结束的问题lock.unlock();
        }
    }
}

运行后,问题来了,票卖完了,但是程序没有结束:

原因在于最后一次拿到锁后,使用break跳出循环,但是也跳过了锁的unlock过程:

处理这个问题有两个方法,一个是在if(ticket==100)条件真的块中加上unlock,但是更为通用的方法是在finally中加上unlock:

class MyRun implements Runnable{
    static Lock lock=new ReentrantLock();
    static int ticket=0;
    @Override
    public void run() {
        while (true){
            lock.lock();
            try {
                if(ticket==100)
                    break;
                else {
                    Thread.sleep(10);
                    ticket++;
                    System.out.println(Thread.currentThread().getName()+"售出了第"+ticket+"张票");
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {                lock.unlock();            }
        }
    }
}

死锁

生产者消费者

等待唤醒机制

生产者消费者模式是一个十分经典的多线程协作的模式

  1. 第一种情况:消费者等待
  1. 第二种情况:生产者等待

上述过程涉及的方法:

模拟——等待唤醒机制

等待唤醒机制就是消费者可以消费有限个资源,消费完后程序就会结束

Desk对象:

class Desk {//桌子
    //是否有资源 @: 没有资源 1: 有资源
    public static int foodFlag= 0;
    //资源总个数
    public static int count= 10;
    //锁对象
    public static Object lockObj= new Object();
    public Desk(int count) {
        this.count=count;
    }

}
Consumer消费者:class Consumer extends Thread {//消费者者

    public Consumer() {}

    public Consumer(String name) {
        super(name);
    }

    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lockObj) {
                if (Desk.count == 0)
                    break;
                else {
                    //先判断桌子上是否有资源
                    // 如果没有就等待
                    if (Desk.foodFlag == 0) {
                        try {
                            Desk.lockObj.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    } else {
                        // 有的话消费,消费完之后,资源总数-1
                        Desk.count--;
                        System.out.println(Thread.currentThread().getName() + "消费了一个资源,还能消费" + Desk.count + "个资源");
                        // 并且唤醒生产者继续生产
                        Desk.lockObj.notifyAll();
                        // 还需要修改桌子上的状态,有没有资源
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}

Cook生产者:

/** * 循环同步代码块 * 判断其享数据是否到了末尾《到了末尾) * 判断共享数据是否到了末尾《没有到末尾,执行核心逻辑 》 */
public class Cook extends Thread{
    public Cook() {}

    public Cook(String name) {
        super(name);
    }

    @Override
    public void run() {
        while(true){
            synchronized (Desk.lockObj){
                if(Desk.count==0){
                    break;
                }else {
                    //判断桌子上是否有资源
                    if(Desk.foodFlag==1){
                        //有的话等待
                        try {
                            Desk.lockObj.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else {
                        //如果没有,就生产资源
                        System.out.println(Thread.currentThread().getName()+"生产了一个资源");
                        // 修改桌子上的食物状态
                        Desk.foodFlag=1;
                        // 唤醒等待的消费者开吃
                        Desk.lockObj.notifyAll();
                    }
                }
            }
        }
    }
}

测试:

public class Main {
    /**     *  模拟生产者——桌子——消费者模式     *  阻塞队列实现     * @paramargs     */public static void main(String[] args) {
        Cook cook=new Cook("生产者");
        Consumer consumer=new Consumer("消费者");
        consumer.start();
        cook.start();
    }
}
生产者生产了一个资源
消费者消费了一个资源,还能消费9个资源
生产者生产了一个资源
消费者消费了一个资源,还能消费8个资源
生产者生产了一个资源
消费者消费了一个资源,还能消费7个资源
生产者生产了一个资源
消费者消费了一个资源,还能消费6个资源
生产者生产了一个资源
消费者消费了一个资源,还能消费5个资源
生产者生产了一个资源
消费者消费了一个资源,还能消费4个资源
生产者生产了一个资源
消费者消费了一个资源,还能消费3个资源
生产者生产了一个资源
消费者消费了一个资源,还能消费2个资源
生产者生产了一个资源
消费者消费了一个资源,还能消费1个资源
生产者生产了一个资源
消费者消费了一个资源,还能消费0个资源

Process finished with exit code 0

模拟——阻塞队列机制

阻塞队列机制就是存在一个队列,生产者生产的资源放在队列上,消费者从从中拿

阻塞队列需要在测试类中创建阻塞队列,然后将阻塞队列对象分别传递到Cook和Consumer中去:

生产者:

public class Cook extends Thread{
    ArrayBlockingQueue<String> queue;
    public Cook() {}

    public Cook(String name) {
        super(name);
    }
    public Cook(ArrayBlockingQueue<String> queue,String name) {
        super(name);
        this.queue=queue;
    }
    @Override
    public void run() {
        while(true){
            //不需要锁,因为put方法底层有锁
            try {
                queue.put("数据");
                System.out.println("生产者生产了一个数据放到了队列中");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

消费者:

class Consumer extends Thread {//消费者
    ArrayBlockingQueue<String> queue ;

    public Consumer() {}

    public Consumer(String name) {
        super(name);
    }
    public Consumer(ArrayBlockingQueue<String>queue,String name) {
        super(name);
        this.queue=queue;
    }
    @Override
    public void run() {
        while(true){
            //不需要锁,因为put方法底层有锁
            try {
                String take = queue.take();
                System.out.println("消费者消费了一个数据,队列中剩余"+queue.size()+"个数据");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

测试方法:

public class Main {
    /**     *  模拟生产者——桌子——消费者模式     *  阻塞队列实现     * @paramargs     */public static void main(String[] args) {
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
        Cook cook=new Cook(queue,"生产者");
        Consumer consumer=new Consumer(queue,"消费者");
        consumer.start();
        cook.start();
    }
}

需要注意的是,生产者和消费者的run方法不需要加锁,因为阻塞对垒底层已经使用了锁实现:

线程状态

可以通过State方法来查看线程状态:

可以看到,没有运行状态:

是因为线程运行时,jvm就把线程交给cpu了

多线程练习

练习3(找奇数)

同时开启两个线程,共同获取1-10之间的所有数字。

要求:将输出所有的奇数。

class Solution{
    public void solve(){
        Thread mythread1=new MyThread("线程1");
        Thread mythread2=new MyThread("线程2");
        mythread1.start();
        mythread2.start();
    }
}
class MyThread extends Thread{
    static int num=100;
    public MyThread() {}
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        while(true){
            if (extracted()) break;
        }
    }

    private static boolean extracted() {
        num--;
        if(num<=0)
            return true;
        else {
            if(num%2!=0){
                System.out.println(Thread.currentThread().getName()+"找到了一个奇数,为:"+num);
            }
        }
        return false;
    }
}

练习4(抢红包)

抢红包也用到了多线程。

假设:100块,分成了3个包,现在有5个人去抢其中,红包是共享数据。5个人是5条线程

打印结果如下

  XXX抢到了XXX元

  XXX抢到了XXX元

  XXX抢到了XXX元

  XXX没抢到

  XXX没抢到

class Solution{
    public void solve(){
        Thread mythread1=new MyThread("线程1");
        Thread mythread2=new MyThread("线程2");
        Thread mythread3=new MyThread("线程3");
        Thread mythread4=new MyThread("线程4");
        Thread mythread5=new MyThread("线程5");
        mythread1.start();
        mythread2.start();
        mythread3.start();
        mythread4.start();
        mythread5.start();
        /**         * 线程1抢到了93块钱,剩余7块钱         * 线程4抢到了2块钱,剩余5块钱         * 线程5抢到了5块钱,剩余0块钱         * 线程3没抢到         * 线程2没抢到         */}
}
class MyThread extends Thread{
    static int num=100;//剩余金额
    static Random random=new Random(System.currentTimeMillis());
    static Object lockObj=new Object();
    public MyThread() {}
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        synchronized (lockObj){
            if(num==0)
                System.out.println(Thread.currentThread().getName()+"没抢到");
            else {
                int number=random.nextInt(num)+1;
                num-=number;
                System.out.println(Thread.currentThread().getName()+"抢到了"+number+"块钱,剩余"+num+"块钱");
            }
        }
    }
}

练习5 (抽奖箱抽奖)

有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为110,5,20,50,100,200,500,800,2,80,300,7001;创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”“抽奖箱2随机从抽奖池中获取奖项元素并打印在控制台上格式如下每次抽出一个奖项就打印个(随机)

  抽奖箱1 又产生了一个 10 元大奖

  抽奖箱1又产生了一个 100 元大奖抽奖箱1 又产生了一个 200 元大奖

  抽奖箱1 又产生了一个 800 元大奖

  抽奖箱2又产生了一个 700 元大奖

class Solution{
    public void solve(){
        ArrayList<Integer> list=new ArrayList<>();
        Collections.addAll(list,110,5,20,50,100,200,500,800,2,80,300,7001);
        Thread mythread1=new MyThread(list,"抽奖箱1");
        Thread mythread2=new MyThread(list,"抽奖箱2");
        mythread1.start();
        mythread2.start();
    }
}
class MyThread extends Thread{
    static ArrayList<Integer> list=new ArrayList<>();
    static Random random=new Random(System.currentTimeMillis());
    public MyThread() {}
    public MyThread(ArrayList<Integer> queue,String name) {
        super(name);
        this.list=queue;
    }
    @Override
    public void run() {
        while(true){
            synchronized (list){
                int len=list.size();
                if(len<=0)
                    break;
                else {
                    int index=random.nextInt(len);
                    int num=list.get(index);
                    list.remove(index);
                    System.out.println(Thread.currentThread().getName()+"产生了一个"+num+"元大奖");
                }
            }
            try {
                sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

练习7 (多线程之间的比较)难

在上一题基础上继续完成如下需求:

在此次抽奖过程中,抽奖箱1总共产生了6个奖项,分别为:10,20,100,500,2,300最高奖项为300元,总计额为932元

在此次抽奖过程中,抽奖箱2总共产生了6个奖项,分别为: 5,50,200,800,80,700

最高奖项为800元,总金额为1835元

在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元

以上打印效果只是数据模拟,实际代码运行的效果会有差异

class Solution{
    public void solve() throws ExecutionException, InterruptedException {
        ArrayList<Integer> list=new ArrayList<>();
        Collections.addAll(list,110,5,20,50,100,200,500,800,2,80,300,7001);
        //创建MyCallable的对象 (表示多线程要执行的任务)
        MyCallable mc1=new MyCallable(list);
        MyCallable mc2=new MyCallable(list);
        //创建FutureTask的对象 (作用管理多线程运行的结果)
        FutureTask<ArrayList<Integer>> ft1=new FutureTask<>(mc1);
        FutureTask<ArrayList<Integer>> ft2=new FutureTask<>(mc2);
        //创建线程的对象
        Thread t1=new Thread(ft1);
        Thread t2=new Thread(ft2);
        t1.setName("线程1");
        t2.setName("线程2");
        //启动线程
        t1.start();
        t2.start();

        //获取线程运行结果
        ArrayList<Integer> result1= ft1.get();
        ArrayList<Integer> result2= ft2.get();
        System.out.println("在此次抽奖过程中,"+t1.getName()+"总共产生了"+result1.size()+"个奖项。");
        System.out.print("\t分别为:");
        for (int i = 0; i < result1.size(); i++)
            System.out.print(result1.get(i)+" ");
        int sum1=0;
        for (int i = 0; i < result1.size(); i++)
            sum1+=result1.get(i);
        System.out.println("\n最高奖:"+result1.stream().max(Integer::compareTo).get()+"\t总计:"+sum1);

        System.out.println("\n在此次抽奖过程中,"+t2.getName()+"总共产生了"+result2.size()+"个奖项。");
        System.out.print("\t分别为:");
        for (int i = 0; i < result2.size(); i++)
            System.out.print(result2.get(i)+" ");
        int sum2=0;
        for (int i = 0; i < result2.size(); i++)
            sum2+=result2.get(i);
        System.out.println("\n最高奖:"+result2.stream().max(Integer::compareTo).get()+"\t总计:"+sum2);

    }
}
class MyCallable implements Callable<ArrayList<Integer>> {
    static ArrayList<Integer> list=new ArrayList<>();
    static Random random=new Random(System.currentTimeMillis());

    public MyCallable() {
    }
    public MyCallable(ArrayList<Integer> list) {
        this.list=list;
    }

    @Override
    public ArrayList<Integer> call() throws Exception {
        ArrayList<Integer> mylist=new ArrayList<>();
        while (true){
            synchronized (list){
                Thread.sleep(10);
                int len=list.size();
                if(len<=0)
                    break;
                else {
                    int index=random.nextInt(len);
                    int num=list.get(index);
                    list.remove(index);
                    mylist.add(num);
                }
            }
            Thread.sleep(10);
        }
        return mylist;
    }
}

输出:

在此次抽奖过程中,线程1总共产生了6个奖项。
        分别为:110 7001 800 100 50 200 
最高奖:7001        总计:8261

在此次抽奖过程中,线程2总共产生了6个奖项。
        分别为:20 300 80 500 2 5 
最高奖:500        总计:907

线程池

以前写多线程的弊端

  • 弊端1:用到线程的时候就创建
  • 弊端2:用完之后线程消失
  • 浪费操作系统资源——多次创建线程分配资源

线程池代码实现

1,创建线程池

2,提交任务

3,所有的任务全部执行完毕,关闭线程池

  1. newCachedThreadPool
public class Main {

    public static void main(String[] args) {
        //    1,创建线程池
        ExecutorService pool1=Executors.newCachedThreadPool();//无限大小线程池
        //    2,提交任务
        pool1.submit(new MyRunable());
        pool1.submit(new MyRunable());
        pool1.submit(new MyRunable());
        pool1.submit(new MyRunable());
        //    3,所有的任务全部执行完毕,关闭线程池
        pool1.shutdown();
    }


}
class MyRunable implements Runnable{

    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println(Thread.currentThread().getName()+"-----"+i );
        }
    }
}
pool-1-thread-3-----1
pool-1-thread-1-----1
pool-1-thread-2-----1
pool-1-thread-4-----1
pool-1-thread-2-----2
pool-1-thread-1-----2
pool-1-thread-3-----2
pool-1-thread-1-----3
pool-1-thread-2-----3
pool-1-thread-4-----2
pool-1-thread-2-----4
pool-1-thread-1-----4
pool-1-thread-3-----3
pool-1-thread-1-----5
pool-1-thread-2-----5
pool-1-thread-4-----3
pool-1-thread-2-----6
pool-1-thread-1-----6
pool-1-thread-3-----4
pool-1-thread-1-----7
pool-1-thread-2-----7
pool-1-thread-4-----4
pool-1-thread-2-----8
pool-1-thread-1-----8
pool-1-thread-3-----5
pool-1-thread-1-----9
pool-1-thread-2-----9
pool-1-thread-4-----5
pool-1-thread-2-----10
pool-1-thread-1-----10
pool-1-thread-3-----6
pool-1-thread-4-----6
pool-1-thread-3-----7
pool-1-thread-4-----7
pool-1-thread-3-----8
pool-1-thread-4-----8
pool-1-thread-3-----9
pool-1-thread-4-----9
pool-1-thread-3-----10
pool-1-thread-4-----10

Process finished with exit code 0

可以看到,因为每个任务要完成的时间不是那么得短,所以四个线程是由不同的线程对象完成的,如果每次submit后让main方法睡10ms,可以实现四个线程由同一个线程对象完成:

pool-1-thread-1-----1
pool-1-thread-1-----2
pool-1-thread-1-----3
pool-1-thread-1-----4
pool-1-thread-1-----5
pool-1-thread-1-----6
pool-1-thread-1-----7
pool-1-thread-1-----8
pool-1-thread-1-----9
pool-1-thread-1-----10
pool-1-thread-1-----1
pool-1-thread-1-----2
pool-1-thread-1-----3
pool-1-thread-1-----4
pool-1-thread-1-----5
pool-1-thread-1-----6
pool-1-thread-1-----7
pool-1-thread-1-----8
pool-1-thread-1-----9
pool-1-thread-1-----10
pool-1-thread-1-----1
pool-1-thread-1-----2
pool-1-thread-1-----3
pool-1-thread-1-----4
pool-1-thread-1-----5
pool-1-thread-1-----6
pool-1-thread-1-----7
pool-1-thread-1-----8
pool-1-thread-1-----9
pool-1-thread-1-----10
pool-1-thread-1-----1
pool-1-thread-1-----2
pool-1-thread-1-----3
pool-1-thread-1-----4
pool-1-thread-1-----5
pool-1-thread-1-----6
pool-1-thread-1-----7
pool-1-thread-1-----8
pool-1-thread-1-----9
pool-1-thread-1-----10

线程池主要核心原理

  1. 创建一个池子,池子中是空的
  2. 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
  3. 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待

自定义线程池

在之前先看一个故事——饭店的故事

细节:

  • 临时线程创建时机:核心线程都在忙,等待队列已满
  • 线程不是按照提交顺序被分配线程对象的
  • 如果提交的任务超过核心线程数+临时线程数+队列长度,就会拒绝剩下的任务,可以选择拒绝策略:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

Index