线程 和 进程详解——以Java为例

news2024/10/6 3:40:47

一、概念

线程

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

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

进程

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

多线程中的两个概念:并发和并行

并发

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

并行

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

二、多线程的实现方式

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

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

public class MyThread extends Thread{
    @Override
    public void run() {
        // 书写线程要执行的代码
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + ":Hello, World!");
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        /*
        * 多线程的第一种启动方式
        * 1. 定义一个类继承Thread
        * 2. 重写run方法
        * 3. 创建子类的对象,并启动线程
        * */

        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
    }
}

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

public class MyThread02 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            // 获取当前线程的对象
            Thread t = Thread.currentThread();
            System.out.println(t.getName() + ":Hello, World!");
        }
    }
}
public class ThreadDemo02 {
    public static void main(String[] args) {
        /*
        * 多线程的第二种启动方式
        * 1. 自己定义一个类实现Runnable接口
        * 2. 重写里面的run方法
        * 3. 创建自己的这个类的对象
        * 4. 创建一个Thread类的对象,启动线程
        * */

        // 创建MyThread02的对象
        // 表示多线程要执行的任务
        MyThread02 mt = new MyThread02();
        // 创建线程对象
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        // 给线程设置名字
        t1.setName("线程1");
        t2.setName("线程2");

        // 启动线程
        t1.start();
        t2.start();
    }
}

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

import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        // 求1~100之间的和
        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 ThreadDemo03 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /*
        * 多线程的第三种实现方式
        * 特点:可以获取到多线程执行的结果
        * 1. 创建一个MyCallable实现Callable接口
        * 2. 重写call(是有返回值的,表示多线程允许的结果)
        *
        * 3. 创建MyCallable的对象(表示多线程要执行的任务)
        * 4. 创建FutureTask的对象(作用:管理多线程运行的结果)
        * 5. 创建Thread类的对象(表示线程)
        * 6. 启动线程
        * */

        // 创建MyCallable的对象(表示多线程要执行的任务)
        MyCallable mc = new MyCallable();
        // 创建FutureTask的对象(作用:管理多线程运行的结果)
        FutureTask<Integer> ft = new FutureTask<>(mc);
        // 创建线程的对象
        Thread t1 = new Thread(ft);
        // 启动线程
        t1.start();
        // 获取多线程运行的结果
        Integer result = ft.get();
        System.out.println(result);  // 5050

    }
}

三、常见的成员方法

方法名称说明
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()插让线程/插队线程

示例一:前四个方法

public class MyThread extends Thread{
    public MyThread() {
    }

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

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(getName() + "@" + i);
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        /*
        String getName()                返回此线程的名称
        细节:1. 如果我们没有给线程设置名字,线程也是有默认的名字的。格式:Thread-X(X:序号, 从0开始)
        void setName(String name)       设置线程的名字
        细节2:如果我们要给线程设置名字,可以用set方法进行设置,也可以使用构造方法设置
        static Thread currentThread()   获取当前线程的对象
        细节3:当JVM虚拟机启动之后,会自动启动多条线程,其中有一条线程就叫做main线程
                它的作用就是去调用main方法,并执行里面的代码。
                在以前,我们写的代码,其实都是运行在main线程当中
        static void sleep(long time)    让线程休眠指定的时间,单位为毫秒
        细节4:①哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间;
                ②方法的参数:表示睡眠的时间,单位毫秒;
                ③当时间到了之后,线程会自动的醒来,继续执行下面的其他代码
         */

        // 1. 创建线程的对象
        MyThread t1 = new MyThread("飞机");
        MyThread t2 = new MyThread("坦克");

        // 2. 开启线程
        t1.start();
        t2.start();

        // 哪条线程执行到这个方法,此时获取的就是哪条线程的对象
        Thread t = Thread.currentThread();
        String name = t.getName();
        System.out.println(name);

        System.out.println("11111111");
        Thread.sleep(5000);
        System.out.println("22222222");

    }
}

示例二:设置优先级

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        /*
        setPriority(int newPriority)            设置线程的优先级
        final int getPriority()                 获取线程的优先级
         */

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

        /*System.out.println(t1.getPriority());
        System.out.println(t2.getPriority());
        System.out.println(Thread.currentThread().getPriority());*/
        t1.setPriority(1);
        t2.setPriority(10);

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

示例三:设置守护线程

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}
public class MyThread2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}
import com.itheima.a04threadmethod.MyThread;

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

         */

        MyThread t1 = new MyThread();
        MyThread2 t2 = new MyThread2();
        t1.setName("女神");
        t2.setName("备胎");
        // 把第二个线程设置为守护线程
        t2.setDaemon(true);

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

示例四:设置礼让线程

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "@" + i);
            // 出让当前CPU的执行权
            Thread.yield();
        }
    }
}
import com.itheima.a04threadmethod.MyThread;

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

        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.setName("飞机");
        t2.setName("坦克");
        // 把第二个线程设置为守护线程
        t2.setDaemon(true);

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

示例五:插入线程

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

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

        MyThread t1 = new MyThread();
        t1.setName("土豆");
        t1.start();

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

四、线程的生命周期

五、线程的安全问题

1. 同步代码块

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

格式

synchronized(锁) {

        操作共享数据的代码

}

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

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

示例

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

public class MyThread extends Thread{
    // 表示这个类的所有对象,都共享ticket数据
    static int ticket = 0;
    // 锁对象:一定要唯一
    static Object obj = new Object();
    @Override
    public void run() {
        while(true) {
            // 同步代码块
            synchronized (obj) {
                if(ticket < 100) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    ticket++;
                    System.out.println(getName() + "正在卖第 " + ticket + " 张票!!!");
                } else {
                    break;
                }
            }
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        /*
        需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
         */

        // 创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        // 设置名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        // 开启线程
        t1.start();
        t2.start();
        t3.start();

    }
}

2. 同步方法

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

格式

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

特点

①同步方法是锁住方法里面的所有代码;

②锁对象不能自己指定。

非静态:this

静态:当前类的字节码文件对象

示例

 需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票。用同步方法完成

public class MyRunnable implements Runnable{
    int ticket = 0;
    @Override
    public void run() {
        // 1. 循环
        while (true) {
            // 2. 同步方法
            if(method()) break;
        }
    }

    private synchronized boolean method() {
        // 3. 判断共享数据是否到了末尾,如果到了末尾
        if(ticket == 100) {
            return true;
        } else {
            // 4. 判断共享数据是否到了末尾,如果没有到末尾
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            ticket++;
            System.out.println(Thread.currentThread().getName() + "在卖第 " + ticket + " 张票!!!");
        }
        return false;
    }
}
package com.itheima.a10threadsafe2;

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

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

3. Lock锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为例更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。

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

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

void lock():获得锁

void unlock():释放锁

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

ReentrantLock的构造方法

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

示例

需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票。用JDK5的lock实现

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

public class MyThread extends Thread{
    static int ticket = 0;

    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        // 1. 循环
        while(true) {
            // 2. 同步代码块
            lock.lock();
            try {
                // 3. 判断
                if(ticket == 100) {
                    break;
                } else {
                    Thread.sleep(10);
                    ticket++;
                    System.out.println(getName() + "在卖第 " + ticket + " 张票!!!");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        /*
        需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
        用JDK5的lock实现
         */

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

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

        t1.start();
        t2.start();
        t3.start();
    }
}

六、死锁

public class ThreadDemo {
    public static void main(String[] args) {
        /*
        需求:死锁
         */

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

        t1.setName("线程A");
        t2.setName("线程B");

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

    }
}
public class MyThread extends Thread{
    static Object objA = new Object();
    static Object objB = new Object();

    @Override
    public void run() {
        // 1. 循环
        while (true) {
            if("线程A".equals(getName())) {
                synchronized (objA) {
                    System.out.println("线程A拿到了A锁,准备拿B锁");
                    synchronized (objB) {
                        System.out.println("线程A拿到了B锁,顺利执行完一轮");
                    }
                }
            } else if("线程B".equals(getName())) {
                if("线程B".equals(getName())) {
                    synchronized (objB) {
                        System.out.println("线程B拿到了B锁,准备拿A锁");
                        synchronized (objA) {
                            System.out.println("线程B拿到了A锁,顺利执行完一轮");
                        }
                    }
                }
            }
        }
    }
}

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

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

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

示例一:

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

    // 是否有面条 0:没有面条  1:有面条
    public static int foodFlag = 0;

    // 总个数
    public static int count = 10;

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

}
public class Cook extends Thread{

    @Override
    public void run() {
        /*
        1. 循环
        2. 同步代码块
        3. 判断共享数据是否到了末尾(到了末尾)
        4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
         */

        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.notifyAll();
                    }
                }
            }
        }
    }
}
import sun.security.krb5.internal.crypto.Des;

public class Foodie extends Thread{

    @Override
    public void run() {
        /*
        1. 循环
        2. 同步代码块
        3. 判断共享数据是否到了末尾(到了末尾)
        4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
         */

        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 {
                        // 把吃的总数-1
                        Desk.count--;
                        // 如果有,就开吃
                        System.out.println("吃货正在吃面条,还能再吃 " + Desk.count + " 碗!!!");
                        // 吃完之后,唤醒厨师继续做
                        Desk.lock.notifyAll();
                        // 修改桌子的状态
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}
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();
    }
}

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

import java.util.concurrent.ArrayBlockingQueue;

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();
            }
        }
    }
}
import java.util.concurrent.ArrayBlockingQueue;

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

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

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

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

        // 2. 创建线程的对象,并把阻塞队列传递过去
        Cook c = new Cook(queue);
        Foodie f = new Foodie(queue);

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

八、综合练习

1. 抢红包

抢红包也用到了多线程。

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

打印结果如下:

                        XXX抢到了XXX元

                        XXX抢到了XXX元

                        XXX抢到了XXX元

                        XXX没抢到

                        XXX没抢到

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Random;

public class MyThread extends Thread{
    // 共享数据
    // 100块,分成了3个红包
    static BigDecimal money = BigDecimal.valueOf(100.0);
    static int count = 3;

    // 最小的中奖金额
    static final BigDecimal MIN = BigDecimal.valueOf(0.01);

    @Override
    public void run() {
        synchronized (MyThread.class) {
            if(count == 0) {
                System.out.println(getName() + "没抢到");
            } else {
                // 定义一个变量,表示中奖的金额
                BigDecimal prize;
                if(count == 1) {
                    // 最后一个红包
                    // 无需随机,剩余所有的钱都是中奖金额
                    prize = money;

                } else {
                    // 表示第一次、第二次(随机)
                    Random r = new Random();
                    double bounds = money.subtract(BigDecimal.valueOf(count - 1).multiply(MIN)).doubleValue();
                    prize = BigDecimal.valueOf(r.nextDouble() * bounds);
                }
                // 设置抽中红包,小数点保留两位,四舍五入
                prize = prize.setScale(2, RoundingMode.HALF_UP);
                // 从money中减去当前中奖的金额
                money = money.subtract(prize);
                // 红包数量减1
                count--;
                System.out.println(getName() + "抢到了" + prize + "元");
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        /*
        抢红包也用到了多线程。
        假设:100块,分成了3个包,现在有5个人去抢。其中,红包是共享数据,5个人是5条线程。
        打印结果如下:
                 XXX抢到了XXX元
                 XXX抢到了XXX元
                 XXX抢到了XXX元
                 XXX没抢到
                 XXX没抢到
         */

        // 创建5个线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        MyThread t4 = new MyThread();
        MyThread t5 = new MyThread();

        t1.setName("小A");
        t2.setName("小B");
        t3.setName("小C");
        t4.setName("小D");
        t5.setName("小E");

        // 启动线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

2. 抽奖箱抽奖

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

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

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

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

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

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

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

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

        ……

import java.util.ArrayList;
import java.util.Collections;

public class MyThread extends Thread{
    // [10, 5, 20, 50, 100, 200, 500, 800, 2, 0, 300, 700]
    // 集合:去重
    ArrayList<Integer> list;

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

    @Override
    public void run() {
        while (true) {
            synchronized (MyThread.class) {
                if(list.isEmpty()) {
                    break;
                } else {
                    // 继续抽奖
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    System.out.println(getName() + "抽又产生了 " + prize + " 元大奖");
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
import java.util.ArrayList;
import java.util.Collections;

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

        // 创建奖池
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);

        // 创建线程
        MyThread t1 = new MyThread(list);
        MyThread t2 = new MyThread(list);

        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");

        // 启动线程
        t1.start();
        t2.start();
    }
}

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

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

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

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

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

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

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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

public class MyThread extends Thread{
    // [10, 5, 20, 50, 100, 200, 500, 800, 2, 0, 300, 700]
    // 集合:去重
    ArrayList<Integer> list;

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

    // 线程一
    static ArrayList<Integer> list1 = new ArrayList<>();
    // 线程二
    static ArrayList<Integer> list2 = new ArrayList<>();

    @Override
    public void run() {
        while (true) {
            synchronized (MyThread.class) {
                if(list.isEmpty()) {
                    if("抽奖箱1".equals(getName())) {
                        Integer[] arr = new Integer[list1.size()];
                        arr = list1.toArray(arr);
                        // 获取数组中的最大值
                        int max = Collections.max(Arrays.asList(arr));
                        int sum = 0;
                        for(int num : list1) {
                            sum += num;
                        }
                        System.out.println("抽奖箱1:" + list1 + ",最高奖项为:" + max + ",总计额为" + sum);
                    } else {
                        Integer[] arr = new Integer[list2.size()];
                        arr = list2.toArray(arr);
                        // 获取数组中的最大值
                        int max = Collections.max(Arrays.asList(arr));
                        int sum = 0;
                        for(int num : list2) {
                            sum += num;
                        }
                        System.out.println("抽奖箱2:" + list2 + ",最高奖项为:" + max + ",总计额为" + sum);
                    }
                    break;
                } else {
                    // 继续抽奖
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    if("抽奖箱1".equals(getName())) {
                        list1.add(prize);
                    } else {
                        list2.add(prize);
                    }
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
import java.util.ArrayList;
import java.util.Collections;

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

        // 创建线程
        MyThread t1 = new MyThread(list);
        MyThread t2 = new MyThread(list);

        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");

        // 启动线程
        t1.start();
        t2.start();
    }
}

或 线程栈

import java.util.ArrayList;
import java.util.Collections;

public class MyThread extends Thread{
    // [10, 5, 20, 50, 100, 200, 500, 800, 2, 0, 300, 700]
    // 集合:去重
    ArrayList<Integer> list;

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



    @Override
    public void run() {
        ArrayList<Integer> boxList = new ArrayList<>();
        while (true) {
            synchronized (MyThread.class) {
                if(list.isEmpty()) {
                    System.out.println(getName() + boxList);
                    break;
                } else {
                    // 继续抽奖
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    boxList.add(prize);
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
import java.util.ArrayList;
import java.util.Collections;

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

        // 创建线程
        MyThread t1 = new MyThread(list);
        MyThread t2 = new MyThread(list);

        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");

        // 启动线程
        t1.start();
        t2.start();

    }
}

4. 多线程之间的比较

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

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

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

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

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer> {
    // [10, 5, 20, 50, 100, 200, 500, 800, 2, 0, 300, 700]
    // 集合:去重
    ArrayList<Integer> list;

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


    @Override
    public Integer call() throws Exception {
        ArrayList<Integer> boxList = new ArrayList<>();
        while (true) {
            synchronized (MyCallable.class) {
                if(list.isEmpty()) {
                    System.out.println(Thread.currentThread().getName() + boxList);
                    break;
                } else {
                    // 继续抽奖
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    boxList.add(prize);
                }
            }
            Thread.sleep(10);
        }
        // 把集合中的最大值返回
        if(boxList.size() == 0) {
            return null;
        } else {
            return Collections.max(boxList);
        }
    }
}
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> 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> ft1 = new FutureTask<>(mc);
        // 线程二
        FutureTask<Integer> ft2 = new FutureTask<>(mc);

        // 创建线程对象
        Thread t1 = new Thread(ft1);
        Thread t2 = new Thread(ft2);

        // 设置名字
        t1.setName("抽奖机1");
        t2.setName("抽奖机2");

        // 开启线程
        t1.start();
        t2.start();

        // 获取结果
        Integer max1 = ft1.get();
        Integer max2 = ft2.get();

        System.out.println(max1);
        System.out.println(max2);
        // 在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元

    }
}

九、线程池

1. 核心原理

①创建一个池子,池子中是空的;

②提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下次再提交任务时,不需要创建新的线程,直接复用已有的线程即可;

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

2. 线程池代码实现

①创建线程池;

②提交任务;

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

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

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

方法一:

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

public class MyThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException {
        // 1. 获取线程池对象
        ExecutorService pool1 = Executors.newCachedThreadPool();

        // 2. 提交任务
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());

        // 3. 销毁线程池
//        poo1.shutdown();
    }
}

方法二:

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

public class MyThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException {
        // 1. 获取线程池对象
        ExecutorService pool1 = Executors.newFixedThreadPool(3);

        // 2. 提交任务
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());

        // 3. 销毁线程池
//        poo1.shutdown();
    }
}

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

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

①当核心线程满时,再提交任务就会排队;

②当核心线程满,队伍满时,会创建临时线程;

③当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略。

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

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

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyThreadPoolDemo1 {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3, // 核心线程数量,能小于0
                6, // 最大的线程数量,不能小于0,最大数量 >= 核心线程数量
                60, // 空闲线程最大存活时间
                TimeUnit.SECONDS, // 时间单位
                new ArrayBlockingQueue<>(3),  // 任务队列
                Executors.defaultThreadFactory(),  // 创建线程工厂
                new ThreadPoolExecutor.AbortPolicy()  // 任务的拒绝策略
        );
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());

        // 3. 销毁线程池
        //pool.shutdown();
    }
}

4. 合适的线程池大小

类型线程池大小
CPU密集型运算最大并行数 + 1
I/O密集型运算最大并行数 * 期望CPU利用率 * 总时间(CPU计算时间 + 等待时间)/ CPU计算时间

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1549281.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

鸿蒙OS开发实例:【工具类封装-emitter组件间通信】

import Emitter from ohos.events.emitter; import pasteboard from ohos.pasteboard; MyEmitterUtil 是一个针对 HarmonyOS 的事件驱动编程封装类&#xff0c;主要用于组件间的通信和数据传递。 使用要求&#xff1a; DevEco Studio 3.1.1 Release 或更高版本API 版本&…

vue3+threejs新手从零开发卡牌游戏(十六):初始化对方手牌

添加对方手牌区时注意位置调整&#xff0c;以及手牌应该是背面朝上&#xff0c;加个rotateX翻转即可&#xff0c;其他代码和p1.vue代码一致&#xff0c;game/hand/p2.vue代码如下&#xff1a; <template><div></div> </template><script setup lan…

C++:关键字(4)

在c中的关键字就是我们各个写的各种代码 这些就是关键字&#xff0c;这些东西是无法当参数的&#xff0c;比如在给变量名设置为int那就不行 这就是个错的 在写其他的参数时候&#xff0c;不可以使用关键词作为参数

Vue 与 React:前端框架对比分析

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

C语言程序编译与链接(拓宽视野的不二之选)

文章目录 翻译环境和运行环境翻译环境预处理编译汇编链接 运行环境 翻译环境和运行环境 1&#xff0c;在ANSI C的任何⼀种实现中&#xff0c;存在两个不同的环境。 第1种是翻译环境&#xff0c;在这个环境中源代码被转换为可执⾏的机器指 令&#xff08;⼆进制指令&#…

AI Agent(LLM Agent)入门解读

1. 什么是AI Agent&#xff1f; AI Agent可以理解为一个智能体&#xff0c;包括感知模块、规划决策模块和行动模块&#xff0c;类似于人类的五官、大脑和肢体。它能帮助人类处理复杂的任务&#xff0c;并能根据环境反馈进行学习和调整。 五官可以理解为感知模块&#xff0c;大…

2024年上半年数学建模竞赛一览表(附赠12场竞赛的优秀论文+格式要求)[电工、妈杯、数维、五一等12场]

为了帮助大家更好地备战今年上半年十二场数学建模竞赛&#xff0c;我们为大家收集到了这十二场相关竞赛的优秀论文以及格式要求&#xff0c;具体内容如下所示。 资料获取 在文末 文中资料来源 名称竞赛官方网站天府杯https://www.tfmssy.org.cn/认证杯http://www.tzmcm.cn/i…

中国土壤厚度空间分布数据

土壤层次分为覆盖层 林溶层 淀积层 母质层&#xff0c;其中在林溶层中的最上面那层就是我们通常说的土壤厚度在这一层中&#xff0c;这一层也被称为腐殖层&#xff0c;是肥力性质最好的一层&#xff0c;植物根系和微生物也集中在这一层。至于覆盖层在森林土壤中比较常见&#x…

零基础学python之高级编程(6)---Python中进程的Queue 和进程锁,以及进程池的创建 (包含详细注释代码)

Python中进程的Queue 和进程锁,以及进程池的创建 文章目录 Python中进程的Queue 和进程锁,以及进程池的创建前言一、进程间同步通信(Queue)二、进程锁&#xff08;Lock&#xff09;三、创建进程池Poorpool 类方法: End! 前言 大家好,上一篇文章,我们初步接触了进程的概念及其应…

matlab实现神经网络

一、原理 人工神经网络是具有适应性的简单神经元组成的广泛并互连的网络&#xff0c;它的组织能够模拟生物神经系统对真实世界物体作出的交互式反应。人工神经网络具有自学习、自组织、较好的容错性和优良的非线性逼近能力 将神经网络的学习能力引入到模糊系统中&#xff0c;…

Zabbix 配置使用

目录 配置流程 添加组机组 添加模板 添加主机 配置图形 配置大屏 Monitoring 配置地图 最新数据 故障 使用IT服务 使用报表 资产管理 全局搜索 导入导出 用户权限 用户组权限 用户 匿名用户 调试模式 与 LDAP 对接 维护模式 故障确认 批量更新 配置流程…

35.HarmonyOS App(ArkUI)使用父组件@Builder装饰的方法初始化子组件@BuilderParam报错

HarmonyOS App(ArkUI)使用父组件Builder装饰的方法初始化子组件BuilderParam报错 Type void is not assignable to type () > void. <tsCheck> 去掉括号()就可以了 装饰器&#xff1a; 用于装饰类、结构、方法以及变量&#xff0c;并赋予其特殊的含义。如上述示例中En…

fastadmin学习01-windows下安装部署

下载源代码 官网 安装 解压&#xff0c;然后使用phpstorm打开 修改配置文件 创建数据库 -- drop database fastadmin01; create database fastadmin01;这样fastadmin就部署好了 访问主页也能看到前台页面

基于OneAPI+ChatGLM3-6B+FastGPT搭建LLM大语言模型知识库问答系统

搭建大语言模型知识库问答系统 部署OneAPI部署一个LLM模型部署嵌入模型部署FastGPT新建FastGPT对话应用新建 FastGPT 知识库应用 部署OneAPI 拉取镜像 docker pull justsong/one-api创建挂载目录 mkdir -p /usr/local/docker/oneapi启动容器 docker run --name one-api -d …

LeetCode讲解算法2-数据结构[栈和队列](Python版)

文章目录 一、栈1.1 栈的定义1.2 栈的实现分析步骤1.3 栈的应用匹配圆括号匹配符号模2除法&#xff08;十进制转二进制&#xff09;进制转换 二、队列2.1 单向队列2.2 双端队列2.3 队列的应用验证回文串滑动窗口最大值 一、栈 1.1 栈的定义 栈是一种线性数据结构&#xff0c;栈…

STM32的SPI通信介绍

SPI简介 SPI:串行外设接口,与IIC一样都是通用数据总线。四根通信线&#xff1a;SCK&#xff0c;MOSI&#xff08;DO&#xff09;&#xff0c;MISO&#xff08;DI&#xff09;&#xff0c;SS。同步&#xff08;共用一根时钟线&#xff09;&#xff0c;全双工&#xff08;数据发…

js算法记录

> 更多请前往 https://www.passerma.com/article/86 滑动窗口 1 给定一个矩阵&#xff0c;包含N*M个整数&#xff0c;和一个包含K个整数的数组。现在要求在这个矩阵中找一个宽度最小的子矩阵&#xff0c;要求子矩阵包含数组中所有的整数 function minSubmatrixWidth(mat…

Openstack创建和操作实例,实现与外部网络通信

一、熟悉OpenStack图形界面操作 1、了解Horizon项目 Horizon项目 各OpenStack服务的图形界面都是由Horizon提供的。Horizon提供基于Web的模块化用户界面。Horizon为云管理员提供一个整体的视图。Horizon为终端用户提供一个自主服务的门户。Horizon由云管理员进行管理与控制&a…

SpringBoot学习笔记一、SpringBoot应用初创建以及应用

一、创建SpringBoot的两种方式 1.Spring Initializr方式创建 &#xff08;1&#xff09;第一步在IDEA中选择 File-->NEW-->Project &#xff0c;选择 Spring Initializr &#xff0c;指定Maven坐标、包名、指定 JDK 版本 1.8 &#xff0c;然后点击Next 。如下图&#x…

修改Jupyter Notebook的默认路径,以及在PowerShell中自定义其启动路径

修改Jupyter Notebook的默认路径&#xff0c;以及在PowerShell中自定义其启动路径 设置 Jupyter Notebook 配置文件&#xff0c;修改默认路径要在PowerShell中设置自定义的启动脚本&#xff0c;以确保Jupyter Notebook能够自动定位到当前路径设置后的效果 在使用Jupyter Notebo…