JAVA 多线程(十八)

多线程(十八)

进程 :进程是程序的基本执行实体。
线程 :一个进程可以包括多个线程,线程是操作系统能够进行运算调度的最小单位,它是进程中实际运作单位,简单理解为在应用软件中互相独立,可以同时运行的功能。

有了多线程 ,可让程序同时做多件事情,多线程 可以提高效率,若想让多个事情同时运行就需要用到多线程,比如:软件中的耗时操作,所有的聊天软件,所有的服务器。
并发 :在同一时刻,有多个指令在单个CPU上交替执行。
并行 :在同一时刻,有多个指令在单个CPU上同时执行。
线程的调度

  1. 抢占式调度:指优先让可运行池中优先级高的线程占用CPU,优先级并不绝对,只不过优先级越高,占用CPU的概率越高。
  2. 分时调度:又称非抢占式调度,指让所有线程轮流获得cpu的使用权。

多线程的实现方式

线程是程序中的执行线程,java虚拟机允许应用程序并发地运行多个执行线程。

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

继承Thread类

步骤

  1. 定义一个类继承Thread
  2. 重写run方法
  3. 创建子类对象,并启动线程(利用start方法启动,run方法会自动调用)

范例:

java 复制代码
//Test.java
public class Test {
    public static void main(String[] args) {
        Function f1=new Function();
        Function f2=new Function();
        f1.setName("线程1");
        f2.setName("线程2");
        f1.start();
        f2.start();
    }
}
//Function.java
public class Function extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName()+"执行");
        }
    }
}
/*
线程2执行
线程2执行
线程2执行
线程2执行
线程1执行
线程2执行
线程1执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
*/

实现Runnable接口

步骤

  1. 定义一个类实现Runnable接口
  2. 重写里面的run方法
  3. 创建自己类的对象
  4. 创建一个Thread类的对象,并开启线程

范例

java 复制代码
//Test.java
public class Test {
    public static void main(String[] args) {
        //创建function对象,表示多线程要执行的任务
        Function f=new Function();
        //创建线程对象
        Thread t1=new Thread(f);
        Thread t2=new Thread(f);
        //给线程设置名字
        t1.setName("线程1");
        t2.setName("线程2");
        //开启线程
        t1.start();
        t2.start();
    }
}
//Function.java
public class Function implements Runnable {
    //线程要执行的代码
    @Override
    public void run() {
        //获取当前线程的对象
        Thread t=Thread.currentThread();
        for (int i=0;i<10;i++) {
            System.out.println(t.getName()+"执行");
        }
    }
}
/*
线程1执行
线程1执行
线程2执行
线程1执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
*/

Callable接口和Future接口方式实现

特点:可获取多线程运行的结果

步骤

  1. 创建一个类MyCallable类实现Callable接口,其中泛型便是多线程运行的结果类型
  2. 重写Call方法(具有返回值,表示多线程运行的结果)
  3. 创建MyCallable的对象(表示多线程要执行的任务)
  4. 创建FutureTask的对象(作用为管理多线程运行的结果)
  5. 创建Thread对象,并启动(表示线程)

范例

java 复制代码
//MyCallable.java

import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum=0;
        for(int i=1;i<=100;i++){
            sum+=i;
        }
        return sum;
    }
}

//

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建一个类MyCallable实现Callable接口
        MyCallable mc=new MyCallable();
        //创建FutureTask的对象(作用为管理多线程运行的结果)
        FutureTask<Integer> ft=new FutureTask<>(mc);
        //创建线程的对象
        Thread t=new Thread(ft);
        //启动线程
        t.start();
        //获取多线程运行的结果
        Integer integer = ft.get();
        System.out.println(integer);

    }
}
/*
5050
*/

三种方式对比

  • 继承Thread类

    • 优点:编程比较简单,可直接使用Thread类的方法
    • 缺点:可扩展性差,不能再继承其他类
  • 实现Runnable接口

    • 优点:扩展性强,实现该接口的同时还可以继承其他类
    • 缺点:编程相对复杂,不能直接使用Thread类中的方法
  • 实现Callable接口

    • 优点:扩展性强,实现该接口的同时还可以继承其他类,可获取多线程运行的结果
    • 缺点:编程相对复杂,不能直接使用Thread类中的方法

常见成员方法

getName

String getName()

说明:返回此线程的名称,若没有给线程设置名字,线程也是有默认名字的,默认名字格式:Thread-X(X序号,从0开始)

setName

void setName(String name)

说明:设置线程名字(构造方法也可以设置名字)

currentThread

static Thread currentThread()

说明:获取当前线程的对象,当虚拟机启动后,会自动启用多条线程,其中一条叫做main线程,它的作用便是调用main方法,并执行其中代码。

sleep

static void sleep(long time)

说明:让线程休眠指定的时间,单位为毫秒(1秒等于1000毫秒),哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间。

setPriority

setPriority(int newPriority)

说明:设置线程的优先级,优先级最高位10,最低为1,默认为5

getPriority

final int getPriority()

说明:获取线程的优先级

setDaemon

final void setDaemon(boolean on)

说明:设置守护线程,当其他非守护线程执行完毕后,守护线程会陆续结束。

yield

public static void yield()

说明:出让线程/礼让线程

join

public final void join()

说明:插入线程/插队线程。表示把调用该方法的线程插入到当前线程之前

范例

java 复制代码
//MyThread.java
public class MyThread extends Thread {
    public MyThread() {
    }

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

    @Override
    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName());
    }
}
//Test.java
public class Test {
    public static void main(String[] args){
        //getName默认
        MyThread mt1=new MyThread();
        mt1.start();
        //构造方法设置
        MyThread mt2=new MyThread("线程2");
        mt2.start();
        //通过setName设置后的getName
        MyThread mt3=new MyThread();
        mt3.setName("线程3");
        mt3.start();

        //currentThread
        Thread t=Thread.currentThread();
        System.out.println(t.getName());
    }
}
/*
main
线程2
Thread-0
线程3
*/
java 复制代码
//MyThread.java
public class MyThread implements Runnable {
    @Override
    public void run() {
        for (int i=0;i<10;i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
//Test.java
public class Test {
    public static void main(String[] args){
        MyThread mt=new MyThread();
        Thread t1=new Thread(mt,"线程1");
        Thread t2=new Thread(mt,"线程2");
        System.out.println("默认");
        System.out.println(t1.getPriority());
        System.out.println(t2.getPriority());
        System.out.println("修该后");
        t1.setPriority(1);
        t2.setPriority(10);
        System.out.println(t1.getPriority());
        System.out.println(t2.getPriority());
        t1.start();
        t2.start();
    }
}
/*
默认
5
5
修该后
1
10
线程2:0
线程2:1
线程1:0
线程2:2
线程1:1
线程1:2
线程2:3
线程1:3
线程2:4
线程1:4
线程2:5
线程2:6
线程2:7
线程2:8
线程2:9
线程1:5
线程1:6
线程1:7
线程1:8
线程1:9
*/
java 复制代码
//Test.java
public class Test {
    public static void main(String[] args){
        MyThread1 mt1=new MyThread1();
        MyThread2 mt2=new MyThread2();
        mt1.setName("守护线程");
        mt2.setName("非守护线程");
        mt1.setDaemon(true);
        mt1.start();
        mt2.start();
    }
}
//MyThread1.java
public class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+i);
        }
    }
}
//MyThread2.java
public class MyThread2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName()+i);
        }
    }
}
/*
守护线程0
非守护线程0
非守护线程1
守护线程1
非守护线程2
守护线程2
非守护线程3
非守护线程4
非守护线程5
非守护线程6
非守护线程7
非守护线程8
非守护线程9
守护线程3
守护线程4
守护线程5
守护线程6
守护线程7
*/
java 复制代码
//Test.java
public class Test {
    public static void main(String[] args){
        MyThread mt1=new MyThread();
        MyThread mt2=new MyThread();
        mt1.setName("线程一");
        mt2.setName("线程二");
        mt1.start();
        mt2.start();
    }
}
//MyThread.java
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName()+i);
            Thread.yield();
        }
    }
}
/*
线程一0
线程二0
线程二1
线程一1
线程一2
线程一3
线程二2
线程一4
线程一5
线程二3
线程二4
线程二5
线程一6
线程二6
线程一7
线程二7
线程一8
线程一9
线程二8
线程二9
*/
java 复制代码
//Test.java
public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt=new MyThread();
        mt.setName("线程一");
        mt.start();
        mt.join();

        for (int i = 0; i < 10; i++) {
            System.out.println("main线程"+i);
        }
    }
}
//MyThread.java
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName()+i);
            Thread.yield();
        }
    }
}
/*
线程一0
线程一1
线程一2
线程一3
线程一4
线程一5
线程一6
线程一7
线程一8
线程一9
main线程0
main线程1
main线程2
main线程3
main线程4
main线程5
main线程6
main线程7
main线程8
main线程9
*/

线程的生命周期

新建状态NEW:创建线程对象。调用start方法后变成就绪状态

就绪状态RUNNABLE:有执行资格,没执行权。不停的抢CPU

阻塞状态BLOCKED:无法获得锁对象

等待状态WAITING:wait方法

计时状态TIMED_WAITING:sleep方法

死亡状态TERMINATED:若run执行完毕,线程死亡,变成垃圾

同步代码块

把操作共享数据的代码锁起来。

特点

  1. 锁默认打开,有一个线程进去,锁自动关闭
  2. 里面的代码全部执行完毕,线程出来,锁自动打开

格式

java 复制代码
synchronized(锁){
	操作共享数据的代码
}
//锁对象一定要是唯一的,一般写当前类的字节码文件。

范例

java 复制代码
//某电影院目前上映的甲电影,共有10张票,有3个窗口售卖,设计一个程序模拟该电影院买票
//Test.java
public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt1=new MyThread();
        mt1.setName("窗口1");

        MyThread mt2=new MyThread();
        mt2.setName("窗口2");

        MyThread mt3=new MyThread();
        mt3.setName("窗口3");

        mt1.start();
        mt2.start();
        mt3.start();

    }
}
//MyThread.java
public class MyThread extends Thread{
    static int ticketNum=1;
    @Override
    public void run() {
        while (true) {
            synchronized (MyThread.class) {
                if (ticketNum <=10) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + "卖出第" + ticketNum + "张票");
                    ticketNum++;

                } else {
                    break;
                }
            }
        }
    }
}
/*
窗口1卖出第1张票
窗口1卖出第2张票
窗口3卖出第3张票
窗口3卖出第4张票
窗口3卖出第5张票
窗口3卖出第6张票
窗口3卖出第7张票
窗口3卖出第8张票
窗口2卖出第9张票
窗口2卖出第10张票
*/

同步方法

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

格式

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

特点

  1. 同步方法是锁住方法里面所有的代码
  2. 锁对象不能自己指定。
    • 非静态方法:this
    • 静态方法:当前类的字节码文件对象

范例

java 复制代码
//某电影院目前上映的甲电影,共有10张票,有3个窗口售卖,设计一个程序模拟该电影院买票,同步方法实现
//Test.java
1public class Test {
    public static void main(String[] args){
        MyThread myThread=new MyThread();
        Thread t1=new Thread(myThread);
        Thread t2=new Thread(myThread);
        Thread t3=new Thread(myThread);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
//MyThread.java
public class MyThread implements Runnable{
    int ticketNum=0;
    @Override
    public void run() {
        while (true){
            if (method()){
                break;
            }
        }
    }
    private synchronized boolean method(){
        if(ticketNum==10){
            return true;
        }else {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticketNum++;
            System.out.println(Thread.currentThread().getName()+ "卖出第" + ticketNum + "张票");
        }
        return false;
    }
}
/*
窗口1卖出第1张票
窗口1卖出第2张票
窗口1卖出第3张票
窗口1卖出第4张票
窗口1卖出第5张票
窗口1卖出第6张票
窗口1卖出第7张票
窗口1卖出第8张票
窗口3卖出第9张票
窗口2卖出第10张票
*/

lock锁

Lock中提供获得锁释放锁的方法。

void lock(); 获得锁

void unlock(); 释放锁

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化。

ReentrantLock的构造方法:

ReentrantLock(); 创建一个ReentrantLock的实例。

范例

java 复制代码
//某电影院目前上映的甲电影,共有10张票,有3个窗口售卖,设计一个程序模拟该电影院买票,Lock实现
//Test.java
public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt1=new MyThread();
        mt1.setName("窗口1");

        MyThread mt2=new MyThread();
        mt2.setName("窗口2");

        MyThread mt3=new MyThread();
        mt3.setName("窗口3");

        mt1.start();
        mt2.start();
        mt3.start();

    }
}
//MyThread.java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyThread extends Thread{
    static int ticketNum=1;
    static Lock lock=new ReentrantLock();
    @Override
    public void run() {
        while (true){
            lock.lock();
            try {
                if (ticketNum <=100) {
                    Thread.sleep(10);
                    System.out.println(getName() + "卖出第" + ticketNum + "张票");
                    ticketNum++;
                } else {
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }
    }
}

:写锁的时候不要让锁嵌套,否则可能会死锁。

等待唤醒机制

方式一

等待唤醒机制(生产者消费者模式)是多个线程之间的一种协作机制。

思路:

此处将消费者比喻成吃货,生产者比喻成厨师。

  • 消费者 :消费数据
    1. 判断桌子上是否有食物
    2. 若无,等待
    3. 若有,开吃
    4. 吃完后,唤醒厨师继续做
  • 生产者 :生产数据
    1. 判断桌子上是否有食物
    2. 若有,等待
    3. 若无,做饭
    4. 把食物放到桌子上
    5. 唤醒等待的消费者开吃
常见方法
wait

void wait()

说明:当前线程等待,直到被其他线程唤醒

notify

void notify()

说明:随机唤醒单个线程

notifyAll

void notifyAll()

说明:唤醒所有线程

范例
java 复制代码
//Desk.java
public class Desk {
    public static int count=10;//吃货能吃的饭的数量
    public static Object lock=new Object();//锁
    public static int state=0;//桌子的状态,若无食物为0,若有食物为1
}
//Cookie.java
public class Cookie extends Thread {
    /*
    1. 循环
    2.同步代码块
    3.判断共享数据是否到了尽头(到了尽头)
    4.判断共享数据是否到了尽头(没到尽头,执行核心逻辑)
    */
    @Override
    public void run() {
        while(true){
            synchronized (Desk.lock){
                if(Desk.count==0){
                    break;
                }else {//判断桌子上是否有食物
                    if(Desk.state==0){//无食物,厨师做饭
                        System.out.println("厨师做饭");
                        //桌子上有了食物,状态设为1
                        Desk.state=1;
                        //唤醒吃货
                        Desk.lock.notifyAll();
                    }else{//若有,则等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}
//Foodie.java
public class Foodie extends Thread{
    /*
    1. 循环
    2.同步代码块
    3.判断共享数据是否到了尽头(到了尽头)
    4.判断共享数据是否到了尽头(没到尽头,执行核心逻辑)
    */
    @Override
    public void run() {
        while (true){
            synchronized (Desk.lock){
                if (Desk.count==0){
                    break;
                }else {
                    //判断桌子是否有食物
                    if(Desk.state==0){
                        try {//若无,等待
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{//若有,开吃
                        //先将饭量-1
                        Desk.count--;
                        System.out.println("吃货还能吃"+Desk.count+"顿饭");
                        //吃饭后,桌子上没有事物,状态设为0
                        Desk.state=0;
                        //唤醒厨师
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}
//Test.java
public class Test {
    public static void main(String[] args) {
        Cookie cookie=new Cookie();
        Foodie foodie=new Foodie();
        cookie.start();
        foodie.start();
    }
}
/*
厨师做饭
吃货还能吃9顿饭
厨师做饭
吃货还能吃8顿饭
厨师做饭
吃货还能吃7顿饭
厨师做饭
吃货还能吃6顿饭
厨师做饭
吃货还能吃5顿饭
厨师做饭
吃货还能吃4顿饭
厨师做饭
吃货还能吃3顿饭
厨师做饭
吃货还能吃2顿饭
厨师做饭
吃货还能吃1顿饭
厨师做饭
吃货还能吃0顿饭
*/

方式二

阻塞队列实现等待唤醒机制。

阻塞队列继承结构

阻塞队列实现Iterable,Collection,Queue,BlockingQueue接口,有两个实现类:

  1. ArrayBlockingQueue:底层是数组,有界
  2. LinkedBlockingQueue:底层是链表,无界,但不是真的无界,最大为int最大值

思路

此处将消费者比喻成吃货,生产者比喻成厨师。

  • 厨师
    1. 构造方法中接收一个阻塞队列对象
    2. run方法中循环向阻塞队列添加食物
    3. 打印添加结果
  • 吃货
    1. 构造方法中接收一个阻塞队列对象
    2. run方法中循环获取阻塞队列的食物
    3. 打印获取结果
核心方法

put(anObject)

将参数放入队列,若放不进去会堵塞。

take()

取出第一个数据,取不到会堵塞。

:put,take方法里本身就有锁

范例
java 复制代码
//Test.java
import java.util.concurrent.ArrayBlockingQueue;

public class Test {
    public static void main(String[] args){
        ArrayBlockingQueue<String> abq=new ArrayBlockingQueue<>(1);//创建阻塞对象,并设置容量
        Cookie cookie=new Cookie(abq);
        Foodie foodie=new Foodie(abq);
        cookie.start();
        foodie.start();

    }
}
//Cookie.java
import java.util.concurrent.ArrayBlockingQueue;

public class Cookie extends Thread{
    ArrayBlockingQueue<String> abq;
    public Cookie(ArrayBlockingQueue<String> abq){
        this.abq=abq;
    }

    @Override
    public void run() {
        while (true){
            try {
                abq.put("食物");
                System.out.println("厨师放食物");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//Foodie.java
import java.util.concurrent.ArrayBlockingQueue;

public class Foodie extends Thread{
    ArrayBlockingQueue<String> abq;
    public Foodie(ArrayBlockingQueue<String> abq){
        this.abq=abq;
    }
    @Override
    public void run() {
        while (true){
            try {
                String s=abq.take();
                System.out.println(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

练习

例1

java 复制代码
/*
假设:100元分成了三个红包(均为整数),有5人去抢
其中红包是共享数据。
5个人5条线程
打印结果如下:
XXX抢到了XX元
XXX抢到了XX元
XXX抢到了XX元
XXX没抢到
XXX没抢到
*/
//Test.java
public class Test {
    public static void main(String[] args) {
        RedPackage rp1=new RedPackage();
        RedPackage rp2=new RedPackage();
        RedPackage rp3=new RedPackage();
        RedPackage rp4=new RedPackage();
        RedPackage rp5=new RedPackage();

        rp1.setName("线程1");
        rp2.setName("线程2");
        rp3.setName("线程3");
        rp4.setName("线程4");
        rp5.setName("线程5");

        rp1.start();
        rp2.start();
        rp3.start();
        rp4.start();
        rp5.start();
    }
}
//RedPackage.java
import java.util.Random;

public class RedPackage extends Thread{
    static int sum=100;
    static int count=3;
    @Override
    public void run() {
        synchronized (RedPackage.class){
            if(count==0){
                System.out.println(getName()+"没抢到");
            }else{
                if(count==1){
                    int money=sum;
                    System.out.println(getName()+"抢到了"+money+"元");
                }else{
                    Random r=new Random();
                    int money=r.nextInt(sum-(count-1))+1;
                    System.out.println(getName()+"抢到了"+money+"元");
                    sum=sum-money;
                }
                count--;
            }
        }
    }
}
/*
线程1抢到了3元
线程5抢到了40元
线程4抢到了57元
线程3没抢到
线程2没抢到
*/

例2

java 复制代码
/*
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为10,5,20,50,100,200,500,800,2,80,300,700
创建两个抽奖箱(线程)设置线程名分别为"抽奖箱1"和"抽奖箱2"
随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
在此次抽奖过程中,抽奖箱2共产生6个奖项
分别为:800,10,80,500,700,2最高奖项为800元,总计额为2092元
在此次抽奖过程中,抽奖箱1共产生6个奖项
分别为:5,100,50,300,20,200最高奖项为300元,总计额为675元
在此次抽奖过程中,抽奖箱2中产生最大奖项,该奖项金额为800元
*/
//Lottery.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.Callable;

public class Lottery implements Callable<Integer> {
    ArrayList<Integer> moneyBox;
    public Lottery(ArrayList<Integer> moneyBox){
        this.moneyBox=moneyBox;
    }
    @Override
    public Integer call() throws Exception {
        ArrayList<Integer> list=new ArrayList<>();
        int sum=0;
        while (true){
            synchronized (Lottery.class) {
                if (moneyBox.size() == 0) {
                    print(list,Collections.max(list),sum);
                    break;
                } else {
                    Collections.shuffle(moneyBox);
                    int money = moneyBox.remove(0);
                    list.add(money);
                    sum+=money;
                }
            }
            Thread.sleep(10);
        }
        if(list.size()==0){
            return null;
        }
        return Collections.max(list);
    }
    private static void print(ArrayList<Integer> list,int max,int sum){
        System.out.println("在此次抽奖过程中,"+Thread.currentThread().getName()+"共产生"+list.size()+"个奖项");
        System.out.print("分别为:");
        for(int i=0;i<list.size();i++){
            System.out.print(list.get(i));
            if(i< list.size()-1){
                System.out.print(",");
            }
        }
        System.out.println("最高奖项为"+max+"元,总计额为"+sum+"元");
    }
}
//Test.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ArrayList<Integer> moneyBox=new ArrayList<>();
        Collections.addAll(moneyBox,10,5,20,50,100,200,500,800,2,80,300,700);
        Lottery lottery=new Lottery(moneyBox);
        FutureTask<Integer> ft1=new FutureTask<>(lottery);
        FutureTask<Integer> ft2=new FutureTask<>(lottery);

        Thread t1=new Thread(ft1);
        Thread t2=new Thread(ft2);
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");
        t1.start();
        t2.start();
        int money;
        String name;
        if(ft1.get()>ft2.get()){
            money=ft1.get();
            name=t1.getName();
        }else{
            money=ft2.get();
            name=t2.getName();
        }
        System.out.println("在此次抽奖过程中,"+name+"产生最大值,最大值金额为"+money);

    }
}
/*
在此次抽奖过程中,抽奖箱1共产生6个奖项
分别为:80,100,50,5,10,700最高奖项为700元,总计额为945元
在此次抽奖过程中,抽奖箱2共产生6个奖项
分别为:500,200,20,300,2,800最高奖项为800元,总计额为1822元
在此次抽奖过程中,抽奖箱2产生最大值,最大值金额为800

*/

线程池

核心原理

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

代码实现步骤

  1. 创建线程池
  2. 提交任务
  3. 所有的任务全部执行完毕,关闭线程池

Executors

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。

newCachedThreadPool

格式

public static ExecutorsService newCachedThreadPool()

说明:创建一个几乎没有上限的线程池

newFixedThreadPool

格式

public static ExecutorsService newFixedThreadPool(int nThreads)

说明:创建一个有上限的线程池

范例

java 复制代码
//Test.java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {
    public static void main(String[] args) throws InterruptedException {
    	//1.获取线程池对象
        ExecutorService pool= Executors.newCachedThreadPool();
        //2.提交任务
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        //3.销毁线程池
        pool.shutdown();
    }
}
//MyRunnable.java
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
/*
pool-1-thread-1
pool-1-thread-3
pool-1-thread-5
pool-1-thread-2
pool-1-thread-4
*/
java 复制代码
//MyRunnable.java
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//Test.java
public class Test {
    public static void main(String[] args) throws InterruptedException {
    	//1.获取线程池对象
        ExecutorService pool= Executors.newFixedThreadPool(3);
        //2.提交任务
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        //3.销毁线程池
        pool.shutdown();
    }
}
/*
pool-1-thread-2
pool-1-thread-1
pool-1-thread-3
pool-1-thread-1
pool-1-thread-2
*/

自定义线程

步骤
  1. 创建一个空的池子

  2. 有任务提交时,线程池会创建线程去执行任务,执行完毕归还线程。

:不断提交任务,会有以下三个临界点

  1. 当核心线程满时,在提交任务就会排队
  2. 当核心线程满,队满时,会创建临时线程
  3. 当核心线程满,队满时,临时线程满时,会触发任务拒绝策略
核心元素(参数)
  1. 核心线程数量(不少于0)
  2. 线程池中最大线程数量(最大数量>=核心线程数量)
  3. 空闲时间(值)(不能小于0)
  4. 空闲时间(单位)(用TimeUnit绑定)
  5. 阻塞队列(不能为null)
  6. 创建线程工厂(不能为null)
  7. 要执行的任务过多时的解决方案(不能为null)
范例
java 复制代码
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 pool=new ThreadPoolExecutor(
                3,//核心线程数量(不少于0)
                6,//线程池中最大线程数量(最大数量>=核心线程数量)
                60,//空闲时间(值)(不能小于0)
                TimeUnit.SECONDS,//空闲时间(单位)(用TimeUnit绑定)
                new ArrayBlockingQueue<>(3),//阻塞队列(不能为null)
                Executors.defaultThreadFactory(),//创建线程工厂(不能为null)
                new ThreadPoolExecutor.AbortPolicy()//要执行的任务过多时的解决方案(不能为null)
        );
    }
}

线程池多大合适

最大并行数:可用Runtime.getRuntime().availableProcessors()返回当前电脑的最大并行数

CPU密集型运算:即项目中CPU计算居多,线程池大小=最大并行数+1  

I/O密集型运算:即项目中文件操作居多,线程池大小=最大并行数*期望CPU利用率*(总时间(CPU计算时间+等待时间)/CPU计算时间)
相关推荐
懒大王爱吃狼40 分钟前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
秃头佛爷2 小时前
Python学习大纲总结及注意事项
开发语言·python·学习
阿伟*rui2 小时前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel
待磨的钝刨2 小时前
【格式化查看JSON文件】coco的json文件内容都在一行如何按照json格式查看
开发语言·javascript·json
XiaoLeisj4 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck4 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei4 小时前
java的类加载机制的学习
java·学习
励志成为嵌入式工程师5 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉5 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer5 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法