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

一、概念

线程

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

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

进程

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

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

并发

在同一时刻,有多个指令在单个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计算时间
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值