线程的等待唤醒机制;线程的生命周期;线程池;单例设计模式;Java8 接口新特性 (Java Day23)

一,线程的等待唤醒机制【线程的通信机制】

  • 概述:就是线程和线程之间的相互沟通的通信手段。
  • 案例:顾客来包子铺不停的吃包子
  • ​ 分析:
  1. 顾客:可以是一条线程 任务是吃包子
  2. 包子铺:可以是一条线程 任务做包子
  3. 包子:被线程共享资源

​          操作的共同资源是包子,顾客有包子开始吃,吃完了叫包子铺开始做包子,没有包子需要等待

​          包子铺有包子等着顾客来吃包子,吃完了开始做包子,做好了等着叫顾客吃

  • ​ 注意事项:
  1. 操作的是同一个资源,需要同步加锁
  2. 执行机制是抢占式,

​           顾客有可能先抢到了要执行,没有包子 等着让给包子铺

​           有包子 开始吃 吃完了叫包子铺来做包子

​           包子铺抢到了 没有包子开始做包子,做好了把顾客叫过来吃包子 

​           有包子等待

代码示例

//定义一个包子铺类继承Thread 

import java.util.ArrayList;
public class Baozipu extends Thread {
//定义一个存放包子的容器
    private ArrayList<String> list;
    // 有参构造
    public Baozipu(ArrayList<String> list) {
        super();
        this.list = list;
    }

//重写run方法指定包子铺的任务 [做包子]
    @Override
    public void run() {
        // 为了安全 加锁 [锁就是起到了监视线程的作用]
        while (true) {
            synchronized ("q") { // list是唯一放包子的对象,同步代码块
                // 判断是否有包子,包子在容器中,可以判断容器的大小
                if (list.size() == 0) { // 没有包子
                    // 开始做包子,需要时间
                    System.out.println("开始做包子.韭菜馅的");
                    // 包子做好之后放到容器里面
                    list.add("包子");
                    // 包子做好了,去叫顾客来吃 list 锁对象(监视对象)可以调用notify方法唤醒另一条线程
                    "q".notify(); //唤醒
                } else {
                    // 有包子,不用做,让给顾客先吃
                    try {
                        "q".wait(); // 等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        }
    }
}

//定义一个顾客类继承Thread 

import java.util.ArrayList;
public class Customer extends Thread {
    private ArrayList<String> list;
    public Customer(ArrayList<String> list) {
        super();
        this.list = list;
    }

    @Override
    public void run() {
        // 为了安全 加锁 [锁就是起到了监视线程的作用]
        while (true) {
            synchronized ("q") { // list是唯一放包子的对象,同步代码块
                // 判断是否有包子,包子在容器中,可以判断容器的大小
                if (list.size() != 0) { // 有包子
                    // 开始吃包子
                    System.out.println("开始吃包子");
                    list.remove("包子");
                    // 包子吃完了,去叫包子铺做包子 list 锁对象(监视对象)可以调用notify方法唤醒另一条线程
                    "q".notify(); // 锁对象说包子铺你回来吧,顾客吃完了等着你的包子呢
                } else {
                    // 没有包子的时候不用吃,让给包子铺先做
                    try {
                        "q".wait(); // 等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

//定义测试类

import java.util.ArrayList;
public class Test {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Baozipu baozipu = new Baozipu(list);
        Customer customer = new Customer(list);
        baozipu.start();
        customer.start(); 
    }
}
  • wait():   锁对象要线程无限等待【线程没有了运行的资格,也没有运行的权利】
  • wait(long time):   要线程等待time时长 【线程失去执行字符一段时间 自动的恢复资格】
  • notifyAll():   唤醒监控的【锁对象】等待的所有线程
  • notify():   唤醒监控的【锁对象】等待的单条线程【线程重新有了执行资格】
  • 注意:这四个方法不是线程的方法,是Object的方法。

二,线程的生命周期

  • 概述:一条线程从创建到消亡的过程。在这个周期中线程对应的会有不同的状态。不同的状态有不同的特征。
  • 生命周期:
  1. ​新建:创建线程对象
  2. 就绪:拥有执行资格,没有执行权【start方法启动了但是jvm没有执行】
  3. 运行:拥有执行资格,有执行权【start方法启动了,同时jvm也赋予执行权】
  4. 阻塞:没有执行资格,没有执行权【被sleep wait等方法阻塞了】
  5. 死亡:线程对象变成垃圾,等待回收【run方法执行完毕了】
  • 涉及的线程的状态:
  1. NEW【新建:  新建时期
  2. RUNNABLE【运行】:  对应的是就绪和运行两个时期
  3. ​ BLOCKED【锁阻塞】:  线程在运行的时候遇到了锁,没有锁对象等待jvm
  4. WAITING【无限等待】:  线程运行的时候,被锁对象制止了,不被唤醒不能执行
  5. TIMED_WAITNG【计时等待】:  线程运行的时候,被锁对象制止了一段时间
  6. TERMINATED【死亡】:  死亡时期
  • 状态和状态之间可以相互转换的

  • Java语言中,获取线程状态的方法:
  • ​ getState():获取线程的状态值

代码示例

public class Demo0 {
    public static void main(String[] args) {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                System.out.println();
            }
        };
        System.out.println(t1.getState());// NEW 值创建了线程对象没有启动线程
        t1.start();
        System.out.println(t1.getState());// RUNNABLE
    }
}
线程的状态值就是:NEW  RUNNABLE   BLOCKED   WAITING  TIMED_WAITING  TERMINATED

三,线程池

  • 概述

  • 字面来说:放线程对象的池子【容器】。
  • ​ 池子:是一种容器 可以同时容纳多个相同类型的对象 对象供将来使用的
  • 概述:存放线程对象的容器。他是有大小。可控的,存放的线程对象都是没有任务线程对象
  • 为什么使用线程池?
  • 没有线程池:
  1. 想要使用新的线程,自己手动的准备线程类,创建对象,准备线程任务,用完线程对象就没有用需要垃圾回收器回收,回收不一定及时,导致额外的占用内存资源。
  2. 多个小任务,需要多个线程来完成,对jvm来说只需要几条线程就够。导致线程对象创建的多了,浪费资源
  3. 线程对象用一个创建一个,没有一个统一化管理,比较乱,对象自己创建的,还得自己维护
  • 有线程池:
  1. ​ 线程池里提供好了既有的线程对象,我想用的时候申请用即可。不用自己创建,用完后把对象归还给池即可不用回收。
  2. ​ 针对多个小任务,选择合适的线程对象个数的线程池来完成,减少了线程对象的创建,避免内存资源的浪费
  3. ​ 线程对象是线程池提供,创建和管理维护都线程池来操心,我们只需要关心线程任务即可
  • 好处:
  1. 线程对象统一管理增加了维护性
  2. ​减少了资源的浪费,提高了效率
  3. 解放开发工作者,提升开发效率
  • 使用

  • 获取线程池对象:
  • 使用一个工具类:Executors 
  •  ExecutorService  newFixedThreadPool(int nThreads)
  • 说明:
  1. ExecutorService【线程池对象类型】:是一个接口,他描述了线程池的相关功能
  2. 参数 nThreads:指定线程池的大小【线程对象的个数】
  • ​ 常用方法:
  • ​ 提交执行线程任务:submit(Runnable runnable):
  • ​ 关闭线程池【不常用】
  1. ​ shutdown():所有提交的任务运行完再关闭线程池
  2. shutdownNow():把正在运行的提交任务运行完立即关闭线程池
  • 线程池使用步骤:
  1. 获取线程池对象【相当于获取到了线程对象】
  2. 实现Runnable接口重写run方法【定义任务对象】
  3. 提交任务对象给线程池【调用submit方法提交任务】
  4. 关闭线程池【不常用】
  • 注意事项:线程池里面存放的都是没有任务的线程对象,只需要提供任务对象到线程池即可。使用线程池只需要使用实现接口方式准备线程任务即可。

代码示例


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo03 {
    public static void main(String[] args) {
        // 线程就是池子里面的对象
       // 获取线程池对象 ExecutorService 是一个接口
        ExecutorService pool = Executors.newFixedThreadPool(2); // 多态,里面有两个线程对象的线程池
        // 创建任务对象
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "花花"); // pool-1-thread-1花花
            }
        };
        // 提交任务给线程池
        // 只要把任务丢给线程池就可以了
        pool.submit(r1); // pool-1-thread-1花花
        pool.submit(r1); // pool-1-thread-2花花
        pool.submit(r1); // pool-1-thread-2花花
        // 提交3个任务,池中有2个对象 先执行其中的两个任务
        // 另外一个任务等着 两个对象哪个先完成出来执行剩下的任务

        // 关闭池的方法
       //    pool.shutdown(); //得到的是三个任务的结果(所有任务的结果)
        pool.shutdownNow(); // 得到的是两个结果,因为线程池里面就只有两个对象。把正在执行的任务干完就关闭了。
    }
}

四,单例设计模式

  • 概念

  • 模式:经验、模板、套路。我们总结出来的一些做事的固有流程。
  • 设计模式:使用固有的流程或方式设计出来的类、接口、枚举等元素. java当中常见的设计模式有23种。比如:单例、工厂、装饰者等
  • 单例模式:就是设计模式中比较简单常用的一种。特点:类有且只有一个对象。
  • 单例模式的设计原则:
  1. 私有化构造方法【private、protected】
  2. 类中创建最终对象【唯一不能被赋值】
  3. 给外界提供公有的访问对象的方式
  • 分类:饿汉式、懒汉式
  • 饿汉式

  • 概述:对类的加载及时的创建对象的一种单例模式
  • 特点:对象及时的创建,占用内存空间。

代码示例

public class Hungry {
    //私有化构造 (空参构造)
    private Hungry() {
    }
    //在内中创建最终的对象
    //提前占用内存空间
    private static final Hungry hungry = new Hungry();
    //给外界提供公有的访问对象的方法 [静态方法]
    public static Hungry getHungry() {
        return hungry;
    }
}
  • 饿汉式变形式
  • 概述:是饿汉式的一种【饿汉式的一种变形】
  • 特点:在饿汉式的基础上,省略了对外界提供的方法公开化声明的变量创建对象赋值给变量同时使用 final 修饰

代码示例:

//饿汉式变形
public class Hungry_Old { //私有化构造 (空参构造) public Hungry_Old() { } //在类中创建最终的对象,静态属性赋值:hungry public static final Hungry_Old hungry = new Hungry_Old (); }
//定义测试类 package com.ujiuye.demo;
public class Hungry_Test { public static void main(String[] args) { //饿汉式对象 Hungry hungry1 = Hungry.getHungry(); Hungry hungry2 = Hungry.getHungry(); Hungry hungry3 = Hungry.getHungry(); System.out.println(hungry1); //com.ujiuye.demo.Hungry@448139f0 System.out.println(hungry2); //com.ujiuye.demo.Hungry@448139f0 System.out.println(hungry3); //com.ujiuye.demo.Hungry@448139f0 //输出的是一个地址值,外界使用有且只有一个对象 System.out.println("------------------------------"); //饿汉式变形(老汉式)---公开化 Hungry_Old hungry4= Hungry_Old.hungry; Hungry_Old hungry5= Hungry_Old.hungry; Hungry_Old hungry6= Hungry_Old.hungry; System.out.println(hungry4); //com.ujiuye.demo.Hungry_Old@7ba4f24f System.out.println(hungry5); //com.ujiuye.demo.Hungry_Old@7ba4f24f System.out.println(hungry6); //com.ujiuye.demo.Hungry_Old@7ba4f24f } }
  • 懒汉式

  • 概述:类加载的时候只声明对象变量不给他赋值,什么时候用对象的时候才创建对象。保证只创建一次。
  • 特点:对象需要使用的时候才创建,不提前占用内存

代码示例

public class Lazy {
    // 私有化构造【有属性的时候需要私有化所有的构造】
    private Lazy() {
    }
    // 声明变量【类对象的变量】,只声明不赋值
    private static Lazy lazy; 
    // 对外界提供访问类对象的方法
    public static Lazy getInstance() {
        if (lazy == null) {
            lazy = new Lazy();
        }
        return lazy;
    }
}

//定义测试类
public class Lazy_Test { public static void main(String[] args) { // 懒汉式 Lazy lazy1 = Lazy.getInstance();// Lazy创建对象没? 创建的 Lazy lazy2 = Lazy.getInstance();// Lazy创建对象没? 没有创建的 if语句判断 Lazy lazy3 = Lazy.getInstance();// Lazy创建对象没? 没有创建的 if语句判断 System.out.println(lazy1);// com.ujiuye.demo.Lazy@15db9742 System.out.println(lazy2);// com.ujiuye.demo.Lazy@15db9742 System.out.println(lazy3);// com.ujiuye.demo.Lazy@15db9742 } }
  • 如果是多线程执行懒汉式,由于会有线程的安全问题导致不同的线程拿到不一样的对象,这样就违背了单例模式的初衷,所以改造单例懒汉式【加锁】

改造代码如下:

public class Lazy {
    // 私有化构造【有属性的时候需要私有化所有的构造】
    private Lazy() {
    }
    // 声明变量【类对象的变量】
    private static Lazy lazy;
    // 对外界提供访问类对象的方法
    public static synchronized Lazy getInstance() {
            // 多线程同时判断lazy  结果同时都是null 都去创建对象
            if (lazy == null) {
                lazy = new Lazy();
            }
        return lazy;
    }
/*    public static Lazy getInstance() {
        synchronized ("a") {
            // 多线程同时判断lazy  结果同时都是null 都去创建对象
            if (lazy == null) {
                lazy = new Lazy();
            }
        }
        return lazy;
    }
*/}
  • 保证了线程安全的前提下,重复使用懒汉式的时候从第二次开始每次都要执行锁的相关代码,这样效率比较低,因为第二次开始有对象了只需要直接方法返回对象即可,不需要每次都走锁的相关代码,所以代码需要进一步改造【在锁代码外面添加if判断对象是否为null】具体代码如下:
public class Lazy {
    // 私有化构造【有属性的时候需要私有化所有的构造】
    private Lazy() {
    }
    // 声明变量【类对象的变量】
    private static Lazy lazy;
    // 对外界提供访问类对象的方法
    /*public static synchronized Lazy getInstance() {
            // 多线程同时判断lazy  结果同时都是null 都去创建对象
            if (lazy == null) {
                lazy = new Lazy();
            }
        return lazy;
    }*/
    public static Lazy getInstance() {
        if (lazy == null) {// 如果为null 保证线程安全前提去创建对象出来
            synchronized ("a") {
                // 多线程同时判断lazy  结果同时都是null 都去创建对象
                if (lazy == null) {// 保证线程安全的时候创建对象的条件
                    lazy = new Lazy();
                }
            }
            // 加锁的代码不用重复的执行效率就提升了
        }
        // 不为null 对象已经存在  不需要创建 直接返回这个对象
        return lazy;
    }
}
  • 注意事项:
  • ​ 这段代码中的两个 if 语句缺一不可
  • ​ 锁内部的 if 语句作用是:解决线程安全问题保证对象的唯一
  • ​ 锁外面得 if 语句作用是:确保对象已经有值得情况下少的去执行锁相关代码,提升运行效率

五,Java8 接口新特性

  • 概述
  1. Java8 中的接口,不仅可以定义抽象方法,也允许定义非抽象方法
  2. 抽象方法的定义方式和以前一样,默认加上了 public abstract,不能拥有方法体
  3. 非抽象方法是指拥有方法体的方法,普通方法接口不允许的,增加默认方法和静态方法
  4. jdk8版本增加默认方法和静态方法
  • ​ 默认方法:必须使用 default 修饰的方法 是接口独有的
  • 默认方法
  • 使用原因:1.8 中很多老接口中,增加了很多新的方法,这样就会让原来的旧项目无法编译通过,因此为了向下兼容,新增的方法,就加上了默认实现,但是这种方法不会被强制要求实现类重写
  • 使用规则
  1. 加上 default 的,实现类可以重写也可以不重写直接调用
  2. 特殊情况 1:实现类实现了多个接口,如果有相同的的抽象方法声明,则强制要求在实现类中,必须重写这个方法,
  3. 特殊情况 2:在特殊情况 1 中,如果在实现类重写这个抽象方法的过程中,希望调用其中某个接口的默认方法时,则可以使用格式: 接口名.super. 默认方法名称 ()进行调用
  4. 特殊情况 3:实现类继承了一个父类,并且实现了一个接口,并且在父类中和接口中,有相同的方法声明,则“类优先”。即使用类的实现,即使继承的是一个抽象类,也是使用父类的实现(即强制重写)。
  5. 特殊情况 4:实现类实现了多个接口,如果不同的接口中抽象方法和默认方法有相同的方法声明;实现类必须进行重写该方法声明

代码示例:

//定义接口InterfaceA
public interface InterfaceA { // 抽象方法 void show(); // 默认方法 public default void add() { System.out.println("你好美啊"); } // 静态方法 public static void print() { System.out.println("这是借口的静态方法"); } }

//定义接口InterfaceB package com.ujiuye.demo;
public interface InterfaceB { // 抽象方法 void show(); void add(); // 默认方法 /* public default void add1() { System.out.println("你好美啊"); }*/ // 静态方法 public static void print() { System.out.println("这是借口的静态方法"); } }

//实现接口
public class ClassA implements InterfaceA ,InterfaceB{ // 只重写了抽象方法 @Override public void show() { // 调用其中一个接口的默认方法 接口名.super.方法名() InterfaceA.super.add(); System.out.println("这是借口抽象方法重写"); } // 重写默认方法 @Override public void add() { System.out.println("这是借口默认方法的重写"); } }
//定义测试类
public class InterfaceA_Test { public static void main(String[] args) { ClassA a = new ClassA(); a.show();// 抽象方法的使用 a.add();// 直接调用默认方法 } }
  • 产生影响
  1. 接口中如果也可以定义非抽象方法,那么和抽象类的差别就越来越小
  2. java 中一个类只能继承一个抽象类,但是可以同时实现多个接口
  3. 所以有了默认方法,就会让大量的抽象类变成接口,即弱化了抽象类
  • 静态方法
  1. 接口的静态方法可以定义方法体内容
  2. static 不能和 abstract 共存
  3. 外界只能通过接口名称 . 静态方法名来访问接口静态方法,实现类中不会继承接口中的静态方法,原因:如果一个类同时实现了多个接口,接口中具有相同的静态方法,实现之后不知道应该以哪个为准进行调用使用
  • 总结:接口的静态方法只属于接口自己,不能被实现类实现,只能使用接口名来调用。

代码示例

//定义接口InterfaceA
public interface InterfaceA { // 抽象方法 void show(); // 默认方法 public default void add() { System.out.println("你好美啊"); } // 静态方法 public static void print() { System.out.println("这是接口A的静态方法"); } }

//定义接口InterfaceB
public interface InterfaceB { // 抽象方法 void show(); void add(); // 默认方法 /* public default void add1() { System.out.println("你好美啊"); }*/ // 静态方法 public static void print() { System.out.println("这是接口B的静态方法"); } }

//实现接口
public class ClassA implements InterfaceA ,InterfaceB{ // 只重写了抽象方法 @Override public void show() { // 调用其中一个接口的默认方法 接口名.super.方法名() InterfaceA.super.add(); System.out.println("这是借口抽象方法重写"); } // 重写默认方法 @Override public void add() { System.out.println("这是借口默认方法的重写"); } }

//定义测试类
public class InterfaceA_Test { public static void main(String[] args) { ClassA a = new ClassA(); a.show();// 抽象方法的使用 a.add();// 直接调用默认方法 // 使用实现类对象调用静态方法报错 //a.print(); InterfaceA.print(); InterfaceB.print(); } }


原文地址:https://www.cnblogs.com/nastu/p/12570751.html