java之多线程篇

一、基本概念

1.什么是线程?

线程就是,操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。简单理解就是:应用软件中互相独立,可以同时运行的功能

2.什么是多线程?

有了多线程,我们就可以让程序同时做多件事情

3.多线程的作用?

提高效率

4.线程的应用场景?

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

比如:软件中的耗时操作、所有的聊天软件、所有的服务器

二、并发和并行的概念

1.什么是并发?

并发就是,同一时刻,有多个指令在单个CPU上交替执行。

2.什么是并行?

并行就是,同一时刻,有多个指令在多个CPU上同时执行
3.电脑不是只有一个CPU么,这个多个CPU同时执行的并行究竟是什么?

其实,CPU在市面有很多类型如下

比如2核4线程的CPU,就可以同时运行4个线程的任务。

三、多线程的实现方式(3种)

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

用法:

1.定义一个类继承Thread类

2.这个类重写run方法

3.在main方法里面创建定义的类的对象

4.通过该对象的.start()方法启动线程
示例代码

java 复制代码
public class ThreadDemo1 {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        MyThread 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<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
}

上面的两个线程的代码run方法是同时执行的,并不会等一个线程的循环走完。

注意:线程类开启后执行的是run方法的代码

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

用法:

1.自己定义一个类实现Runnable接口

2.重写里面的run方法

3.在main方法创建自己的类的对象

4.将定义的类传递给Thread构造方法创建一个Thread类的对象,并开启线程
示例

java 复制代码
public class ThreadDemo2 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread thread1 = new Thread(myThread);
        Thread thread2 = new Thread(myThread);
        thread1.setName("线程1");
        thread2.setName("线程2");
        thread2.start();
        thread1.start();
    }
}
class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

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

前面两种实现方式,run方法没有返回值,不知道线程实现的结果,现在这个第三种方法是有返回值的。

用法:

1.创建一个类MyCallable实现callable接口

2.重写call(是有返回值的,表示多线程运行的结果)

3.创建MyCallable的对象(表示多线程要执行的任务)

4.传递MyCallable对象为参数创建FutureTask的对象(作用管理多线程运行的结果)

5.传递FutureTask对象为参数创建Thread类的对象,并启动(表示线程)
示例

java 复制代码
public class ThreadDemo3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable mc = new MyCallable();
        FutureTask<Integer> ft = new FutureTask<>(mc);
        Thread t = new Thread(ft);
        t.start();
        System.out.println(ft.get());
    }
}
class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

结果输出5050

4.三种方式的比较

四、Thread类的常用成员方法

1.七个方法

2.前四个细节

String getName()

void setName(String name)

细节:

  1. 果我们没有给线程设置名字,线程也是有默认的名字的

格式:Thread-X(X序号,从0开始的)

2.如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置

static Thread currentThread()

细节:

当JVM虚拟机启动之后,会自动的启动多条线程

其中有一条线程就叫做main线程

他的作用就是去调用main方法,并执行里面的代码

在以前,我们写的所有的代码,其实都是运行在main线程当中

static void sleep(long time)

细节:

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

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

1秒 = 1000毫秒

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

3.线程的优先级

(1)线程调度

抢占式调度: 各条线程执行的顺序和时间是不确定的(随机)

**非抢占式调度:**各条线程执行的顺序是轮流执行和执行时间是差不多的

++java中是选取第一种抢占式调度++

(2)优先级(1~10)

抢占式调度就要涉及到线程的优先级越大,执行的顺序越前

于是可以通过Thread类的上面的两个成员方法来设置和获取线程的优先级

没有设置默认就是5

注意: 这里的优先级是指优先级大的线程先执行的概率比较大,而不是百分百,比如说有两个一样的方法的线程,优先大的线程是有概率计较大 的先执行完,但还是有小概率执行慢

4.守护线程

当一个线程使用Thread类的etDaemon(boolean on)时,这个线程变成守护线程。

细节:

这个线程也可以叫做"备胎"线程,它将其他线程视为"女神"线程,当其他线程结束的时候,这个守护线程就会陆续结束,觉得自己没必要存在了,这就会可能导致守护线程的代码没有全部执行完。
应用场景

上面聊天的场景,两个人聊天开两个线程,一个聊天,一个传输文件,如果聊天线程关闭了,就没有传输文件的必要了,于是将传输文件的线程设置为守护线程。

5.出让/礼让线程

在当前线程的工作不重要时,将CPU资源让位给其他线程,通过使用Thread类的yield()方法来将当前资源让位给其他同优先级线程

java 复制代码
public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
        System.out.println("线程1开始运行!");
        for (int i = 0; i < 50; i++) {
            if(i % 5 == 0) {
                System.out.println("让位!");
                Thread.yield();
            }
            System.out.println("1打印:"+i);
        }
        System.out.println("线程1结束!");
    });
    Thread t2 = new Thread(() -> {
        System.out.println("线程2开始运行!");
        for (int i = 0; i < 50; i++) {
            System.out.println("2打印:"+i);
        }
    });
    t1.start();
    t2.start();
}

观察结果,我们发现,在让位之后,尽可能多的在执行线程2的内容。

6.插入线程

当我们希望一个线程等待另一个线程执行完成后再继续进行,我们可以使用Thread类的join()方法来实现线程的插入。

java 复制代码
public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
        System.out.println("线程1开始运行!");
        for (int i = 0; i < 50; i++) {
            System.out.println("1打印:"+i);
        }
        System.out.println("线程1结束!");
    });
    Thread t2 = new Thread(() -> {
        System.out.println("线程2开始运行!");
        for (int i = 0; i < 50; i++) {
            System.out.println("2打印:"+i);
            if(i == 10){
                try {
                    System.out.println("线程1加入到此线程!");
                    t1.join();    //在i==10时,让线程1加入,先完成线程1的内容,再继续当前内容
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });
    t1.start();
    t2.start();
}

线程2执行一半,线程1加入,先完成线程1的内容,再继续线程2的内容

五、线程的生命周期

六、线程的安全问题

1.售票代码引出问题

有100张票售卖,一共3个窗口在卖。

下面代码设置三个线程当作三个卖票的窗口,看看有什么问题

java 复制代码
public class ThreadSafetyProblem {
    public static void main(String[] args) {
        MyThread1 myThread1 = new MyThread1();
        MyThread1 myThread2 = new MyThread1();
        MyThread1 myThread3 = new MyThread1();
        myThread1.setName("窗口1");
        myThread2.setName("窗口2");
        myThread3.setName("窗口3");
        myThread1.start();
        myThread2.start();
        myThread3.start();
    }
}
class MyThread1 extends Thread{
    //静态变量,几个线程共享
    private static int count = 0;
    public void run(){
        while(true){
            try {//这里只能try-catch丢给JVM,不能throws
                Thread.sleep(100);    //因为父类的run方法没有throws
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (count < 100){
                System.out.println(Thread.currentThread().getName() + "正在卖第" + ++count+"张票");
            }else{
                break;
            }
        }
    }
}

2.超卖和重复卖问题

运行后出现的问题结果如下图:

三个窗口在同时卖一张票,不合法!


超卖了,仅有100张票


原因

线程的执行是有随机性的,cpu的执行权有可能被其他线程抢走。

线程1执行完票数的自增还没来得及打印的时候,线程2和线程3完成自增就会导致超卖和重复卖
那么要怎么解决这个安全问题呢?下一个点就会讲到解决的方法之一------同步代码块。

七、同步代码块

同步代码块的意思就是,把操作共享数据的代码锁起来

1.格式

synchronized(锁){

操作共享数据的代码

}
特点:

1.锁默认打开,有一个线程进去了,锁自动关闭

2.里面的代码全部执行完毕,线程出来,锁自动打开

2.修改后的售票代码

java 复制代码
class MyThread1 extends Thread{
    //静态变量,几个线程共享
    private static int count = 0;
    //锁对象,一定要是唯一的
    static Object obj = new Object();
    public void run(){
        while(true){
            //同步代码块
            synchronized (obj){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                if (count < 2000){
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ++count+"张票");
                }else {
                    break;
                }
            }
        }
    }
}

运行后的结果,票正常售卖,从第一张卖到最后一张。

3.细节问题

**细节1:**同步代码块不能放在while循环里面,因为放在外的话,一个线程拿到锁后就会必须把循环执行完,才会释放锁,这样的话一个线程就把票都卖完了。

**细节2:**锁的对象必须是唯一的,修改后的代码的锁是一个Object对象,用static修饰后表示全局共享唯一的对象。也可以使用MyThread.class表示唯一的字节码文件对象

八、同步方法

如果我们要锁的代码是整个方法,这个时候就要用到同步方法了

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

1.格式

修饰符 synchronized 返回值类型 方法名(方法参数){....}

2.特点

**特点1:**同步方法是锁住方法里面所有的代码

**特点2:**锁对象不能自己指定,如果方法是非静态的,锁对象就是方法所在类对象this

如果方法是静态的,锁对象就是当前类的字节码文件对象

3.使用同步方法解决售票问题

示例代码

这段代码与前面不同的是,这段代码是使用实现Runnable接口实现的多线程,只需要创建一个实现Runnable接口的javabean类的对象,所以票数这个变量的内存地址是唯一的,所以不用像上面的代码一样用static修饰票数count

注意:下面同步方法是非静态的,所以锁对象就是MyRunnable类的对象mr

java 复制代码
public class ThreadSafetyProblem {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
        Thread myThread1 = new Thread(mr);
        Thread myThread2 = new Thread(mr);
        Thread myThread3 = new Thread(mr);
        myThread1.setName("窗口1");
        myThread2.setName("窗口2");
        myThread3.setName("窗口3");
        myThread1.start();
        myThread2.start();
        myThread3.start();
    }
}
class MyRunnable implements Runnable{
    int count = 0;
    public void run(){
        while(true){
            //同步代码块
            if (method()) break;
        }
    }
    //锁对象是this
    private synchronized boolean method() {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (count < 2000){
                System.out.println(Thread.currentThread().getName() + "正在卖第" + ++count+"张票");
            }else {
                return true;
            }
        return false;
    }
}

4.StringBuffer为什么是线程安全的?

虽然StringBuilder和StringBuffer的成员方法是一样的,但为什么之前建议在多线程的情况下使用StringBuffer?

这是由于StringBuffer的成员方法比较 StringBuilder多了一个Sychronized修饰词,保证了线程的安全,但是在单线程的情况下,还是使用StringBuilder好一些。

九、Lock锁

1.售票代码使用手动上锁

java 复制代码
public class ThreadSafetyProblem {
    public static void main(String[] args) {
        MyThread1 myThread1 = new MyThread1();
        MyThread1 myThread2 = new MyThread1();
        MyThread1 myThread3 = new MyThread1();
        myThread1.setName("窗口1");
        myThread2.setName("窗口2");
        myThread3.setName("窗口3");
        myThread1.start();
        myThread2.start();
        myThread3.start();
    }
}
class MyThread1 extends Thread{
    //静态变量,几个线程共享
    private static int count = 0;
    //创建一个锁对象 用static修饰表示共享唯一
    Lock lock = new ReentrantLock();
    public void run(){
        while(true){
            lock.lock();
            try {
                Thread.sleep(10);
                if (count < 2000){
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ++count+"张票");
                }else {
                    break;
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
        }
    }
}

十、死锁

1.示例代码

下面的代码用了锁的嵌套

java 复制代码
public class MyThread extends Thread {
    static Object objA = new Object();
    static Object objB = new Object();
    @Override
    public void run() {
        //1.循环
        while (true) {
            if ("线程1".equals(getName())) {
                synchronized (objA) {
                    System.out.println("线程1拿到了A锁,准备拿B锁");//线程1卡在这里
                    synchronized (objB) {
                        System.out.println("线程1拿到了B锁,顺利执行完一轮");
                    }
                }
            } else if ("线程2".equals(getName())) {
                if ("线程2".equals(getName())) {
                    synchronized (objB) {
                        System.out.println("线程2拿到了B锁,准备拿A锁");//线程2卡在这里
                        synchronized (objA) {
                            System.out.println("线程2拿到了A锁,顺利执行完一轮");
                        }
                    }
                }
            }
        }
    }
}

运行结果

显然两个线程都卡在第一层同步代码的锁那里,程序结束不了。

2.如何解决

很简单,不要写锁或同步代码块的锁嵌套就行。

十一、生产者和消费者模式

生产者消费者模式是一个十分经典的多线程协作的模式,就是要打破两个线程随机执行的规则,你一次我一次。

1.流程框图(等待唤醒机制)

2.涉及的方法

调用 wait() 方法的线程会释放它持有的锁,并进入等待状态,直到它被其他线程通过调用 notify()notifyAll() 唤醒。

3.步骤

消费者和生产者的代码都按下面的步骤走:

1.循环

2.同步代码块(给消费者或生产者上锁)

3.判断共享数据是否到了末尾(到了末尾)

4.判断共享数据是否到了末尾(没有到末尾,执行(消费者或生产者的)核心逻辑)

4. 示例代码

(1)桌子代码

java 复制代码
public class Desk {
    /*
    桌子的作用:控制生产者和消费者的执行
     */
    // 判断桌子上有没有食物
    // 0 没有食物 生产者的线程执行
    // 1 有食物   消费者的线程执行
    //这里一般不用boolean类型,因为boolean类型只能是true或者false
    //如果有多条线程,int 可以表示多条线程的状态
    public static int foodFlat = 0;

    // 消费者现在所能吃食物的碗数
    public static int count = 10;

    //锁对象
    static Object lock = new Object();
}

(2)消费者代码

java 复制代码
public class Foodie extends Thread {
    /*
    1.循环

    2.同步代码块(给消费者或生产者上锁)

    3.判断共享数据是否到了末尾(到了末尾)

    4.判断共享数据是否到了末尾(没有到末尾,执行(消费者或生产者的)核心逻辑)
     */
    public void run(){
        while(true){
            synchronized (Desk.lock){
                if (Desk.count == 0){//吃不下了,直接结束
                    break;
                }else {
                    //如果桌上没有食物
                    if (Desk.foodFlat == 0){
                        try {
                            //进入等待
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else {
                        //桌上有食物
                        Desk.foodFlat = 0;
                        Desk.count--;
                        System.out.println("消费者吃掉了一碗面条,还能吃"+Desk.count+"碗");
                        //通知唤醒厨师
                        Desk.lock.notify();
                    }
                }
            }
        }
    }
}

(3)生产者代码

java 复制代码
public class Cook extends Thread {
    public void run(){
        while(true){
            synchronized (Desk.lock){
                //判断消费者是否吃饱了
                if (Desk.count==0){
                    //吃饱了就结束
                    break;
                }else {
                    //消费者还能吃
                    //判断桌子上还有食物么
                    //有食物就进入等待
                    if (Desk.foodFlat==1){
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else {
                        //没食物
                        //厨师做一碗面条
                        Desk.foodFlat = 1;
                        System.out.println("厨师做了一碗面条");
                        //做好就唤醒消费者
                        Desk.lock.notify();
                    }
                }
            }
        }
    }

}

(4)main方法

java 复制代码
public class Test {
    public static void main(String[] args) {
        Desk desk = new Desk();
        Foodie foodie = new Foodie();
        Cook cook = new Cook();
        foodie.setName("吃货");
        cook.setName("厨师");
        foodie.start();
        cook.start();
    }
}

(5)执行结果

从运行结果可以看出来,消费者和生产者模式可以使多个线程不再随机而是按顺序的来执行。

5.阻塞队列实现唤醒机制

(1)成员方法put和take

put方法底层原理

放入一个数据,put方法接收数据,先使当前线程获得锁(这个队列的),唤醒等待的线程,如果队列满了,当前线程进入等待,释放当前锁。


take方法底层原理

take方法取出数据,先使当前线程获得锁(这个队列的),唤醒等待的线程,如果队列空的,当前线程进入等待,释放当前锁。

(2)代码实现

用阻塞队列实现消费者和生产者的示例代码

消费者代码

java 复制代码
public class Foodie extends Thread {
    ArrayBlockingQueue<String> queue;
    public Foodie(ArrayBlockingQueue<String> queue)
    {
        this.queue = queue;
    }
    public void run() {
        while(true){
            try {
                queue.take();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("吃货吃了一碗面条");
        }
    }
}

生产者代码

java 复制代码
public class Cook extends Thread{
    ArrayBlockingQueue<String> queue;
    public Cook(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }
    public void run() {
        while(true){
            try {
                queue.put("一碗面");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("厨师做了一碗面条");
        }
    }
}

main方法

java 复制代码
public class Test {
    public static void main(String[] args) {
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
        Foodie f = new Foodie(queue);
        Cook c = new Cook(queue);
        f.setName("吃货");
        c.setName("厨师");
        f.start();
        c.start();
    }
}

运行结果

看到上面的结果有人会认为,厨师连续做了两碗面,是不是违反了模式了?

其实不然,消费者和生产者两条线程还是一条一次轮流执行的,重复输出是因为输出的代码放在了锁的外面,所以两个线程是随机的,抢着输出的,厨师做了多少碗面和消费者吃了多少碗的数量还是一样的。厨师放一碗面到队列,吃货就拿一碗。

注意:锁只在阻塞队列里面,即示例代码的put和take方法里面

十二、线程的6种状态

严格的来说,线程有7种状态,但是线程在进入要运行阶段的时候,JVM直接将线程丢给操作系统,java就不管了。所以对于java来说,线程是有6种状态。如下,

十三、Demo

1.Demo1

有100份礼品,两人同时发送,当剩下的礼品品小于10份分的时候则不再送出

利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来

java 复制代码
public class MyRunnable implements Runnable{
    int count = 100;
    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (count < 10){
                    break;
                }else {
                    count--;
                    System.out.println(Thread.currentThread().getName() + "送出一份礼物,剩余" + count + "份礼物");
                }
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        t1.setName("小明");
        t2.setName("小红");
        t1.start();
        t2.start();
    }
}

2.Demo2

java 复制代码
public class MyRunnable implements Runnable{
    double money = 100;
    int count = 3;
    Random rnd = new Random();
    public void run(){
            synchronized (this) {
                if (count == 0){
                    System.out.println(Thread.currentThread().getName()+"没抢到");
                }else if (count == 1){
                    count--;
                    System.out.println(Thread.currentThread().getName()+"抢到了"+money+"元");
                    money = 0;
                }
                else {
                    double get = (rnd.nextDouble()*money);
                    System.out.println(Thread.currentThread().getName()+"抢到了"+get+"元");
                    money = money - get;
                    count--;
                }
            }
    }
}
public class Test {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        Thread t3 = new Thread(mr);
        Thread t4 = new Thread(mr);
        Thread t5 = new Thread(mr);
        t1.setName("玩家1");
        t2.setName("玩家2");
        t3.setName("玩家3");
        t4.setName("玩家4");
        t5.setName("玩家5");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

3.Demo3

java 复制代码
public class MyRunnable implements Runnable{
    //奖池
    ArrayList<Integer> list;
    public MyRunnable(ArrayList<Integer> list) {
        this.list = list;
    }
    @Override
    public void run() {
        while (true){
            synchronized (this) {
                if (list.isEmpty()){
                    break;
                }
                Collections.shuffle(list);
                System.out.println(Thread.currentThread().getName()+"又产生了一个"+list.get(0)+"元大奖");
                list.remove(0);
            }
            //在锁外面休眠一会,这样另外一个线程就先执行,输出会好看一点
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
        MyRunnable mr = new MyRunnable(list);
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");
        t1.start();
        t2.start();
    }
}

4.Demo4 (多线程统计并求最大值)

java 复制代码
public class MyRunnable implements Runnable{
    //奖池
    ArrayList<Integer> list;
    public MyRunnable(ArrayList<Integer> list) {
        this.list = list;
    }
    @Override
    public void run() {
        //定义一个集合,收集每一个抽奖箱每次中奖的金额
        ArrayList<Integer> moneys = new ArrayList<>();
        while (true){
            synchronized (this) {
                if (list.isEmpty()){
                    Collections.sort(moneys);
                    System.out.println("在此次抽奖过程中,"+Thread.currentThread().getName()
                            +"总共产生了"+moneys.size()+"个奖项");
                    String string = moneys.toString();
                    System.out.println("\t分别为:"+string.substring(1,string.length()-1)
                            +"最高奖项为"+moneys.get(moneys.size() - 1)
                            +",总计额为"+moneys.stream().mapToInt(Integer::intValue).sum());
                    break;
                }
                Collections.shuffle(list);
                moneys.add(list.get(0));
                list.remove(0);
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
        String name1 = "抽奖箱1";
        String name2 = "抽奖箱2";
        MyRunnable mr = new MyRunnable(list);
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        t1.setName(name1);
        t2.setName(name2);
        t1.start();
        t2.start();
    }
}

5.Demo5(多线程之间的比较)

由于这里要多线程之间进行比较,所以必须要有返回值,run方法没有返回值,所以使用第三种方法实现多线程,即实现Callable接口和Future接口

java 复制代码
public class MyCallable implements Callable<Integer> {
    //奖池
    ArrayList<Integer> list;
    public MyCallable(ArrayList<Integer> list) {
        this.list = list;
    }
    @Override
    public Integer call(){
        //定义一个集合,收集每一个抽奖箱每次中奖的金额
        ArrayList<Integer> moneys = new ArrayList<>();
        while (true){
            synchronized (this) {
                if (list.isEmpty()){
                    Collections.sort(moneys);
                    System.out.println("在此次抽奖过程中,"+Thread.currentThread().getName()
                            +"总共产生了"+moneys.size()+"个奖项");
                    String string = moneys.toString();
                    System.out.println("\t分别为:"+string.substring(1,string.length()-1)
                            +"最高奖项为"+moneys.get(moneys.size() - 1)
                            +",总计额为"+moneys.stream().mapToInt(Integer::intValue).sum());
                    break;
                }
                Collections.shuffle(list);
                moneys.add(list.get(0));
                list.remove(0);
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        return moneys.get(moneys.size() - 1);
    }
}
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
        String name1 = "抽奖箱1";
        String name2 = "抽奖箱2";
        MyCallable mc = new MyCallable(list);
        FutureTask<Integer> ft1 = new FutureTask<>(mc);
        FutureTask<Integer> ft2 = new FutureTask<>(mc);
        Thread t1 = new Thread(ft1,name1);
        Thread t2 = new Thread(ft2,name2);
        t1.start();
        t2.start();
        if (ft1.get()>ft2.get()){
            System.out.println("在此次抽奖过程中,"+name1+"中产生了最大奖项,该奖项金额为"+ft1.get()+"元");
        }else {
            System.out.println("在此次抽奖过程中,"+name2+"中产生了最大奖项,该奖项金额为"+ft2.get()+"元");
        }
    }
}

十四、多线程的内存图

十五、线程池

1.核心原理

(1)创建一个池子,池子中是空的

(2)提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子

下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可

(3)但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待

2.操作步骤

(1)创建线程池对象

(2)提交任务(提交线程)

(3)任务都执行完毕后,关闭线程池

注意:一般的服务器线程池是不会关闭的,比如王者游戏24小时都能玩

3.自定义线程池

把一个餐厅的运营的七大核心因素看作线程的参数

(1)两种情况+三个临界点

**A.**线程池的参数如下,提交的任务数小于3+3+3,因此不会触发任务过多解决方案,8个任务从核心线程开始放,然后放队伍,发现不够放了,于是找来临时工(临时线程)放多余的两个任务。

注意:任务并不是先放就先执行,比如下面任务7,8后放比在排队的4,5,6先走。


**B.**下面这种情况和上面不同的是,提交的任务数量超过了3+3+3,于是最后一个任务触发了任务拒绝策略。 其他和上面相同

三个临界点

(2)任务拒绝策略

(3)创建一个线程池

构造方法和参数如下

使用这个线程池就提交任务就行pool.submit(线程任务)

(4)最大并行数

在多线程编程中,指同时运行的线程数量的上限。

下面代码可以获得java可用的处理器的数目,即可同时运行线程的最大数量

(5)线程池多大合适

cpu计算时间和等待时间可以通过插件thread dump计算统计

相关推荐
程序猿进阶1 小时前
ThreadLocal 释放的方式有哪些
java·开发语言·性能优化·架构·线程池·并发编程·threadlocal
战族狼魂1 小时前
java代码 识别pdf文件是否含有表格
java·python·pdf
程序者王大川1 小时前
【大数据】如何读取多个Excel文件并计算列数据的最大求和值
开发语言·python·excel·big data
Mryan20051 小时前
OpenJudge | 寻找中位数
开发语言·数据结构·c++·算法·openjudge
码里法2 小时前
springmvc用配置类替换xml配置
java·spring·mvc
lizi888882 小时前
打包Python代码的常用方法实现程序exe应用
开发语言·python
api茶飘香3 小时前
守护应用边界:通过反射API实现安全的输入输出过滤
java·开发语言·python·安全·django·virtualenv·pygame
杀死一只知更鸟debug3 小时前
策略模式的小记
java·开发语言·策略模式
nice666603 小时前
CSS的基本语法
java·前端·css·visual studio code
efls1113 小时前
Qt_了解Qt Creator
开发语言·qt