Java多线程编程:什么是多线程、实现方式、成员方法、线程安全、死锁、生产者和消费者、多线程练习、线程池

目录

什么是多线程?

多线程的实现方式

一、继承Thread类

二、实现Runnable接口

三、实现Callable接口

常见的成员方法

线程安全的问题 

同步代码块

同步方法

Lock锁

生产者和消费者 

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

 线程的生命周期

线程的6种状态

多线程练习

1.卖电影票

2.送礼品

3.打印奇数数字

4.抢红包

5.抽奖箱抽奖

6.多线程统计并求最大值

线程栈(Thread Stack)

7.多线程之间的比较

线程池

自定义线程池


什么是多线程?

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

进程是程序的基本执行实体。

以前的线程是单线程程序,从头往下依次执行的,CPU不会切换到别的程序中去运行。多线程是CPU在多个线程中轮流切换,同时执行,把以前需要等待的时间利用起来,提高效率。

应用场景:软件中的耗时操作(如拷贝、迁移大文件、加载大量的资源文件)、所有的聊天软件、所有的后台服务器

总结:

  1. 什么是多线程?  有了多线程,我们就可以让程序同时做多件事情 
  2. 多线程的作用?  提高效率
  3. 多线程的应用场景?   只要你想让多个事情同时运行就需要用到多线程,比如:软件中的耗时操作、所有的聊天软件、所有的服务器

多线程的实现方式

在Java中,实现多线程的方式主要有三种:继承Thread类、实现Runnable接口、实现Callable接口。

一、继承Thread类

通过继承Thread类并重写其run方法,可以实现多线程。每个Thread对象代表一个新的线程。

 使用方法

  1. 创建一个类继承 Thread 类。
  2. 重写 Thread 类的 run() 方法,把要执行的代码放在 run() 方法中。
  3. 创建该类的实例,并调用 start() 方法启动线程。

示例代码:

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " is running");
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        
        thread1.start();
        thread2.start();
    }
}

适用场景

  1. 当不需要共享资源时,使用这种方式比较简单直接。
  2. 由于 Java 不支持多重继承,如果已经继承了另一个类,就不能再使用这种方式。

二、实现Runnable接口

通过实现Runnable接口并重写其run方法,可以将该接口的实现类作为线程任务传递给Thread对象。

使用方法

  1. 创建一个类实现 Runnable 接口。
  2. 实现 Runnable 接口的 run() 方法,把要执行的代码放在 run() 方法中。
  3. 创建该类的实例,并将其传递给 Thread 类的构造函数,创建 Thread 对象。
  4. 调用 Thread 对象的 start() 方法启动线程。

示例代码:

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

public class RunnableDemo {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread1 = new Thread(myRunnable);
        Thread thread2 = new Thread(myRunnable);
        
        thread1.start();
        thread2.start();
    }
}

适用场景

  1. 当需要多个线程共享资源时,这种方式更加灵活。
  2. 实现接口的方式更适合需要从其他类继承的情况。

三、实现Callable接口

通过实现Callable接口并重写其call方法,可以创建有返回值的线程任务。与Runnable不同的是,它可以返回结果,并且可以抛出异常。

使用方法

  1. 创建一个类实现 Callable 接口。
  2. 实现 Callable 接口的 call() 方法,并在该方法中编写要执行的代码。
  3. 创建该类的实例,并将其传递给 FutureTask 类的构造函数,创建 FutureTask 对象。
  4. FutureTask 对象传递给 Thread 类的构造函数,创建 Thread 对象。
  5. 调用 Thread 对象的 start() 方法启动线程。
  6. 调用 FutureTask 对象的 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;
    }
}

public class CallableDemo {
    public static void main(String[] args) {
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
        Thread thread = new Thread(futureTask);
        
        thread.start();
        
        try {
            Integer result = futureTask.get();
            System.out.println("Sum: " + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

常见的成员方法

方法名称说明
String getName()返回此线程的名称
void setName(String name)设置线程的名字(构造方法也可以设置名字)
static Thread currentThread()获取当前线程的对象
static void sleep(long time)让线程休眠指定的时间,单位为毫秒
setPriority(int newPriority)设置线程的优先级
final int getPriority()获取线程的优先级
final void setDaemon(boolean on)设置为守护线程
public static void yield()出让线程/礼让线程
public final void join()插入线程/插队线程

线程安全的问题 

synchronized 关键字是 Java 中用于实现同步的机制,确保在同一时刻只能有一个线程访问被它保护的代码块或方法。它的主要作用是防止多个线程同时访问共享资源,避免出现数据不一致的问题。

synchronized 可以用在方法上,也可以用在代码块上。

当一个线程进入 synchronized 方法或代码块时,它会自动获得该方法或代码块的锁,其他线程在尝试进入时会被阻塞,直到该线程释放锁。锁的释放是在该线程退出 synchronized 方法或代码块时自动发生的。

注意事项

  1. 死锁:不当的锁使用可能会导致死锁,即两个或多个线程互相等待对方释放锁,导致程序无法继续执行。
  2. 性能问题:过度使用同步会导致性能下降,因为线程需要等待锁释放。
  3. 锁的范围:尽量缩小同步块的范围,只同步必要的代码部分,以减少线程争用,提高并发性。

同步代码块

通过同步代码块,可以确保同一时间只有一个线程可以执行被同步的代码,从而避免多个线程同时修改共享资源导致的数据不一致问题。

同步代码块的语法:

synchronized (lockObject) {
    // 同步代码
}

lockObject 是一个对象,作为同步锁。所有要访问同步代码块的线程都必须先获得这个锁。

同步代码块中的代码是互斥执行的,即一个线程进入该代码块后,其他线程必须等待,直到该线程退出同步代码块。

同步代码块的锁对象

同步代码块的锁对象可以是任意对象,常见的选择包括:

  1. this:当前实例对象,用于同步实例方法中的代码块。
  2. ClassName.class:类对象,用于同步静态方法或代码块。
  3. 任意自定义对象:可以选择一个单独的对象作为锁,以便更细粒度地控制同步范围。
class SharedResource {
    private static int resource = 0;

    public void increment() {
        synchronized (SharedResource.class) {
            resource++;
            System.out.println(Thread.currentThread().getName() + " incremented resource to " + resource);
        }
    }
}

同步方法

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

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

     如 private synchronized boolean method(){..}

特点

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

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

方法:先写同步代码块,然后把同步代码块在synchronized后面的方法,idea按住Ctrl+Alt+m,把后面的代码抽取成一个方法了。

ThreadDemo.java

MyRunnable mr =new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
MyRunnable.java

int ticket =0;

@override
public void run(){
    //1.循环
    while(true){
        //2.同步代码块(同步方法)
        if(method()) break;
    }
}

//this
private synchronized boolean method(){
    //3.判断共享数据是否到了末尾,如果到了末尾
    if(ticket == 100){
        return true;
    }else {
        //4.判断共享数据是否到了末尾,如果没有到末尾
        try {
            //如果不设置休眠10毫秒,一个线程在短时间内全部执行完,看不到多线程执行的效果了
            Thread.sleep(10);  
        }catch(InterruptedException e){
            e.printstackTrace();
        }
        ticket++;
        System.out.println(Thread.currentThread().getName()
     }
     return false;
}

Lock锁

synchronized(obj)会自动打开自动关闭,没法自我控制。这里介绍一个锁对象Lock,Lock中提供了获得锁和释放锁的方法

void lock():获得锁

void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

ReentrantLock的构造方法

ReentrantLock():创建一个ReentrantLock的实例

static int ticket =0;
static Lock lock = new ReentrantLock();
@override
public void run(){
    while(true){
        //synchronized(MyThread.class){
        lock.lock();
            if(ticket == 100){
                Lock.unlock();
                break;
            }else{
                  try {
                    Thread.sleep(10);
                 } catch (InterruptedException e){
                     e.printstackTrace();
                 }
                 ticket++;
                 System.out.println(getName()+"在卖第"+ticket);
            }
        Lock.unlock();
        }
    }

把开锁XX.unlock()放在finally更好

static int ticket =0;
static Lock lock =new ReentrantLock();
@override
public void run(){
    while(true){
        lock.lock();
    try {
        if(ticket == 100){
            break;
        }else{
            Thread.sleep(10);
            ticket++;
            System.out.println(getName()+"在卖第"+ticket);
    }catch(InterruptedException e){
        e.printstackTrace();
    } finally {
        Lock.unlock();
    }
}

生产者和消费者

常用方法
方法名称说明
void wait()当前线程等待,直到被其他线程唤醒
void notify()随机唤醒单个线程
void notifyAll()唤醒所有线程

示例:厨师煮面条(线程1) 面条放在桌子上   吃货吃面条(线程2)

public class Desk{  //作用:控制生产者和消费者的执行
    public static int foodFlag = 0;  //是否有面条。0:没有面条,1:有面条。
    public static int count = 10;  //总个数
    public static object lock = new object();  //锁对象
}

等待唤醒机制---消费者代码实现  Desk.java

while(true){
    synchronized(Desk.lock){
        if(Desk.count == 0){
            break;
        }else{   
            if(Desk.foodFlag ==0){  //先判断桌子上是否有面条,如果没有,就等待
                try {
                    Desk.lock.wait();  //让当前线程跟锁
                catch(InterruptedException e){
                    e.printstackTrace();
            }else{
                Desk.count--;  //把吃的总数-1
                System.out.println("吃货在吃面条,还剩"+Desk.count+"碗面条");
                Desk.lock.notifyA11();   //吃完之后,唤醒厨师继续做
                Desk.foodFlag =0;  //修改桌子的状态
            }
        }
    }
}

等待唤醒机制---生成者代码实现  Cook.java

public class Cook extends Thread{
    @Override
    public void run(){
        while(true){
            synchronized(Desk.lock){
                if(Desk.count == 0){
                    break;
                }else{
                    if(Desk.foodFlag == 1){  //判断桌子上是否有食物
                        try {   //如果有,就等待
                            Desk.lock.wait();
                        } catch(InterruptedException e) {
                            e.printstackTrace();
                        }    
                        }else{
                            System.out.println("厨师做了一碗面条");   //如果没有,就制作食物                    
                            Desk.foodFlag =1;  //修改桌子上的食物状态
                            Desk.lock.notifyA11();   //叫醒等待的消费者开吃
                        }
                    }
                 }
              }
            }
        }   

使用wait()方法时不能直接使用wait(),要调用锁Desk.lock.wait();这样释放锁的时候就知道释放哪些线程。

测试类 ThreadDemo.java

public class ThreadDemo {
    public static void main(string[] args){

        Cook c = new cook();  //创建线程的对象
        Foodie f = new Foodie();

        c.setName("厨师");  //给线程设置名字
        f.setName("吃货");  

        c.start();  //开启线程
        f.start();
    }
}

运行结果

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

阻塞队列的继承结构
接口lterable、Collection、Queue、BlockingQueue
实现类

ArrayBlockingQueue:底层是数组,有界

LinkedBlockingQueue:底层是链表,无界但不是真正的无界最大为int的最大值

Cook.java

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();
            }
        }
    }

}

Foodie.java

public class Foodie extends Thread{

    ArrayBlockingQueue<string> queue;

    public Foodie(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();
            }
        }
    }

}

测试类ThreadDemo.java

public class ThreadDemo {
    public static void main(string[] args){
        //创建阻塞队列的对象,后面的1是队列的大小,最多存放一个数据
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
     
        Foodie f = new Foodie(queue);   //2.创建线程的对象,并把阻塞队列传递过去
        Cook c=new cook(queue);
        
        c.start();  //3.开启线程
        f.start();
    }
}

 线程的生命周期

线程的6种状态

状态说明1说明2
新建状态(NEW)创建线程对象至今尚未启动的线程
就绪状态(RUNNABLE)start方法正在 Java 虚拟机中执行的线程
阻塞状态(BLOCKED)无法获得锁对象受阻塞并终待某个监视器锁的线程
等待状态(WAITING)wait方法无限期地等待另一个线程来执行某一特定操作的线程
计时等待(TIMED WAITING)sleep方法等待另一个线程来执行取决于指定等待时间的操作的线程
结束状态(TERMINATED)全部代码运行完毕已退出的线程

多线程练习

1.卖电影票

一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒

要求:请用多线程模拟卖票过程并打印剩余电影票的数量

public class TicketSeller implements Runnable {
    private static int tickets = 1000;

    @Override
    public void run() {
        while (true) {
            boolean ticketSold = false;
            synchronized (TicketSeller.class) {
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "售出一张票, 剩余票数: " + (--tickets));
                    ticketSold = true;
                } else {
                    break;
                }
            }
            if (ticketSold) {
                try {
                    Thread.sleep(3000); // 模拟每次领取时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        TicketSeller seller = new TicketSeller();
        Thread window1 = new Thread(seller, "1号窗口");
        Thread window2 = new Thread(seller, "2号窗口");

        window1.start();
        window2.start();
    }
}

Thread.sleep(3000) 移出同步代码块,这样就可以让其他线程在一个线程等待时继续售票。

2.送礼品

有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出,利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来

public class GiftSender implements Runnable {
    private static int gifts = 100;
    private static boolean isPerson1Turn = true;

    @Override
    public void run() {
        while (true) {
            synchronized (GiftSender.class) {
                if (gifts < 10) {
                    // 唤醒所有等待的线程,以确保它们退出
                    GiftSender.class.notifyAll();
                    break;
                }
                // 等待轮到当前线程执行
                while ((Thread.currentThread().getName().equals("玩家1") && !isPerson1Turn) ||
                        (Thread.currentThread().getName().equals("玩家2") && isPerson1Turn)) {
                    try {
                        GiftSender.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 再次检查礼品数量以确保正确
                if (gifts >= 10) {
                    System.out.println(Thread.currentThread().getName() + " 送出一份礼品, 礼物的剩余数量为: " + (--gifts));
                }
                isPerson1Turn = !isPerson1Turn;   // 切换当前线程
                GiftSender.class.notifyAll();  // 唤醒所有等待的线程
            }
        }
    }

    public static void main(String[] args) {
        GiftSender sender = new GiftSender();
        Thread person1 = new Thread(sender, "玩家1");
        Thread person2 = new Thread(sender, "玩家2");

        person1.start();
        person2.start();
    }
}

两个线程交替进行,不然会让某个线程一直执行完才会释放synchronized锁。

3.打印奇数数字

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

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


public class OddNumberPrinter implements Runnable {
    private static int number = 1;

    @Override
    public void run() {
        while (number <= 100) {
            synchronized (OddNumberPrinter.class) {
                if (number % 2 != 0) {
                    System.out.println(Thread.currentThread().getName() + " printed: " + number);
                    number++;
                    OddNumberPrinter.class.notify(); // 唤醒其他等待的线程
                    try {
                        OddNumberPrinter.class.wait(); // 使当前线程等待,直到其他线程唤醒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    number++;
                }
            }
        }
        synchronized (OddNumberPrinter.class) {
            OddNumberPrinter.class.notifyAll(); // 确保所有线程在结束时被唤醒
        }
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(new OddNumberPrinter(), "线程1");
        Thread thread2 = new Thread(new OddNumberPrinter(), "线程2");

        thread1.start();
        thread2.start();
    }
}

4.抢红包

抢红包也用到了多线程。
假设:100块,分成了3个包,现在有5个人去抢。其中,红包是共享数据5个人是5条线程
打印结果如下:
XXX抢到了XXX元
XXX抢到了XXX元
XXX抢到了XXX元
XXX没抢到
XXX没抢到

public class RedWars implements Runnable {
    static double money = 100;  // 红包总额
    static int count = 3;  // 抢红包人数
    static final double MIN = 0.01;  // 抢红包最小金额

    @Override
    public void run() {
        synchronized (RedWars.class) {
            if (count == 0) {
                System.out.println(Thread.currentThread().getName() + "没有抢到红包");
            } else {
                double price = 0;
                if (count == 1) {
                    price = money;  // 最后一个人抢到剩下的金额
                } else {
                    Random r = new Random();
                    double bounds = money - (count - 1) * MIN;  // 最大红包金额限制
                    price = r.nextDouble() * bounds;
                    if (price < MIN) {
                        price = MIN;
                    }
                }
                // 使用 BigDecimal 保留两位小数
                BigDecimal priceBigDecimal = new BigDecimal(price).setScale(2, BigDecimal.ROUND_HALF_UP);
                price = priceBigDecimal.doubleValue();

                money = money - price;
                count--;
                System.out.println(Thread.currentThread().getName() + "抢到了" + price + "元");
            }
        }
    }

    public static void main(String[] args) {
        for (int i = 1; i <= 5; i++) {
            new Thread(new RedWars(), "抢红包玩家" + i + "号").start();
        }
    }
}

5.抽奖箱抽奖

有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80,300,700};

创建两个抽奖箱(线程),设置线程名称分别为“抽奖箱1"、“抽奖箱2”,随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:

每次抽出一个奖项就打印一个(随机)

抽奖箱1 又产生了一个 10 元大奖
抽奖箱1 又产生了一个 100 元大奖
抽奖箱1 又产生了一个 200 元大奖
抽奖箱1 又产生了一个 800 元大奖
抽奖箱2 又产生了一个 700 元大奖

......

public class LotteryBox implements Runnable {
    private final ArrayList<Integer> list;
    public LotteryBox(ArrayList<Integer> list) {this.list = list;}

    @Override
    public void run() {
        while (true) {
            synchronized (LotteryBox.class) {
                if (list.size() == 0) {
                    break;
                } else {
                    Collections.shuffle(list);  //随机打乱给定列表list中元素的顺序。
                    int price = list.remove(0);
                    System.out.println(Thread.currentThread().getName() + " 产生了一个 " + price + " 元的大奖");
                }
            }
            try {
                Thread.sleep(500); // 模拟抽奖时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    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);

        Thread box1 = new Thread(new LotteryBox(list), "抽奖箱1");
        Thread box2 = new Thread(new LotteryBox(list), "抽奖箱2");

        box1.start();
        box2.start();
    }
}

6.多线程统计并求最大值

有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80,300,700};

创建两个抽奖箱(线程),设置线程名称分别为“抽奖箱1"、“抽奖箱2”,随机从抽奖池中获取奖项元素,每次抽的过程中,不打印,抽完时一次性打印(随机),例如:

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

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

public class LotteryBox implements Runnable {
    private final ArrayList<Integer> list;
    public LotteryBox(ArrayList<Integer> list) {this.list = list;}

    @Override
    public void run() {
         //每个线程都有独立的boxList,使用了线程栈
        ArrayList<Integer> boxList = new ArrayList<>(); 
        while (true) {
            synchronized (LotteryBox.class) {
                if (list.size() == 0) {
                    System.out.println(Thread.currentThread().getName()+ boxList);
                    break;
                } else {
                    Collections.shuffle(list);
                    int price = list.remove(0);
                    boxList.add(price);
                }
            }
            try {
                Thread.sleep(10); // 模拟抽奖时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    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);

        Thread box1 = new Thread(new LotteryBox(list), "抽奖箱1");
        Thread box2 = new Thread(new LotteryBox(list), "抽奖箱2");

        box1.start();
        box2.start();
    }
}

线程栈(Thread Stack)

线程栈是每个线程在内存中独立拥有的栈空间,用于存储线程在运行时的局部变量、方法调用信息和一些临时数据。

线程栈的特点:

  1. 每个线程都有自己的栈空间,彼此独立,互不干扰。
  2. 线程栈的生命周期与线程相同,当线程结束时,线程栈也会被释放。
  3. 在多线程环境中,每个线程都有自己的线程栈。这意味着各个线程之间的局部变量和方法调用是相互独立的,不会互相影响。

7.多线程之间的比较

在上一题基础上继续完成如下需求:
在此次抽奖过程中,抽奖箱1总共产生了6个奖项,分别为:10,20,100,500,2,300,最高奖项为300元,总计额为932元。

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

在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元。(比上一题多了这一句话)

提示:因为要比较两个线程中谁获得了最大奖项,可以比较两个数组中的最大值。这里刚好可以用实现Callable的方法,返回线程的值。

public class MyCallable implements Callable<Integer> {
    private final ArrayList<Integer> list;
    public MyCallable(ArrayList<Integer> list) {this.list = list;}
    @Override
    public Integer call() throws Exception {
        ArrayList<Integer> boxList = new ArrayList<>();  //每个线程都有独立的boxList,使用了线程栈
        while (true) {
            synchronized (LotteryBox.class) {
                if (list.size() == 0) {
                    System.out.println(Thread.currentThread().getName() + boxList);
                    break;
                } else {
                    Collections.shuffle(list);
                    int price = list.remove(0);
                    boxList.add(price);
                }
            }
            Thread.sleep(10);
        }
            if(boxList.size()== 0){
                return null;
            }else {
                return Collections.max(boxList);
            }
        }

    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);

        //创建多线程要运行的参数对象
        MyCallable mc =new MyCallable(list);

        //创建多线程运行结果的管理者对象
        FutureTask<Integer> Box1= new FutureTask<>(mc);
        FutureTask<Integer> Box2= new FutureTask<>(mc);

        //创建线程对象
        Thread box1 = new Thread(Box1);
        Thread box2 = new Thread(Box2);

        box1.setName("抽奖箱1");
        box2.setName("抽奖箱2");

        box1.start();
        box2.start();

        Integer maxbox1 = Box1.get();
        Integer maxbox2 = Box2.get();

        String winningBox = maxbox1 > maxbox2? "抽奖箱1" : "抽奖箱2";
        System.out.println("在此次抽奖过程中," + winningBox + "中产生了最大奖项,该奖项金额为" + Math.max(maxbox1, maxbox2) + "元。");
    }
}

线程池

线程池是一种预先创建并管理多个线程的技术,用于提高应用程序的性能和资源利用率。通过使用线程池,应用程序可以避免频繁创建和销毁线程的开销,减少资源消耗,提高响应速度。

线程池代码实现:1.创建线程池  2.提交任务   3.所有的任务全部执行完毕,关闭线程池(可以不关)

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

方法名称说明
public static ExecutorService newCachedThreadPool()创建一个没有上限的线程池
public static ExecutorService newFixedThreadPool(int nThreads)创建一个固定大小的线程池

线程池实现还包括:

ScheduledThreadPool:支持定时和周期性任务的线程池。

SingleThreadExecutor:单线程的线程池,所有任务在同一线程中按顺序执行。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService pool1 = Executors.newFixedThreadPool(3);

        // 提交任务给线程池执行
        for (int i = 0; i < 10; i++) {
            pool1.execute(() -> {
                // 线程执行的具体逻辑
                System.out.println(Thread.currentThread().getName() + " 正在执行任务");
            });
        }

        // 关闭线程池
        pool1.shutdown();
    }
}

运行的其一结果:
        pool-1-thread-2 正在执行任务
        pool-1-thread-3 正在执行任务
        pool-1-thread-1 正在执行任务
        pool-1-thread-3 正在执行任务
        pool-1-thread-1 正在执行任务
        pool-1-thread-2 正在执行任务
        pool-1-thread-3 正在执行任务
        pool-1-thread-2 正在执行任务
        pool-1-thread-1 正在执行任务
        pool-1-thread-3 正在执行任务

自定义线程池

可以使用 ThreadPoolExecutor 类来创建自定义线程池,通过构造函数的参数来精细控制线程池的行为:

import java.util.concurrent.*;

public class CustomThreadPoolDemo {
    public static void main(String[] args) {
        // 创建自定义线程池
        ThreadPoolExecutor customThreadPool = new ThreadPoolExecutor(
                2,  // 核心线程数
                4,  // 最大线程数
                60, // 线程空闲时间
                TimeUnit.SECONDS, // 线程空闲时间单位
                new LinkedBlockingQueue<>(10), // 任务队列
                Executors.defaultThreadFactory(), // 线程工厂
                new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
        );

        // 提交任务给线程池执行
        for (int i = 1; i <= 15; i++) {
            int taskNumber = i;
            customThreadPool.submit(() -> {
                System.out.println("任务" + taskNumber + "开始执行");
                try {
                    Thread.sleep(1000); // 模拟任务执行时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务" + taskNumber + "执行完毕");
            });
        }
        // 关闭线程池
        customThreadPool.shutdown();
    }
}

ThreadPoolExecutor 类的构造函数的参数核心有7个:线程数量、最大线程数、空闲时间值、空闲时间单位、阻塞队列、创建线程的方式、拒绝策略

举个通俗易懂的例子:假设一个餐厅一名服务员只为一个客户服务,服务完一位客户才能继续服务下一位客户。这个餐厅正式服务员(参数1:核心线程数量)有三名,临时服务员有三名,餐厅最多容纳六名服务员(参数2:线程池中最大线程的数量)。如果在某一段时间内(参数3、4:空闲时间值、单位),餐厅没什么生意,就会把临时员工辞退。生意好的时间段,餐厅坐不下,会有排队的客户(参数5:阻塞队列),到时候老板就要招人(参数6:线程工厂)。餐厅门口最多排十名客户,当排队人数多于10位时,超出顾客拒绝服务请下次再来(参数7:拒绝策略)。正式员工是不会被辞退的,而临时员工超出餐厅空闲时间就会被辞退。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值