Java多线程

进程

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

线程

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

简单理解:应用软件中互相独立,可以同时运行的功能

并发和并行

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

并行:在同一时刻,有多个指令在多个CPU上同时执行

多线程的实现方式

  1. 继承 Thread 类的方式进行实现
package com.faxont.threadcase1;

public class ThreadDemo {
    public static void main(String[] args) {
        /**
         * 多线程的第一种启动方式:
         *      1. 自己定义一个类继承 Thread
         *      2. 重写 run 方法
         *      3. 创建子类的对象,并启动线程
         */

        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        // 为线程命名
        mt1.setName("线程1");
        mt2.setName("线程2");
        // 开启线程
        mt1.start();
        mt2.start();
    }
}
package com.faxont.threadcase1;

public class MyThread extends Thread{

    @Override
    public void run() {
        // 书写线程要执行的代码
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + " : HelloWorld");
        }
    }
}
  1. 实现 Runnable 接口的方式进行实现
package com.faxont.threadcase2;

public class ThreadDemo {
    public static void main(String[] args) {
        /**
         * 多线程的第二种启动方式:
         *      1. 自己定义一个类实现 Runnable 接口
         *      2. 重写里面的 run 方法
         *      3. 创建自己的类对象
         *      4. 创建一个 Thread 类对象,并开启线程
         */

        // 创建 MyRun 对象
        // 表示多线程要执行的任务
        MyRun mr = new MyRun();

        // 创建 Thread 类对象
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);

        // 给线程设置名字
        t1.setName("线程1");
        t2.setName("线程2");

        // 开启线程
        t1.start();
        t2.start();
    }
}
package com.faxont.threadcase2;

public class MyRun implements Runnable{
    @Override
    public void run() {
        // 书写线程要执行的代码
        String name = Thread.currentThread().getName();
        for (int i = 0; i < 100; i++) {
            System.out.println(name + " : HelloWorld");
        }
    }
}
  1. 利用 Callable 接口和 Future 接口方式实现
package com.faxont.threadcase3;

import java.util.concurrent.FutureTask;

public class ThreadDemo {
    public static void main(String[] args) {
        /**
         * 多线程的第三种实现方式:
         *      特点:可以获取到多线程运行的结果
         *      1. 创建一个类 MyCallable 实现 Callable 接口
         *      2. 重写 call 方法(这个方法是有返回值的,表示多线程运行的结果)
         *      3. 创建 MyCallable 类对象(表示多线程要执行的任务)
         *      4. 创建 Future 的实现类 FutureTask 的对象(作用管理多线程运行的结果)
         *      5. 创建 Thread 类对象,并开启线程
         */

        // 创建 MyCallable 类对象
        MyCallable mc = new MyCallable();
        // 创建 FutureTask 的对象
        FutureTask<Integer> ft = new FutureTask<>(mc);
        // 创建 Thread 类对象,并开启线程
        Thread t1 = new Thread(ft);
        t1.start();
        try {
            // 获取多线程运行结果
            Integer sum = ft.get();
            System.out.println("结果:" + sum);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
package com.faxont.threadcase3;

import java.util.concurrent.Callable;

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

多线程三种实现方式对比

实现方法 优点 缺点
继承 Thread 类 编程比较简单,可以直接使用 Thread 类中的方法 可扩展性较差,不能再继承其它的类
实现 Runnable 接口 扩展性强,实现该接口的同时还可以继承其它的类 编程相对复杂,不能直接使用 Thread 类中的方法
实现 Callable 接口 1. 扩展性强,可继承其它类;
2. 支持获取线程执行结果;
3. 可抛出异常
1. 编程更复杂,需要配合 FutureTask 使用;
2. 不能直接使用 Thread 类中的方法;
3. 获取结果时需要处理异常

常见的成员方法

方法名称 说明
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 static void join() 插入线程 / 插队线程
package com.faxont.threadmethod1;

public class ThreadDemo {
    public static void main(String[] args) {
        /*
            String getName()                    返回此线程的名称
            void setName(String name)           设置线程的名字(构造方法也可以设置名字)
            细节:
                1. 如果我们没有给线程设置名字,线程也是有默认的名字的
                        格式:Thread-X(X为序号,从0开始递增)
                2. 如果我们要给线程设置名字,可以用 setName()方法,也可以用构造方法

            static Thread currentThread()       获取当前线程的对象
            细节:
                当 JVM 虚拟机启动之后,会自动地启动多条线程
                其中有一条线程就叫做 main 线程
                它的作用就是去调用 main 方法,并执行里面的代码
                所以之前我们写的所有代码都是在 main 线程中执行的

            static void sleep(long time)        让线程休眠指定的时间,单位为毫秒
            细节:
                1. 哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
                2. 方法的参数:就表示睡眠时间,单位毫秒
                3. 当时间到了之后,就会自动地醒来,继续执行下面的其它代码
         */

        // 1. 创建线程对象
        MyThread mt1 = new MyThread("军团");
        MyThread mt2 = new MyThread("护士");

        // 2. 开启线程
        mt1.start();// Thread-0
        mt2.start();// Thread-1
//
//        Thread t = Thread.currentThread();
//        System.out.println(t.getName());// main

//        System.out.println("准备进行线程休眠...");
//        try {
//            Thread.sleep(5000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
//        System.out.println("线程休眠结束...");
    }
}
package com.faxont.threadmethod1;

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName() + "@" + i);
        }
    }

    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }
}
package com.faxont.threadmethod2;

public class ThreadDemo {
    public static void main(String[] args) {
        /*
            setPriority(int newPriority)    设置线程的优先级
            final int getPriority()         获取线程的优先级

            优先级:1~10    默认为5    优先级越高,抢占资源的概率越高
         */

        // 创建线程要执行的参数对象
        MyRunnable mr = new MyRunnable();
        // 创建线程对象
        Thread t1 = new Thread(mr, "线程1");
        Thread t2 = new Thread(mr, "线程2");

//        System.out.println(t1.getPriority());// 5
//        System.out.println(t2.getPriority());// 5
//        System.out.println(Thread.currentThread().getPriority());// 5

        t1.setPriority(1);
        t2.setPriority(10);

        t1.start();
        t2.start();

    }
}
package com.faxont.threadmethod2;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}
package com.faxont.threadmethod3;

public class ThreadDemo {
    public static void main(String[] args) {
        /*
            final void setDaemon(boolean on)        设置为守护线程
            细节:
                当其它的非守护线程执行完毕之后,守护线程也会陆续结束
         */

        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        t1.setName("女神");
        t2.setName("备胎");

        // 把第二个线程设置为守护线程
        t2.setDaemon(true);

        t1.start();
        t2.start();
    }
}
package com.faxont.threadmethod3;

public class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}
package com.faxont.threadmethod3;

public class MyThread2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}
package com.faxont.threadmethod4;

public class ThreadDemo {
    public static void main(String[] args) {
        /*
            public static void yield()      出让线程 / 礼让线程
         */

        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("线程1");
        t2.setName("线程2");

        t1.start();
        t2.start();
    }
}
package com.faxont.threadmethod4;

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "@" + i);
            try {
                // 表示出让当前CPU的执行权
                Thread.yield();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
package com.faxont.threadmethod5;

public class ThreadDemo {
    public static void main(String[] args) {
        /*
            public static void join()       插入线程 / 插队线程
         */

        MyThread t = new MyThread();
        t.setName("线程1");
        t.start();

        try {
            // 表示把 t 线程插入到当前线程之前
            // t:线程1
            // 当前线程:main
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 执行在 main 线程当中的
        for (int i = 0; i < 10; i++) {
            System.out.println("main 线程" + i);
        }
    }
}
package com.faxont.threadmethod5;

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}

线程安全的问题

练习:多窗口卖票

需求:

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

package com.faxont.threadsafe1;

public class ThreadDemo {
    public static void main(String[] args) {
        /*
            需求:
            某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
         */

        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        Thread t3 = new Thread(myRunnable);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
package com.faxont.threadsafe1;

public class MyRunnable implements Runnable{
    private static int ticket = 100;
    private static final Object obj = new Object();
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (obj) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                    ticket--;
                }else {
                    break;
                }
            }
        }
    }
}

同步代码块

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

// 格式
synchronized(锁){
    操作共享数据的代码
}

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

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

同步方法

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

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

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

特点2:锁对象不能自己指定

  • 非静态:this
  • 静态:当前类的字节码文件对象
package com.faxont.threadsafe2;

public class ThreadDemo {
    public static void main(String[] args) {
        /*
            需求:
            某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
            利用同步方法完成
         */

        MyRunnable target = new MyRunnable();
        Thread t1 = new Thread(target);
        Thread t2 = new Thread(target);
        Thread t3 = new Thread(target);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
package com.faxont.threadsafe2;

public class MyRunnable implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            saleTicket();
        }
    }

    public synchronized void saleTicket() {
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
            ticket--;
        }
    }
}

Lock 锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,

但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,

为了更清晰地表达如何加锁和释放锁,JDK5 以后提供了一个新的锁对象 Lock

Lock 实现提供比使用 sysnchronized 方法和语句可以获得更广泛的锁定操作

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

void lock():获得锁

void unlock():释放锁

即可以手动上锁,手动释放锁

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

ReentrantLock 的构造方法

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

package com.faxont.threadsafe3;

import com.faxont.threadsafe2.MyRunnable;

public class ThreadDemo {
    public static void main(String[] args) {
        /*
            需求:
            某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
            利用同步方法完成
         */

        MyRunnable target = new MyRunnable();
        Thread t1 = new Thread(target);
        Thread t2 = new Thread(target);
        Thread t3 = new Thread(target);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
package com.faxont.threadsafe3;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyRunnable implements Runnable{
    private int ticket = 100;
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                    ticket--;
                }else{
                    break;
                }
            } finally{
                lock.unlock();
            }
            try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        }
    }

}

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

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

生产者和消费者(常见方法)

方法名称 说明
void wait() 当前线程等待,直到被其它线程唤醒
void notify() 随机唤醒单个线程
void notifyAll() 唤醒所有线程
package com.faxont.waitandnotify;

public class ThreadDemo {
    public static void main(String[] args) {
        /*
            需求:完成生产者和消费者(等待唤醒机制)的代码
                    实现线程轮流交替执行的效果
         */

        Desk desk = new Desk();
        Foodie foodie = new Foodie(desk);
        Cook cook = new Cook(desk);
        Thread t1 = new Thread(foodie);
        Thread t2 = new Thread(cook);
        t1.setName("消费者");
        t2.setName("生产者");
        t1.start();
        t2.start();


    }
}
package com.faxont.waitandnotify;

public class Foodie implements Runnable{
    private Desk desk;

    public Foodie(Desk desk) {
        this.desk = desk;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (desk.obj){
                if(desk.count == 0 && desk.hasFood == 0){
                    break;
                }
                if(desk.hasFood == 0){
                    try {
                        desk.obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + "正在吃,还能再吃" + desk.count + "碗");
                desk.hasFood = 0;
                System.out.println(Thread.currentThread().getName() + "吃完了");
                desk.obj.notify();
            }
        }
    }
}
package com.faxont.waitandnotify;

public class Cook implements Runnable{
    private Desk desk;

    public Cook(Desk desk) {
        this.desk = desk;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (desk.obj){
                if(desk.count == 0){
                    break;
                }
                if(desk.hasFood == 1){
                    try {
                        desk.obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println(Thread.currentThread().getName() + "开始做菜");
                desk.hasFood = 1 ;
                System.out.println(Thread.currentThread().getName() + "做菜完毕,放到了桌上");
                desk.count--;
                desk.obj.notify();
            }
        }
    }
}
package com.faxont.waitandnotify;

public class Desk {
    /**
     * 作用:控制生产者和消费者的执行
     */

    // 0-表示没有食物,1-表示有食物
    public int hasFood = 0;

    // 总个数
    public int count = 10;

    // 锁对象
    public Object obj = new Object();
}

等待唤醒机制(阻塞队列方式实现)

put 数据时:放不进去,会等着,也叫做阻塞。

take 数据时:取出第一个数据,取不到会等着,也叫做阻塞。

package com.faxont.waitandnotify2;

import java.util.concurrent.ArrayBlockingQueue;

public class ThreadDemo {
    public static void main(String[] args) {
        /*
            需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
            细节:
                生产者和消费者必须使用同一个阻塞队列
         */

        // 1. 创建阻塞队列的对象
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);

        // 2. 创建生产者和消费者
        Cook cook = new Cook(queue);
        Foodie foodie = new Foodie(queue);

        Thread t1 = new Thread(cook);
        Thread t2 = new Thread(foodie);

        t1.setName("厨师");
        t2.setName("吃货");
        t1.start();
        t2.start();
    }
}
package com.faxont.waitandnotify2;

import java.util.concurrent.ArrayBlockingQueue;

public class Cook implements Runnable{

    ArrayBlockingQueue<String> queue;

    public Cook(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                queue.put("面条");
                System.out.println(Thread.currentThread().getName() + "放了一碗面条");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
package com.faxont.waitandnotify2;

import java.util.concurrent.ArrayBlockingQueue;

public class Foodie implements Runnable{

    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(Thread.currentThread().getName() + "吃掉了一个" + food);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

综合练习

练习:多线程练习1(卖电影票)

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

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

package com.faxont.com.practice1;

public class Test {
    public static void main(String[] args) {
        /*
            一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒
            要求:请用多线程模拟卖票过程并打印剩余电影票的数量
         */

        MovieTickerSellRunnable mt = new MovieTickerSellRunnable();
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t1.start();
        t2.start();
    }
}
package com.faxont.com.practice1;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MovieTickerSellRunnable implements Runnable{

    private int ticketCount = 1000;
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try{
                if(ticketCount > 0){
                    System.out.println(Thread.currentThread().getName() + "卖出一票,剩余" + ticketCount + "张电影票");
                    ticketCount--;
                }else{
                    break;
                }
            }finally{
                lock.unlock();
            }
            try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
        }
    }
}

练习:多线程练习2(送礼品)

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

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

package com.faxont.com.practice2;

public class Test {
    public static void main(String[] args) {
        /*
            有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出
            利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来
         */

        GiftsSendRunnable gsr = new GiftsSendRunnable();
        Thread t1 = new Thread(gsr);
        Thread t2 = new Thread(gsr);
        t1.setName("Faxont");
        t2.setName("Scroll");
        t1.start();
        t2.start();
    }
}
package com.faxont.com.practice2;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class GiftsSendRunnable implements Runnable{
    private int giftCount = 100;
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while(true){
            lock.lock();
            try{
                if(giftCount >=10){
                    System.out.println(Thread.currentThread().getName() + "送出礼物,剩余" + giftCount + "个礼物");
                    giftCount--;
                }else{
                    break;
                }
            }finally {
                lock.unlock();
            }
            try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        }

    }
}

练习:多线程练习3(打印奇数数字)

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

要求:将输出所有的奇数

package com.faxont.com.practice3;

public class Test {
    public static void main(String[] args) {
        /*
            同时开启两个线程,共同获取1-100之间的所有数字
            要求:将输出所有的奇数
         */

        PrintOddRunnable por = new PrintOddRunnable();
        Thread t1 = new Thread(por);
        Thread t2 = new Thread(por);
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
    }
}
package com.faxont.com.practice3;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class PrintOddRunnable implements Runnable{
    private int num = 100;
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while(true){
            lock.lock();
            try{
                if(num > 0){
                    if(num % 2 == 1){
                        System.out.println(Thread.currentThread().getName() + "打印了" + num);
                    }
                    num--;
                }else{
                    break;
                }
            }finally {
                lock.unlock();
            }
            try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        }

    }
}

练习:多线程练习4(抢红包)

抢红包也用到了多线程

假设:100块,分成了3个包,现在有5个人去抢

其中,红包是共享数据

5个人是5条线程

打印结果如下:

XXX抢到了XXX元

XXX抢到了XXX元

XXX抢到了XXX元

XXX没抢到

XXX没抢到

package com.faxont.com.practice4;

public class Test {
    public static void main(String[] args) {
        /*
            抢红包也用到了多线程
            假设:100块,分成了3个包,现在有5个人去抢
            其中,红包是共享数据
            5个人是5条线程
            打印结果如下:
            XXX抢到了XXX元
            XXX抢到了XXX元
            XXX抢到了XXX元
            XXX没抢到
            XXX没抢到
         */

        HongBaoRunnable hbr = new HongBaoRunnable();
        Thread t1 = new Thread(hbr);
        Thread t2 = new Thread(hbr);
        Thread t3 = new Thread(hbr);
        Thread t4 = new Thread(hbr);
        Thread t5 = new Thread(hbr);

        t1.setName("路人甲");
        t2.setName("路人乙");
        t3.setName("路人丙");
        t4.setName("路人丁");
        t5.setName("路人戊");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}
package com.faxont.com.practice4;

import java.text.DecimalFormat;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class HongBaoRunnable implements Runnable{
    private double money = 100.00;
    private int count = 3;
    private Lock lock = new ReentrantLock();
    private int index = 0;
    private double[] moneyArr;
    private static final double MIN_MONEY = 0.01;
    // 格式化工具:保留两位小数
    private static final DecimalFormat df = new DecimalFormat("#.00");

    public HongBaoRunnable() {
        moneyArr = getMoney(money, count);
    }

    @Override
    public void run() {
        lock.lock();
        try {
            if(index < count){
                System.out.println(Thread.currentThread().getName() + "抢到了" + moneyArr[index] + "元");
                index++;
            }else{
                System.out.println(Thread.currentThread().getName() + "没抢到");
            }
        }finally{
            lock.unlock();
        }
    }

    private double[] getMoney(double totalMoney, int count){
        double[] result = new double[count];
        Random r = new Random();
        double leftMoney = totalMoney;
        for (int i = 0; i < count - 1; i++) {
            // df.format(...)会返回字符串,所以需要 Double.parseDouble 将其转换为 double 类型的数据
            double num = Double.parseDouble(df.format(r.nextDouble(leftMoney - MIN_MONEY * (count - i - 1))));
            if(num < MIN_MONEY){
                num = MIN_MONEY;
            }
            result[i] = num;
            leftMoney -= num;
        }
        result[count - 1] = Double.parseDouble(df.format(leftMoney));
        return result;
    }
}

练习:多线程练习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 元大奖

...

package com.faxont.com.practice5;

public class Test {
    public static void main(String[] args) {
        /*
            有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80,300,700}
            创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
            随机从抽奖池中获取奖项元素并打印再控制台上,格式如下:
            每次抽出一个奖项就打印一个(随机)
            抽奖箱1 又产生了一个 10 元大奖
            抽奖箱1 又产生了一个 100 元大奖
            抽奖箱1 又产生了一个 200 元大奖
            抽奖箱1 又产生了一个 800 元大奖
            抽奖箱2 又产生了一个 700 元大奖
            ...
         */

        LotteryRunnable lr = new LotteryRunnable();
        Thread t1 = new Thread(lr);
        Thread t2 = new Thread(lr);

        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");
        t1.start();
        t2.start();
    }
}
package com.faxont.com.practice5;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LotteryRunnable implements Runnable{
    private static final List<Integer> lottery = new ArrayList<>();
    private int index = 0;
    private Lock lock = new ReentrantLock();
    private static final Random r = new Random();

    static {
        int[] arr = {10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700};
        for (int num : arr) {
            lottery.add(num);
        }
    }

    @Override
    public void run() {
        while(true){
            lock.lock();
            try{
                if(lottery.isEmpty()){
                    break;
                }
                int randomIndex = r.nextInt(lottery.size());
                int prize = lottery.remove(randomIndex);
                System.out.println(Thread.currentThread().getName() + "又产生了一个 " + prize + " 元大奖");
                index++;
            }finally{
                lock.unlock();
            }
            try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        }
    }
}

练习:多线程练习6(多线程统计并求最大值)

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

每次抽的过程中,不打印,抽完一次性打印(随机)

在此次抽奖过程中,抽奖箱1总共产生了6个奖项

​ 分别为:10, 20, 100, 500, 2, 300最高奖项为500元,总计饿为932元

在此次抽奖过程中,抽奖箱2总共产生了6个奖项

​ 分别为:5, 50, 200, 800, 80, 700最高奖项为800元,总计额为1835元

package com.faxont.com.practice6;

public class Test {
    public static void main(String[] args) {
        /*
            有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80,300,700}
            创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
            随机从抽奖池中获取奖项元素并打印再控制台上,格式如下:
            每次抽出一个奖项就打印一个(随机)
            抽奖箱1 又产生了一个 10 元大奖
            抽奖箱1 又产生了一个 100 元大奖
            抽奖箱1 又产生了一个 200 元大奖
            抽奖箱1 又产生了一个 800 元大奖
            抽奖箱2 又产生了一个 700 元大奖
            ...

            在上一题基础上继续完成如下需求:
            每次抽的过程中,不打印,抽完一次性打印(随机)
            在此次抽奖过程中,抽奖箱1总共产生了6个奖项
            分别为:10, 20, 100, 500, 2, 300最高奖项为500元,总计饿为932元
            在此次抽奖过程中,抽奖箱2总共产生了6个奖项
            分别为:5, 50, 200, 800, 80, 700最高奖项为800元,总计额为1835元
         */

        LotteryRunnable lr1 = new LotteryRunnable();
        LotteryRunnable lr2 = new LotteryRunnable();
        Thread t1 = new Thread(lr1);
        Thread t2 = new Thread(lr2);
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");
        t1.start();
        t2.start();
    }
}
package com.faxont.com.practice6;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LotteryRunnable implements Runnable{
    private static final List<Integer> lottery = new ArrayList<>();
    private static volatile int index = 0;
    private static final Lock lock = new ReentrantLock();
    private final List<Integer> prizes = new ArrayList<>();
    private static final Random r = new Random();

    static {
        int[] arr = {10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700};
        for (int num : arr) {
            lottery.add(num);
        }
    }

    @Override
    public void run() {
        while(true){
            lock.lock();
            try{
                if(lottery.isEmpty()){
                    break;
                }
                int randomIndex = r.nextInt(lottery.size());
                int prize = lottery.remove(randomIndex);
                prizes.add(prize);
            }finally {
                lock.unlock();
            }
            try { Thread.sleep(10); } catch (InterruptedException e) {}
        }
        printLottery();
    }

    private void printLottery(){
        String name = Thread.currentThread().getName();
        int count = prizes.size();
        int max = Collections.max(prizes);
        int sum = prizes.stream().mapToInt(Integer::intValue).sum();
        System.out.println("在此次抽奖过程中," + name + "总共产生了" + count + "个奖项\n分别为" + prizes + "\s最高奖项为" + max + "元,总计额为" + sum + "元");
    }
}

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

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

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

​ 最高奖项为300元,总计额为932元

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

​ 最高奖项为800元,总计额为1835元

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

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

package com.faxont.com.practice7;

public class Test {
    public static void main(String[] args) {
        /*
            有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80,300,700}
            创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
            随机从抽奖池中获取奖项元素并打印再控制台上,格式如下:
            每次抽出一个奖项就打印一个(随机)
            抽奖箱1 又产生了一个 10 元大奖
            抽奖箱1 又产生了一个 100 元大奖
            抽奖箱1 又产生了一个 200 元大奖
            抽奖箱1 又产生了一个 800 元大奖
            抽奖箱2 又产生了一个 700 元大奖
            ...

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

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

        LotteryRunnable lr1 = new LotteryRunnable();
        LotteryRunnable lr2 = new LotteryRunnable();
        Thread t1 = new Thread(lr1);
        Thread t2 = new Thread(lr2);
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        int max1 = lr1.getMaxPrize();
        int max2 = lr2.getMaxPrize();

        if(max1 > max2){
            System.out.println("在此次抽奖过程中," + t1.getName() + "中产生了最大奖项,该奖项金额为" + max1 + "元");
        }else{
            System.out.println("在此次抽奖过程中," + t2.getName() + "中产生了最大奖项,该奖项金额为" + max2 + "元");
        }
    }
}
package com.faxont.com.practice7;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LotteryRunnable implements Runnable{
    private static final List<Integer> lottery = new ArrayList<>();
    private static volatile int index = 0;
    private static final Lock lock = new ReentrantLock();
    private final List<Integer> prizes = new ArrayList<>();
    private static final Random r = new Random();

    static {
        int[] arr = {10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700};
        for (int num : arr) {
            lottery.add(num);
        }
    }

    @Override
    public void run() {
        while(true){
            lock.lock();
            try{
                if(lottery.isEmpty()){
                    break;
                }
                int randomIndex = r.nextInt(lottery.size());
                int prize = lottery.remove(randomIndex);
                prizes.add(prize);
            }finally {
                lock.unlock();
            }
            try { Thread.sleep(10); } catch (InterruptedException e) {}
        }
        printLottery();
    }

    private void printLottery(){
        String name = Thread.currentThread().getName();
        int count = prizes.size();
        int max = Collections.max(prizes);
        int sum = prizes.stream().mapToInt(Integer::intValue).sum();
        System.out.println("在此次抽奖过程中," + name + "总共产生了" + count + "个奖项\n分别为" + prizes + "\s最高奖项为" + max + "元,总计额为" + sum + "元");
    }

    public int getMaxPrize(){
        return Collections.max(prizes);
    }
}

线程池

以前写多线程的弊端

弊端1:用到线程的时候就创建

弊端2:用完之后线程消失

线程池的核心原理

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

线程池代码实现

  1. 创建线程池

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

    方法名称 说明
    public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池
    public static ExecutorService newFixedThreadPool(int nThreads) 创建有上限的线程池
  2. 提交任务

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

package com.faxont.threadpool1;

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

public class ThreadPoolDemo {
    public static void main(String[] args) {
        /*
            public static ExecutorService newCachedThreadPool()                 创建一个没有上限的线程池
            public static ExecutorService newFixedThreadPool(int nThreads)      创建有上限的线程池
         */

        // 1. 获取线程池对象
        ExecutorService pool1 = Executors.newCachedThreadPool();
        ExecutorService pool2 = Executors.newFixedThreadPool(3);

        // 2. 提交任务
//        pool1.submit(new MyRunnable());// 线程名为 pool-1-thread-1
//        pool1.submit(new MyRunnable());
//        pool1.submit(new MyRunnable());
//        pool1.submit(new MyRunnable());
//        pool1.submit(new MyRunnable());

        // 能提交多个线程任务,但是线程池里只有三个线程
        pool2.submit(new MyRunnable());// 线程名为 pool-2-thread-1
        pool2.submit(new MyRunnable());
        pool2.submit(new MyRunnable());
        pool2.submit(new MyRunnable());
        pool2.submit(new MyRunnable());

        // 3. 销毁线程池
//        pool1.shutdown();
    }
}
package com.faxont.threadpool1;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}

自定义线程池(任务拒绝策略)

任务拒绝策略 说明
ThreadPoolExecutor.AbortPolicy 默认策略:丢弃任务并抛出 RejectedExecutionException 异常
ThreadPoolExecutor.DiscardPolicy 丢弃任务,但是不抛出异常,这是不推荐的做法
ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务,然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy 调用任务的 run() 方法绕过线程池直接执行
package com.faxont.threadpool2;

import java.util.concurrent.*;

public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        /*
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
            (核心线程数量,最大线程数,空间线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

            参数一:核心线程数量          不能小于0
            参数二:最大线程数量          不能小于等于0,最大数量 >= 核心线程数量
            参数三:空间线程最大存活时间    不能小于0
            参数四:时间单位              用 TimeUnit 指定
            参数五:任务队列              不能为 null
            参数六:创建线程工厂          不能为 null
            参数七:任务的拒绝策略         不能为 null
         */

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                3,// 核心线程数量, 不能小于0
                6,// 最大线程数量, 不能小于等于0, 最大数量 >= 核心线程数量
                60,// 空间线程最大存活时间, 不能小于0
                TimeUnit.SECONDS,// 时间单位
                new ArrayBlockingQueue<>(3),// 任务队列
                Executors.defaultThreadFactory(),// 创建线程工厂
                new ThreadPoolExecutor.AbortPolicy()// 任务的拒绝策略
        );
    }
}

自定义线程池小结

  1. 创建一个空的池子
  2. 有任务提交时,线程池会创建线程去执行任务,执行完毕归还线程

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

  1. 当核心线程满时,再提交任务就会排队
  2. 当核心线程满,队伍满时,会创建临时线程
  3. 当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略

线程池多大合适呢?

屏幕截图 2026-05-14 135245

package com.faxont.threadpool2;

public class ThreadPoolDemo2 {
    public static void main(String[] args) {
        // 向 Java 虚拟机返回可用处理器的数目
        int count = Runtime.getRuntime().availableProcessors();
        System.out.println(count);
    }
}
posted @ 2026-05-14 14:01  弋湖  阅读(17)  评论(0)    收藏  举报