哈士奇发布多线程笔记

1、线程的第一种实现方式 继承Thread类

1.1、先创建一个类继承Thread

public class MyThread extends Thread {
    @Override
    public void run() {
        // 书写要执行的代码
        for (int i = 0; i < 20; i++) {
            System.out.println(this.getName() + "hello");
        }
    }
}

1.2、在main方法进行测试

public class ThreadDemo {
    public static void main(String[] args) {
        /**
         * 多线程的第一种启动方式
         *  1、定义一个类继承 Thread
         *  2、重写 run 方法
         *  3、创建类的对象,并启动线程
         */
        MyThread myThread = new MyThread();
        MyThread myThread1 = new MyThread();
        myThread.setName("线程一");
        myThread1.setName("线程二");
        myThread.start();
        myThread1.start();
    }
}

2、线程的第二种实现方式 实现Runnable接口

2.1、定义一个类实现Runnable接口 并 重写里面的run方法

public class MyRun implements Runnable{

    @Override
    public void run() {
        // 书写线程执行的方法
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() +"hello");
        }
    }
}

2.2、创建自己类的对象 并 创建一个Thread类的对象,并启动线程

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

        /**
         * 多线程的第二种启动方式
         *  1、定义一个类实现Runnable接口
         *  2、重写里面的run方法
         *  3、创建自己类的对象
         *  4、创建一个Thread类的对象,并启动线程
         */

        // 创建 MyRun 的对象
        // 表示多线程要执行的任务
        MyRun myRun = new MyRun();
        // 创建线程对象
        Thread t1 = new Thread(myRun);
        Thread t2 = new Thread(myRun);
        t1.setName("run---1");
        t2.setName("run---2");
        // 开启线程
        t1.start();
        t2.start();

    }
}

3、线程的第三种实现方式 实现 Callable接口

3.1、创建一个类 MyCallable 实现 Callable 接口,重写call方法

// 1、创建一个类 MyCallable 实现 Callable 接口
public class MyCallable implements Callable<Integer> {

    // 2、重写 call (是有返回值的,表示多线程运行结果)
    @Override
    public Integer call() throws Exception {
        // 求 1-100之间的和
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

3.2、创建 MyCallable的对象,创建 FutureTask的对象,创建 Thread 类的对象

public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        /**
         * 多线程的第三种实现方式 【可以获取多线程运行结果】
         *  1、创建一个类 MyCallable 实现 Callable 接口
         *  2、重写 call (是有返回值的,表示多线程运行结果)
         *  3、创建 MyCallable的对象(表示多线程要执行的任务)
         *  4、创建 FutureTask的对象(作用管理多线程运行的结果)
         *  5、创建 Thread 类的对象(并启动表示线程)
         */

        // 3、创建 MyCallable的对象(表示多线程要执行的任务)
        MyCallable myCallable = new MyCallable();
        // 4、创建 FutureTask的对象(作用管理多线程运行的结果)
        FutureTask<Integer> ft = new FutureTask<Integer>(myCallable);
        // 5、创建 Thread 类的对象(并启动表示线程)
        Thread t1 = new Thread(ft);
        // 启动
        t1.start();
        // 获取返回结果并打印
        Integer result = ft.get();
        System.err.println("result=" + result);
    }
}

使用Thread类

优点:编写相对简单,访问当前线程直接使用this就可以获得当前线程。

缺点:由于线程类已经继承了Thread类,所以不能再继承其他的父类。

使用Runnable接口

优点:没有继承Thread类,所以可以继承其他的类,适合多线程访问同一资源的情况,将 CPU 和数据分开,形成清晰的模型,较好的体现了面向对象的思想

缺点:编程相对复杂,要想获得当前线程对象,需要使用 Thread.currentThread() 方法。

使用callable接口

优点:也可以继承其他的类,多线程可以共享同一个target对象,适合多线程访问同一资源的情况,将cpu和数据分开,形成清晰的模型,较好的体现了面向对象的思想,还有返回值

缺点:编程稍显复杂,如果要访问当前线程,则必须使用 Thread.currentThread() 方法。

4、多线程常用成员方法

方法名功能说明
getName()获取线程名
setName(String name)修改线程名
static currentThread()获取当前正在执行的线程
static sleep(long millis, int nanos)使得当前线程阻塞millis毫秒加上纳秒
setPriority(int newPriority)设置线程的优先级
final int getPriority()获取线程的优先级
final void setDaemon(boolean on)设置为守护线程【当其他非守护线程结束,守护线程会陆续结束】
public static void yield()出让线程 / 礼让线程
public final void join()插入线程 / 插队线程

5、线程的生命周期

新建、就绪、运行、等待、销毁~

6、线程的安全问题

6.1、方式一 同步代码块

public class MyThread extends Thread {

    // 共享 ticket 数据
    static int ticket = 0;
    // 锁对象,一定要唯一的
    // static Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            // 同步代码块
            synchronized (MyThread.class) {
                if (ticket < 100) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    ticket++;
                    System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
                } else {
                    break;
                }
            }
        }
    }
}

6.2、同步方法

同步代码块 抽取出来形成同步方法

public class MyRunnable implements Runnable {

    int ticket = 0;

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

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

6.3、Lock 锁

public class MyThread extends Thread {

    // 共享 ticket 数据
    static int ticket = 0;

    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            // 同步代码块
//            synchronized (MyThread.class) {
            lock.lock();
            try {
                if (ticket == 100) {
                    break;
                } else {
                    Thread.sleep(100);
                    ticket++;
                    System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
//            }
        }
    }
}

6.4、死锁

public class MyThread extends Thread {

    static Object objA = new Object();
    static Object objB = new Object();

    @Override
    public void run() {
        // 循环
        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())) {
                synchronized (objB) {
                    System.out.println("线程B拿到了A锁,准备拿A锁");
                    synchronized (objA) {
                        System.out.println("线程B拿到了A锁,顺利执行完一轮");
                    }
                }
            }
        }
    }
}

结论:不要两个锁嵌套使用

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

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

7.1、消费者等待

消费者:判断桌子上是否有食物,如果没有就等待;

生产者:制作食物,把食物放在桌子上,叫醒等待的消费者开吃

7.2、生产者等待

生产者:判断桌子上是否有食物,如果有就等待,没有就制作食物,把食物放在桌子上,叫醒等待的消费者开吃;

消费者:判断桌子上是否有食物,如果没有就等待,有就开吃,吃完之后,唤醒厨师继续做

步骤1 创建厨师线程

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) {
                            throw new RuntimeException(e);
                        }
                    } else {
                        // 如果没有,就制作食物
                        System.out.println(getName() + "做了一碗面条");
                        // 修改桌子的状态
                        Desk.foodFlag = 1;
                        // 修改完成后,唤醒消费者开吃
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}

步骤2 创建吃货线程

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

步骤3 创建一个共享类

public class Desk {

    /**
     * 作用:控制生产者和消费者的执行
     */
    // 桌上是否有食物  0 无  1 有
    public static int foodFlag = 0;
    // 总个数
    public static int count = 10;
    // 锁对象
    public static Object lock = new Object();
}

步骤4 创建主进程 【执行】

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

        /**
         * 需求:完成生产者和消费者(等待唤醒机制)
         *  实现线程轮流交替执行的效果
         */

        // 创建线程对象
        Cook cook = new Cook();
        Foodie foodie = new Foodie();
        cook.setName("厨师");
        foodie.setName("吃货");
        cook.start();
        foodie.start();
    }
}

7.3、阻塞队列实现 【模糊人事~】

厨师线程

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方法自带锁
                queue.put("面条");
                System.out.println("厨师放了一碗面条");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

吃货线程

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

主程序

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

        /**
         * 需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)
         * 细节:
         *  生产者和消费者必须使用同一个阻塞队列
         */

        // 1、创建阻塞队列的对象
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(10);
        // 2、创建线程的对象,并把阻塞队列传递过去
        Cook c = new Cook(queue);
        Foodie f = new Foodie(queue);
        // 3、开启线程
        c.start();
        f.start();
    }
}

8、线程的状态

新增、就绪、运行、阻塞、等待、计时等待、死亡

9、测试

9.1、抢红包

public class MyThread extends Thread {

    // 共享数据【100块,3个红包】
    static int money = 100;
    static int count = 3;
    // 最小的中奖金额【0.01】,因为jdk17才可以,所以我定义最小金额为1
    static final int MIN = 1;

    @Override
    public void run() {
        // 循环【因为每人只抢一次,所以没有必要循环】
        // 同步代码块
        synchronized (MyThread.class) {
            if (count == 0) {
                // 判断共享数据是否已经到末尾【已经到末尾】
                System.out.println(getName() + "没有抢到红包!");
            } else {
                // 判断共享数据是否已经到末尾【没到末尾】
                // 不能直接随机【要扣除每一次的】
                // 定义一个变量,表示中奖的金额
                int prize = 0;
                if (count == 1) {
                    // 此时是最后一个红包【无需随机,直接拿走】
                    prize = money;
                } else {
                    // 表示还没到最后一次【随机】
                    Random r = new Random();
                    int bounds = money - (count-1)*MIN;
                    prize = r.nextInt(bounds);
                    // 判断是否会小于最小值
                    if (prize < MIN) {
                        prize = MIN;
                    }
                }
                // 去除这次获奖的金额
                money = money - prize;
                // 红包个数减一
                count--;
                // 本次红包的信息进行打印
                System.out.println(getName() + "抢到了" + prize + "元");
            }
        }
    }
}

 主程序

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

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

        t3.setName("王昭君");
        t4.setName("兰陵王");
        t5.setName("钟馗");
        t2.setName("阿轲");
        t1.setName("安琪拉");
        
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

9.2、抽奖池【初始态】

直接输出抽奖箱抽中的奖金

public class MyThread extends Thread {

    // {10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700};
    // 选择集合
    ArrayList<Integer> list;

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

    @Override
    public void run() {
        while (true) {
            synchronized (MyThread.class) {
                if (list.size() == 0) {
                    break;
                } else {
                    // 继续抽奖
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    System.out.println(getName() + "产生了一个"
                    + prize + "元大奖");
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

9.3、抽奖池【改良态】

现将抽中的奖金存入集合中,待抽奖完毕后一次性打印,并获取最高奖金

public class MyThread extends Thread {

    // {10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700};
    // 选择集合
    ArrayList<Integer> list;

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

    // 线程一 所使用的的集合
    static ArrayList<Integer> list1 = new ArrayList<Integer>();
    // 线程二 所使用的的集合
    static ArrayList<Integer> list2 = new ArrayList<Integer>();

    @Override
    public void run() {
        while (true) {
            synchronized (MyThread.class) {
                if (list.size() == 0) {
                    // 判断是哪一个线程打印
                    if ("抽奖箱A".equals(getName())) {
                        System.out.println("抽奖箱A 中奖金额分别是" + list1 + ",最高金额是" + Collections.max(list1));
                    } else {
                        System.out.println("抽奖箱B 中奖金额分别是" + list2 + ",最高金额是" + Collections.max(list2));
                    }
                    break;
                } else {
                    // 继续抽奖
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    // 判断是哪一个线程抽奖
                    if ("抽奖箱A".equals(getName())) {
                        list1.add(prize);
                    } else {
                        list2.add(prize);
                    }
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

9.4、抽奖池【最终态】

抽奖箱获取抽奖池中的最大值

public class MyCallable implements Callable<Integer> {

    // {10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700};
    // 选择集合
    ArrayList<Integer> list;

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

    @Override
    public Integer call() throws Exception {
        ArrayList<Integer> boxList = new ArrayList<Integer>();
        while (true) {
            synchronized (MyCallable.class) {
                if (list.size() == 0) {
                    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);
        }
    }
}

主线程

public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

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

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

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

        // 创建线程对象
        Thread t1 = new Thread(ft1);
        Thread t2 = new Thread(ft2);
        // 设置名字
        t1.setName("抽奖箱A");
        t2.setName("抽奖箱B");
        // 启动线程
        t1.start();
        t2.start();

        Integer max1 = ft1.get();
        Integer max2 = ft2.get();

        System.out.println("抽奖箱A中最大奖金为" + max1);
        System.out.println("抽奖箱B中最大奖金为" + max2);
        if (max1 > max2) {
            System.out.println("抽奖箱A获得抽奖池中最大奖金为" + max1);
        } else {
            System.out.println("抽奖箱B获得抽奖池中最大奖金为" + max2);
        }
    }
}

10、线程池

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

方法名称说明
public static ExecutorService newCachedThreadPool()创建一个没有上限的线程池【Int的最大值】
public static ExecutorsService newFixedThreadPool(int nThreads)创建有上限的线程池
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、销毁线程池
//        pool1.shutdown();

         */

        // 1、获取指定数量线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(3);
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
    }
}

核心原理

  • 创建一个池子,池子中是空的。

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

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

10.1、线程池详解

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

任务拒绝策略说明
ThreadPoolExecutor.AbortPolicy默认策略:丢弃任务并抛出RejectedEexcutionException异常
ThreadPoolExecutor.DiscardPolicy丢弃任务,但是不抛出异常,不推荐做法
ThreadPoolExecutor.DiscardOldestPolicy抛弃队列中等待最久的任务,然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy调用任务的run()方法绕过线程池直接执行
public class MyThreadPoolDemo {
    public static void main(String[] args) {

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

小结 【不断的提交任务,会有以下三个临界点】

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

  • 当核心线程满,队列满时,会创建临时线程

  • 当核心线程满,队列满,临时线程满时,会触发任务拒绝策略

10.2、获取最大并行数

public class MyThreadPoolDemo {
    public static void main(String[] args) {
        // 向 Java 虚拟机返回可用处理器的数目
        int count = Runtime.getRuntime().availableProcessors();
        System.out.println(count);
    }
}

10.3、线程池多大合适

可分为 CPU密集型运算I/O密集型运算

  • CPU密集型运算

    最大并行数 + 1
  • I/O密集型运算

    最大并行数 * 期望CPU利用率 * (总时间【CPU计算时间 + 等待时间】)/ CPU计算时间
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值