Java基础22 生产者消费者模式 线程第三种实现方式 枚举 线程的生命周期 线程池 定时任务 单例模式

一、多线程的常用的方法

在这里插入图片描述
stop() 停止线程 方法已过时了因为不安全 但还是有效果 一般不使用,建议用wait()
interrupt();只能短暂停止,还是会运行完线程

package day22;

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
           /* if(i==5){
                //停止线程   stop()已过时了 用interrupt()
                //stop();
               interrupt();//只能短暂停止,还是会运行完线程
            }*/
            System.out.println(Thread.currentThread().getName()+"\t"+i);
        }
    }
}

package day22;

public class Test01 {
    public static void main(String[] args) throws InterruptedException {
        MyThread th1 = new MyThread();
        th1.start();
        Thread.sleep(2000);
        //结束当前线程
        //th1.stop();
       th1.interrupt();
    }
}

yield() 是静态方法 直接通过类名调用 不建议用对象名调用 暂停当前正在运行的线程 执行其他线程
阿里规范 用对象名调用静态方法增加编译器解析成本,直接用类名来访问即可。

package day22;

public class Test02 {
    public static void main(String[] args) {
        MyThread th1 = new MyThread();
        th1.start();
        //yield() 是静态方法 直接通过类名调用 不建议用对象名调用
        //阿里规范 用对象名调用增加编译器解析成本,直接用类名来访问即可。
        //暂停当前正在运行的线程 执行其他线程
        Thread.yield();
        MyThread th2 = new MyThread();
        th2.start();
    }
}

join()表示等待该线程执行完毕之后 其它线程才能执行(方法调用必须是在开启线程之后调用才有效果)

package day22;

public class Test03 {
    public static void main(String[] args) throws InterruptedException {
        MyThread th1 = new MyThread();
        th1.start();
        //表示等待改线程执行完毕之后 其它线程才能执行(方法调用必须是在开启线
        //程之后调用才有效果)
        th1.join();
        MyThread th2 = new MyThread();
        th2.start();
    }
}

二、Object 提供线程中的使用的方法

在这里插入图片描述
需求分析
在这里插入图片描述
其原理就是根据包子存不存在就行等待休眠,同时锁住所有操作,因为包子一种状态对应不同线程的不同操作,如果不锁住,就会抢发生IllegalMonitorStateException 非法监视器状态异常
这个异常就是 线程1 刚生产了一个包子,线程2抢到消费了,包子的存在对线程1就是问题,计算机理解不了,回忆之前未使用锁时,两个线程循环打印1~100,并不是等线程1打印完线程2在打印,而是谁抢到了打印谁的,包子的状态时共有的,线程抢占就会有冲突。
或者就是单纯不能直接使用wait();notify()
在线程中调用wait方法的时候 要用synchronized锁住对象,确保代码段不会被多个线程调用,即需要obj.wait();obj.notify();obj为锁对象

包子存在就 消费者去吃,吃完唤醒老板,老板就等待
包子不存在就消费者等待,老板生产,做完包子唤醒消费者

package day22;

public class Test04 {
    public static void main(String[] args) {
        //定义一个锁的对象
        Object obj = new Object();
        //买包子消费者线程
        new Thread(){
            @Override
            public void run() {
                synchronized (obj){
                    //第一步
                    System.out.println("我需要一个包子");
                    //需要无线进行等待
                    try {
                        //第二步
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("==========");
                    //第五步
                    System.out.println("我正在吃包子");
                }
            }
        }.start();
        //老板线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized(obj){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //第三步
                    System.out.println("我的包子已经做好了,可以开始吃了");
                    //需要唤醒 消费者 可以来吃包子
                    //第四步
                    obj.notify();
                }
            }
        }).start();
    }
}

三、多线程中的生产者与消费者模式

需求分析
在这里插入图片描述
包子类

package day22;

public class BaoZi {
    public String pi;
    public String xian;
    //定义一个标记表示是否存在包子 false 没有包子 ture 有包子
    boolean flag = false;
    //boolean 默认值为false
}

生产者

package day22;

public class ProducterThread extends  Thread{
    private BaoZi baoZi;
    public  ProducterThread(BaoZi baoZi){
        this.baoZi = baoZi;
    }

    @Override
    public void run() {
        //一直生产包子
        while (true){
            //同步代码块 //互斥锁
            synchronized(baoZi){
                //已存在包子 等待消费  不需要在生产
                if(baoZi.flag){
                    try {
                        baoZi.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //不存在包子 开始生产包子
                baoZi.pi = "薄皮";
                baoZi.xian ="韭菜鸡蛋";
                System.out.println("==============");
                System.out.println("我生产了一个"+baoZi.pi+baoZi.xian+"的包子");
                //将标记值设置为ture
                baoZi.flag = true;
                //通知消费者去消费
                baoZi.notify();
            }
        }
    }
}

消费者

package day22;

public class CustomerThread extends Thread{
    private  BaoZi baoZi;
    public CustomerThread(BaoZi baoZi){
        this.baoZi=baoZi;
    }

    @Override
    public void run() {
        while (true){
            synchronized (baoZi){
                //如果包子不存在 等包子生成出来
                if(!baoZi.flag){
                    //无线等待
                    try {
                        baoZi.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //包子存在 吃包子
                System.out.println("我消费了一个"+baoZi.pi+baoZi.xian+"的包子");
                baoZi.pi=null;
                baoZi.xian=null;
                //标记设置为false
                baoZi.flag = false;
                //通知生产者开始生产
                baoZi.notify();
            }
        }
    }
}

测试类
多个类(称为线程一类,线程二类) 需要一个共有的变量,即将这个变量封装为一个类(称为属性类)的属性,注意属性类的属性修饰符为public公共的,在测试类中new 这个属性类实例化为对象,然后将此对象作为参数传入需要的多个类(线程一,线程二)中,就可以实现共享对象的属性即变量,且多个类(线程一,线程二) 需要定义此对象(属性类)为私有变量,并提供有参构造

package day22;

public class Test05 {
    public static void main(String[] args) {
        BaoZi baoZi =new BaoZi();
        //开启生产者的线程
        new ProducterThread(baoZi).start();
        //开启消费者的线程
        new CustomerThread(baoZi).start();
    }
}

四、多线程的第三种实现方式(实现类连接Callable接口-带返回值)

步骤
定义一个类 实现Callable 这个接口
实现接口中的抽象方法 cell()
实例化 实现Callable的类
实例化任务的对象 FutureTask 构造方法的参数 需要传递 Callable 的实现类
实例化Thead对象 构造方法中需要传递 任务对象 调用start() 开启线程
根据任务对象的get() ==>获取返回值

案例 实现多线程1-100之和 获取其返回值结果

package day22;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        Integer count =0;
        for (int i = 0; i < 100; i++) {
            count+=i;
        }
        System.out.println(Thread.currentThread().getName());
        return count;
    }
}

package day22;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test06 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //实例化MyCallable对象
        MyCallable call = new MyCallable();
        //实例化任务对象
        FutureTask<Integer> ft = new FutureTask<>(call);
        //实例化Thread对象
        new Thread(ft).start();
        //调用任务对象的get方法 获取其返回值
        Integer count = ft.get();
        System.out.println(count);
    }


}

五、枚举

1.枚举的概念:用于间接的表示一些固定的值 Java提供了枚举来进行表示
在枚举中可以将变量 一一进行罗列 而枚举的值必须是罗列的值 限制的数据的范围
2.定义的格式
访问修饰符 enum 枚举的名称 {
枚举项
}
enum 是枚举的关键字
3.代码

public enum Color {
//枚举的选项
BLUE,YELLOW,RED;
}

以前程序员手写实现枚举作用的类,便于理解枚举

class EnumByClass{
    public static final int RED=0;
    public static final int GREEN=1;
    public static final int BLUE=2;
}

4.枚举的特点
1.所有的枚举类都是Enum子类
2.每一个枚举项都一个对象
3.每一个枚举项都可以通过枚举类来进行获取 枚举类.枚举项
4.一般枚举项是编写在第一行 不同的枚举项以,来进行分割 最后以分号进行结尾
5.枚举类中可以有成员变量
4.枚举类中可以有构造 但是只能是私有的构造 默认的也是私有的构造
5.枚举类中可以有抽象方法,但是每一个枚举项都必须实现其抽象方法

5.代码
//枚举的选项实例 BLUE,YELLOW,RED都可以理解是Color的子类对象 且每个枚举实例都是static final类型的,也就表明只能被实例化一次,且每次访问都是在实例化,因为final修饰地址值不变,对象是相等的

package day22;

public enum  Color {
    //枚举的选项 BLUE,YELLOW,RED都可以理解是Color的子类对象
    BLUE{
        @Override
        public String getColor() {
            return "蓝色";
        }
    },YELLOW{
        @Override
        public String getColor() {
            return "黄色";
        }
    },RED{
        @Override
        public String getColor() {
            return "红色";
        }
    };
    private String name;
    private Color(){

    }
    public abstract String getColor();

}


6.枚举中常用的一些方法
在这里插入图片描述

package day22;

public class Test07 {
    public static void main(String[] args) {
        /*//输出结果为BLUE YELLOW RED
        System.out.println(Color.BLUE);
        System.out.println(Color.YELLOW);
        System.out.println(Color.RED);*/
        Color c1 = Color.RED;
        Color c = Color.YELLOW;
        System.out.println(c.name());
        System.out.println(c.ordinal());
        //c1-c的索引值之差
        System.out.println(c1.compareTo(c));
        System.out.println(c1.toString());
        System.out.println(Color.valueOf("YELLOW"));
        Color[] values = Color.values();
        for (Color value : values) {
            System.out.println(value.name());
        }
    }
}

7.switch与枚举进行搭配的使用

package day22;

public enum Week {
    星期一,星期二,星期三,星期四,星期五,星期六,星期天;
}

package day22;

public class Test08 {
    public static void main(String[] args) {
        Week w = Week.星期四;
        switch (w){
            case 星期一:
                System.out.println("猪脚饭");
                break;
            case 星期二:
                System.out.println("烧鸭饭");
                break;
            case 星期三:
                System.out.println("白切鸡");
                break;
            case 星期四:
                System.out.println("烧腊");
                break;
            case 星期五:
                System.out.println("猪排");
                break;

            case 星期六:
                System.out.println("茄子");
                break;
            case 星期天:
                System.out.println("土豆丝");
                break;
            default:
                break;
        }
    }
}

六、线程的生命周期

1.线程的基本状态
在这里插入图片描述
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
1)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
3)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
原文地址
记忆:一个三角形 加外延两点 即 可运行 正在运行 阻塞 新建和死亡
具体的状态转换看下图

2.线程的生命周期
在这里插入图片描述
3.线程的状态
Thread.State ==> 通过枚举来进行一一罗列
4.获取线程状态的方法
public Thread.State getState()
5.代码
线程类

public class MyThread extends Thread {
@Override
public void run() {
}
}

测试类

public class Test {
public static void main(String[] args) {
MyThread th1 = new MyThread();
System.out.println(th1.getState());
th1.start();
System.out.println(th1.getState());
}
}

七、线程池

1.线程池:用于存储大量线程的的容器 就称为叫做线程池
2.没有线程池之前
在这里插入图片描述
3.有线程池之后
在这里插入图片描述
4.使用线程池的步骤
1.通过这个Executors工具类来获取获取线程池对象 并且设置线程的个数
newFixedThreadPool(设置线程池的个数)
newSingleThreadExecutor() 线程池的线程的个数1
2.创建任务的对象
定义一个类 实现Runnable
定义一个类 实现Callable
3.调用submit方法 提交任务到线程池中 submit(任务对象)
4.调用这个类Future的get() 可以获取到返回值
Executors 线程池工具的方法
在这里插入图片描述
创建任务的对象 Runnable 没有返回值 Callable 有返回值
1.实现Runnable接口

package day22.threadpool;

public class MyRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println("哈哈哈");
    }
}

2.实现Callable

package day22.threadpool;

import java.util.concurrent.Callable;

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

创建线程池,提交任务到线程池中

package day22.threadpool;

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

public class Test09 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //获取单个线程的线程池对象
        //创建一个线程
        ExecutorService service1 = Executors.newSingleThreadExecutor();
        //创建3个线程
        ExecutorService service2 = Executors.newFixedThreadPool(3);
        //不带返回值
        service2.submit(new MyRunnable());
        service2.submit(new MyRunnable());
        service2.submit(new MyRunnable());
        //带返回值
        Future f = service1.submit(new MyCallable());
        Integer count = (Integer) f.get();
        System.out.println(count);
    }
}

线程两个方法
在这里插入图片描述

package day22.threadpool;

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

public class Test10 {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.submit(new MyRunnable());
        executorService.submit(new MyRunnable());
        executorService.submit(new MyRunnable());
        //执行已提交的任务,不会在接受新任务
        executorService.shutdown();
        //试图停止所有正在执行的活动任务,暂停处理正在等待的任务
        //完成时间短的任务一般先完成了,但是正在等待的任务一定会被停止,不执行
        executorService.shutdownNow();
    }
}

八、java中定时任务

1.生活中的定时任务就是闹钟 在开发中定时发送邮件 定时向文件中写入内容
2.定时任务使用的步骤
1.定义一个类 继承 TimerTask == > 表示是一个任务的对象
2.创建定时器对象 Timer
3.需要将任务提交到定时对象中
3.需求: 指定的时间向文件中写入内容 ==> 使用定时任务
4.代码

package day22.threadpool;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.TimerTask;

public class RemindTask extends TimerTask {
    @Override
    public void run() {
        System.out.println("我是定时任务");
        try {
            BufferedWriter bw = new BufferedWriter(new FileWriter("1.txt"));
            bw.write("定时任务");
            bw.flush();
            bw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

如果d 在当前时间之前就会直接执行定时任务
如果d 在当前时间之后 就会等到指定d的时间才会执行定时任务

package day22.threadpool;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Scanner;
import java.util.Timer;

public class Test11 {
    public static void main(String[] args) throws ParseException {

        /*long time = System.currentTimeMillis();
        Date d = new Date(time+5000);*/
        // 如果d 在当前时间之前就会直接执行定时任务
        //如果d 在当前时间之后 就会等到指定d的时间才会执行定时任务
        //Date d= new Date(time-5000);
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入时间");
        String s = sc.nextLine();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = sf.parse(s);
        System.out.println(d);
        Timer t =new Timer();
        RemindTask ta = new RemindTask();
        t.schedule(ta,d);
    }
}


可以直接通过设置第二个参数来延迟时间完成

        //timer.schedule(remindTask,date);
        //第二个参数可以为long类型,即延迟执行的时间,单位毫秒
        timer.schedule(task,5000);

九、单例设置模式

1.特点: 有且仅实例化一个对象 保证对象的唯一 即实例化两次对象,第一次对象 == 第二次对象 为true
2.使用场景: 开发中的工具类 properties文件的加载 也只需要加载一次
3.有且仅实例化
1.私有的属性
2.私有的构造 ==> 只能在本类实例化这个对象
3.提供一个公有的方法
4.单例设置模式的分类 使用双重锁来实现单例设置模式
1.饿汉模式
优点: 没有锁机制 效率比较高
缺点: 项目初始化的时候 就进行加载 可能出现项目卡顿
代码
从上往下一一执行,一一看,一一理解就行了

package day22.danli;
import java.util.Date;
/*
* 实例化一次*/
public class DateUtils {
    //私有的属性
    private static DateUtils utils = new DateUtils();
    //私有的构造
    private DateUtils(){

    }
    //提供一个公有的方法
    public static DateUtils getInstance(){
        if(utils!=null){
            return utils;
        }
        return null;
    }
}

测试类

public class Test {
public static void main(String[] args) {
DateUtils d1 = DateUtils.getInstance();
DateUtils d2 = DateUtils.getInstance();
System.out.println(d1);
System.out.println(d2);
System.out.println(d1==d2);
}
}

2.懒汉模式
优点: 使用的时候进行加载 不是项目初始化就立即加载 懒加载
缺点: 在多线程不安全的
代码

package day22.danli;

import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtils1 {
    private static DateUtils1 utils1;
    private DateUtils1(){

    }
    public static DateUtils1 getInstance(){
        if(utils1 == null){
            utils1 = new DateUtils1();
        }
        return utils1;
    }
}

测试类


public class Test {
public static void main(String[] args) {
DateUtils1 d1 = DateUtils1.getInstance();
DateUtils1 d2 = DateUtils1.getInstance();
System.out.println(d1);
System.out.println(d2);
System.out.println(d1==d2);
}
}

3.双重锁(使用最多)
优点:使用多线程的锁的机制来保证多线程中的安全
缺点: 效率会降低
代码
其他地方看到的双重锁只是双重检验,方法上没有synchronized,即没有对getInstance()方法加锁
理解: 在多个线程中如果是单个锁,在线程1进入getInstance()方法通过if(utils2 ==null)是被其他线程抢占,就会多次执行utils2=new DateUtils2(); 语句,导致utils2地址冲突
而双重锁 在线程1进入getInstance()方法通过if(utils2 ==null)还要获取锁对象,如果获取了synchronized (DateUtils2.class)锁对象,此时被其他线程抢占,其执行到synchronized (DateUtils2.class)时,因为锁对象被线程1占取,进入阻塞状态,只有等线程1执行完所有语句(创建了utils2对象),在释放锁对象其他线程才能进入运行状态,当其他线程进行运行状态,此时线程1已创建了utils2对象,if(utils2 ==null)结果为false

从阻塞状态进入运行状态,其代码接着未运行的代码继续运行(每个线程都有单独的栈运行方法,阻塞只是栈没获取到cpu,放在阻塞区,转为就绪状态时无需新建栈重新运行),所以如果变量发生改变就可能报错,通过锁来保证安全,

package day22.danli;

public class DateUtils2 {
    //私有的属性 volatile 可见性 变量发生改变之后 所有线程获取到改变后的值
    private static volatile DateUtils2 utils2;
    //私有的构造
    private DateUtils2(){

    }
    //提供一个公有的方法
    public static synchronized DateUtils2 getInstance(){
        //单个锁
        /*if(utils2 ==null){
            utils2=new DateUtils2();
        }*/
        
        if(utils2 == null){
            //双重锁
            synchronized (DateUtils2.class){
               if (utils2 ==null){
                  utils2 = new DateUtils2();
               }
            }
        }
        return utils2;


    }



}

测试类

public static void main(String[] args) {
DateUtils2 d1 = DateUtils2.getInstance();
DateUtils2 d2 = DateUtils2.getInstance();
System.out.println(d1);
System.out.println(d2);
System.out.println(d1==d2);
}

4.使用静态内部类

package day22.danli;

public class DateUtils3 {
    //静态的内部类
    private static class Holer{
        //在内部类中实例化外部类的对象
        public static final DateUtils3 utiils3 = new DateUtils3();
    }
    //私有的构造方法
    private DateUtils3(){

    }
    //提供一个公有的方法来进行访问
    public static DateUtils3 getInstance(){
        return Holer.utiils3;
    }

}

测试类

public static void main(String[] args) {
DateUtils3 d1 = DateUtils3.getInstance();
DateUtils3 d2 = DateUtils3.getInstance();
System.out.println(d1);
System.out.println(d2);
System.out.println(d1==d2);
}

5.枚举单例模式
访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。
在调用构造方法时,我们的单例被实例化。所以使用枚举能够实现懒加载、线程安全。

package day22.work;
public enum DataUtils {
    DATA_UTILS;
    DataUtils(){}
    public DataUtils getInstance(){
        return DATA_UTILS;
    }

}


测试

package day22.work;

public class Work02 {
    public static void main(String[] args) {
        DataUtils dataUtils1= DataUtils.DATA_UTILS;
        DataUtils dataUtils2= DataUtils.DATA_UTILS;
        System.out.println(dataUtils1 ==dataUtils2);

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值