多线程(线程池、死锁、线程状态、等待与唤醒)

17 篇文章 0 订阅
2 篇文章 0 订阅

多线程基础

线程池

概念及作用

  1. 什么是“线程池”:它是一个“容器”,里面可以存储一些“线程对象”。
  2. 它的作用:
    1. 它可以“反复的”执行同一个线程对象,可以避免每次使用这个线程对象,而去创建这个线程对象。
    2. 它内部可以控制多个线程的“并发数量”;

线程池的类层次结构

  1. java.util.concurrent.Executor(接口):所有线程池的父接口
    |–java.util.concurrent.ExecutorService(接口):我们学习使用的“线程池”:
  2. 常用方法:
    1. submit(Runnable run):用于执行Runnable线程
    2. submit(Callable cal):用于执行Callable线程。
  3. 可以通过一个“Executors工具类”来获取ExecutorService的子类对象:
    java.util.concurrent.Executors(工具类)的静态方法;
    public static ExecutorService newFixedThreadPool(int nThreads)
代码演示:
package com.itheima.demo01_线程池的使用;

public class MyThread extends Thread {
    public MyThread(){
        System.out.println("构造线程对象很耗时,需要5秒...");
        for (int i = 0; i < 5; i++) {
            System.out.println("i = " + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("鼓掌" + i);

        }
    }
}
package com.itheima.demo01_线程池的使用;

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

public class Demo01 {
    public static void main(String[] args) {
        /*MyThread t1 = new MyThread();//需要5秒
        t1.start();
        MyThread t2 = new MyThread();//需要5秒
        t2.start();*/
        //改用线程池
        //1.获取线程池对象
        ExecutorService service = Executors.newFixedThreadPool(2);
        //2.创建自定义线程对象
        MyThread t  = new MyThread();//需要5秒
        //3.使用"线程池"去执行t线程
        service.submit(t);//开始执行

        //可以让线程池反复的执行同一个t线程
        service.submit(t);//开始执行

        service.submit(t);//由于核心线程只有2,所以前两个线程会"同时执行",第三个线程会排队

        //结束线程池
        service.shutdown();//会等待所有的线程执行完毕,然后再关闭。


    }
}
代码图解

在这里插入图片描述

实现线程的第三种方式:可以通过返回值的Callable接口

  1. 之前我们实现线程的两种方式:
    1. 继承Thread类,重写run()方法。
    2. 实现Runnable接口,重写run()方法。
  2. 之前的两种方式都有两个弊端:
    1. 子类重写run()方法时,不能抛出“编译期异常”;
    2. run()方法没有返回值,不能给它的启动线程返回一个值;
  3. 从JDK1.5开始,Java提供了第三种实现线程的方式:
    1. 自定义类,实现Callable接口,并重写call()方法。
    2. 启动线程(注意:启动Callable线程,必须要用线程池)
      1. 获取一个线程池对象:
      2. 使用线程池启动Callable线程,并通过Future对象获取线程的返回值;
代码演示
package com.itheima.demo02_实现线程的第三种方式_可以返回值的Callable接口;

import java.util.concurrent.Callable;

//要求:此线程要计算1--100的累加和,并返回给调用线程
public class MyThread implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100 ; i++) {
            sum += i;
            Thread.sleep(50);
        }
        System.out.println("【线程】计算完毕!");
        return sum;
    }
}
package com.itheima.demo02_实现线程的第三种方式_可以返回值的Callable接口;

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

public class Demo02 {
    public static void main(String[] args) {
        //1.获取线程池对象
        ExecutorService service = Executors.newFixedThreadPool(2);
        //2.创建自定义线程对象
        MyThread t = new MyThread();

        //3.使用线程池去执行我们的线程
        Future<Integer> future = service.submit(t);//不阻塞,启动线程
        while (!future.isDone()){//判断线程是否结束,如果没结束,就可以做点其他事情,可以避免提前调用get()而产生的阻塞。
            System.out.println("线程没执行完,可以做一些其他的事情!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            System.out.println("获取future的结果:");
            Integer result = future.get();//阻塞的,等待计算完成
            System.out.println("线程执行结果:" + result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //关闭线程池
        service.shutdown();

    }
}
代码图解

在这里插入图片描述

线程状态

线程状态概述

  1. 一个线程,从创建对象开始,到最后被销毁,中间由于调用线程的各种方法,会使线程进入不同的“状态”。

  2. 一个线程可能的几种状态:
    在这里插入图片描述
    在这里插入图片描述

等待和唤醒

  1. 等待和唤醒机制:它是“两个线程”之间相互协作的一种机制。一个线程先开始工作,当发现一些问题时,可以主动“退出—释放锁”,让另一个线程开始执行,第二个线程拿到锁后,就可以开始工作—解决问题,解决完问题后,再“唤醒-让之前等待的线程,从’无限等待’状态进入到’可运行状态’去抢锁”。这样就实现了:两个线程相互协作,共同完成一项任务。
  2. 实现前提:
    1. 要有两个线程;
    2. 两个线程要共用一把锁;
  3. 注意:
    1. wait()和notify()、notifyAll()都是Object的方法,因为“任何对象都可以做锁”,所以任何对象都可以“进入等待”。
    2. wait(long s)和sleep(long s)的区别:
      1. wait(long s)可以被唤醒;
        sleep(long s)必须时间到,然后自己醒;
      2. wait()方法会释放锁;
        sleep()方法不会释放锁。
代码演示
	public class Demo04 {
    public static void main(String[] args) throws InterruptedException {
        //1.定义一把锁
        Object obj = new Object();
        //2.定义两个线程
        //第一个线程:主工作线程
        new Thread(){
            //必须要先启动,先拿到锁
            @Override
            public void run() {
                synchronized (obj) {
                    for (int i = 0; i < 50; i++) {
                        System.out.println("白雪公主线程:i = " + i);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        if (i == 20) {
                            System.out.println("白雪公主线程 i = 20,主动退出,释放锁,进入到'无限等待状态',等待白马王子...");
                            try {
                                obj.wait();//无限等待,等待被其他线程唤醒
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }.start();
        Thread.sleep(1000);//为了保证让"白雪公主"线程先运行

        //白马王子线程
        new Thread(){
            @Override
            public void run() {
                System.out.println("【白马王子】线程启动,获取锁......");
                synchronized (obj){
                    System.out.println("【白马王子】拿到锁,终于可以娶白雪公主了!!!");
                    System.out.println("【白马王子】和白雪公主举行婚礼!");
                    try {
                        Thread.sleep(1000 * 5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("【白马王子】婚礼举行完毕,和白雪公主愉快的度过余生!");
                    //唤醒白雪公主线程,继续生活
                    obj.notifyAll();//【特别注意】唤醒,不释放锁-会让在这个锁上等待的线程,由'无限等待状态'转入到'可运行状态',去抢锁。
                }//执行完同步代码块,才释放锁
            }
        }.start();
    }
}

包子铺案例
  1. 包子铺案例:这个案例需要两个线程:一个线程无限“生产包子”,一个线程无限“获取包子”,我们会有一个集合,来存放包子。
  2. 我们的程序必须要控制:生产一个,卖出一个。不会生产多个!生产一个,来买包子的,也必须要买走一个。
    在这里插入图片描述
代码演示
  1. 生产者线程:

public class SetThread extends Thread {
    @Override
    public void run() {
        int index = 1;
        while (true) {
            synchronized (Demo05.bzList){
                if (Demo05.bzList.size() > 0) {//有包子
                    try {
                        System.out.println("【生产者】有包子,等待消费者...");
                        Demo05.bzList.wait();
                        System.out.println("【生产者】被唤醒...");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //如果没有包子,或者被唤醒
                //生产一个包子
                String s = "包子" + index++;
                Demo05.bzList.add(s);
                System.out.println("【生产者】生产:" + s);
                //唤醒消费者线程
                Demo05.bzList.notify();
            }
        }
    }
}

  1. 消费者线程
public class GetThread extends Thread {
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (Demo05.bzList) {
                if (Demo05.bzList.size() == 0) {//没有包子
                    try {
                        Demo05.bzList.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //如果有包子,或者被唤醒
                //取走一个包子
                String s = Demo05.bzList.remove(0);
                System.out.println("【消费者】拿走:" + s);
                //唤醒生产者线程
                Demo05.bzList.notify();
            }
        }
    }
}

  1. 测试类
public class Demo05 {
    public static ArrayList<String> bzList = new ArrayList<>();

    public static void main(String[] args) {
        //1.启动两个线程
        new SetThread().start();
        new GetThread().start();
    }
}

定时器Timer的使用

  1. 创建一个Timer对象
    Timer t = new Timer();
  2. 设置时间、任务,并会启动任务。
    1. public void schedule(TimerTask task, long delay) 在指定的延迟之后安排指定的任务执行。一次性定时器
    2. public void schedule(TimerTask task, long delay,long period)在指定的delay延迟之后开始task任务,然后会每隔period时间,会反复执行task任务。往复定时器。
    3. public void schedule(TimerTask task, Date time) 在指定的时间安排指定的任务执行。
    4. public void schedule(TimerTask task, Date firstTime, long period)从指定的时间开始,对指定的任务执行重复的 固定延迟执行 。
代码演示
	public class Demo06 {
    public static void main(String[] args) throws ParseException {
        //1.public void schedule(TimerTask task, long delay):一次性
        /*Timer t1 = new Timer();
        t1.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("嘣......");
                t1.cancel();//结束"定时器"
            }
        },1000 * 2);

        System.out.println("t1已启动,2秒后开始!");*/

        //2.public void schedule(TimerTask task, long delay,long period):往复定时器
       /* Timer t2 = new Timer();
        t2.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("嘣......");
            }
        },1000 * 2,1000);*/

       //3).public void schedule(TimerTask task, Date time) 在指定的时间安排指定的任务执行。
        /*Timer t3 = new Timer();
        String str = "2020-07-28 14:35:00";
        t3.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("扫描全盘......");
                t3.cancel();
            }
        },new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(str));

        */

        //4).public void schedule(TimerTask task, Date firstTime, long period)从指定的时间开始,对指定的任务执行重复
        //的 固定延迟执行 。
        Timer t4 = new Timer();
        String str = "2020-07-28 14:37:15";
        t4.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("扫描全盘...");
            }
        },new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(str),1000);
    }
}

死锁

  1. 当两个线程,交替使用“两把锁”的时候,就可能产生“死锁”的情况,我们编程时,应该避免这种情况。
  2. 如果项目需要两个线程交替使用两把锁,应该有一些预案,来防止死锁。
代码演示
package com.itheima.demo03_死锁的现象演示;
public class ThreadA extends Thread {
    @Override
    public void run() {
        System.out.println("【线程A】去拿锁1...");
        synchronized (Demo03.obj1) {
            System.out.println("【线程A】拿到锁1,去拿锁2...");
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (Demo03.obj2) {
                System.out.println("【线程A】拿到锁2...");
            }
        }
    }
}
package com.itheima.demo03_死锁的现象演示;
public class ThreadB extends Thread {
    @Override
    public void run() {
        System.out.println("【线程B】去拿锁2...");
        synchronized (Demo03.obj2) {
            System.out.println("【线程B】拿到锁2,去拿锁1...");
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (Demo03.obj1) {
                System.out.println("【线程B】拿到锁1...");
            }
        }
    }
}
package com.itheima.demo03_死锁的现象演示;

public class Demo03 {
    public static Object obj1 = new Object();//锁1
    public static Object obj2 = new Object();//锁2
    public static void main(String[] args) {
        //1.启动两个线程
        new ThreadA().start();
        new ThreadB().start();
    }
}
代码图解

在这里插入图片描述

多线程基础

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值