初始多线程

程序

  • 按照一定的逻辑编写的代码,存储到文件中,文件存放到磁盘---静态状态

进程

  • 字面理解-正在进行中的程序
  • 资源:内存+CPU
  • 程序从启动到结束的过程称为进程
  • cpu在某一时刻只能执行其中一个进程,而cpu在做告诉切换动作
  • 每个进行执行时间不固定,多个进程抢夺cpu的执行权

线程

  • 属于进行,是进行中一个可以独立运行执行单元(执行顺序,执行路径、代码片段)

  • 一个进程最少有一个线程,一个进程有多个可以独立运行的执行单元---多线程

  • 代码之所以是单线程,以为只有一个执行单元

  • 执行单元都放在main方法中---主线程

  • 实现效果

    • 一个进程中,出现多个执行路径,各自执行各自的
  • cpu进行切换:实则切换的各个进程中的线程

  • 在main中创建其他线程--子线程

  • 子线程要运行的代码封装到run方法中

多线程目的

  • 提高cpu使用率,提高程序执行效率

    • cpu有极限,不能无限开启线程

并行

  • 前提:多个cpu
  • 多个任务同时发起,在某个时刻,每个任务都在执行

并发

  • 单个cpu,多个任务同时发起,在某个时刻,一个任务在执行,其他任务等待cpu

创建线程

java是面向对象思想,任何事物都可以叫做对象,因此线程也是一类事物,就会有对应的对象来进行描述,java.lang.Thread类。

创建线程方式

  • 1、继承Thread类

    • 步骤

      • 1.定义类继承Thread,子类就是一个线程类
      •  2.重写run方法
        
      •  3.创建子类对象
        
      •  4.启动线程,调用start方法
        
  • 2、实现接口Runnable

    • 步骤

      • 1.定义类实现Runnable接口,重写接口中的run方法 (run方法中就是线程任务)
      • 2.创建实现类对象
        
      • 3.创建Thread对象,将实现类对象作为参数传给Thread的构造方法
        
      • 4.调用start方法,启动线程
        
  • 3、实现接口Callable

    • 步骤

      • 1.定义类实现Callable ,重写call方法,方法的返回值是V类型 (线程任务)
      •     2.创建实现类对象
        
      •     3.创建FutureTask对象,并将实现类对象作为参数传递给构造方法
        
      •     4.创建线程对象,并将FutureTask对象作为参数传递给构造方法
        
      •     5.调用start方法,启动线程
        
    • FutureTask实现了Runnable接口,类中提供get方法,可以获取call方法的返回值,即获取线程执行后返回的结果,还又判断线程任务状态的方法,如:isDone()...

  • 三种方式区别

    • 继承Thread类

      • 代码简单

      • 因单继承原因,继承此类不能继承其他类

      • 线程任务和线程对象绑一起,耦合度增高

      • 原理

        • start-->start0--->run 由于子类重写了run方法,因此创建子类对象调用run方法,运行时子类的内容(方法重写)
    • 实现Runnable

      • 线程任务和线程对象是分离的,降低耦合度

      • 实际开发建议使用此方式

      • 原理

        • start-->start0-->run(Thread类中的run),方法中:target是Runnable类型,创建Thread对象时,给target变量赋值了,是Runnable的实现类对象,因此方法中的判断是true,多态形式调用方法,编译看父类运行看子类。
    • 实现Callab好处

      • 任务有返回结果,可以抛出异常

      • 基本与Runnable类似

      • 原理

        • start-->start0-->run(Thread类中的run),方法中:target是Runnable类型,创建Thread对象时,给target变量赋值了,是Runnable的实现类对象, 是FutureTask,因此方法中的判断是true,多态形式调用方法,编译看父类运行看子类,执行的是FutureTask中的run方法。在该方法中,

常用方法

  • String getName()

    • 获取线程名

    • 线程默认格式

      • Thread-编号,编号是从0开始
    • 主线程默认的名称

      • main
  • Thread static currentThread()

    • 返回当前正在执行的线程对象的引用
  • void setName(String name)

    • 将线程名称设置为name

    • 线程启动前或启动后去设置都可以

    • 构造方法也可给线程设置名称

      • new Thread(String name)
      • new Thread(Runnable run,String name)
  • static sleep(long millis)

    • 当前线程休眠millis时间
    • 主动放弃执行资格,cpu不用调度
    • 时间到,线程醒过来,恢复执行资格,等待cpu调度
    • 1000毫秒=1秒
  • int getPriority()

    • 获取线程的优先级
    • 每个线程在创建时都有一个优先级,默认是5,一共10个级别,1最小,10最大。
    • 优先级大的线程,被执行的概率更高
  • void setPriority(int)

    • 设置线程优先级

    • 常量

      • MAX_PRIORITY

        • 线程可以具有的最高优先级
      • MIN_PRIORITY

        • 线程可以具有的最低优先级
      • NORM_PRIORITY

        • 分配给线程的默认优先级
  • isDaemon()

    • 判断线程是否是守护线程
    • 守护线程也叫后台线程,为前台线程(非守护线程)提供服务
    • 守护线程是依赖于前台线程而存在的,前台线程结束守护线程也会随之结束
    • 默认线程是非守护线程
  • setDaemon(true)

    • 设置线程为守护线程,在线程前去设置
  • interrupt()

    • 中断线程执行,想要中断哪个线程,必须在其另一个线程去调用
  • static void yiele()

    • 让步的意思。某个线程指向到该方法,意思是把cpu的执行权让出来,让其他线程或自己优先执行。注意:调用后有可能该线程会得到cpu的执行权也能得不到。

线程安全

线程安全问题产生

  • 多线程执行时,共同操作同一个资源数据, 其中一个线程对共享资源数据的一次计算还没有执行完还没有完成,另一个线程参与进来执行,从而导致错误数据的产生
  • t1刚执行完判断和打印,没有执行减减计算,t2获取cpu执行权执行if和打印...

线程安全问题解决

  • 1、同步代码块

    • synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

    • 格式

      • synchronized(同步锁){
        需要同步操作的代码
        }
    • 同步锁

      • 对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
        1. 锁对象 可以是任意类型。
        1. 多个线程对象 要使用同一把锁。
      • 3、至少两个或两个以上线程
    • 执行流程

      • lock是一个对象,这个对象有两种状态:开和关,默认状态是开。
      • 某个线程获取到执行权,执行到synchronized该语句,会先判断lock的状态,如果是关,则不能进入同步块中;如果是开,立即获取lock对象(线程持有该锁对象),将lock的状态由开改为关,线程执行到sleep后不会释放持有的锁。持有锁的线程一旦出了同步块,会立即释放持有的锁,将锁的状态由关改为开。
    • 好处

      • 解决了安全问题
    • 弊端

      • 线程执行需要判断锁,只有持有了锁,才能执行,因此执行效率降低
  • 2、同步方法

    • 使用关键字synchronized修饰的方法

    • 特点

      • 有锁对象,对象固定,不需要手动指定
    • 方法分为

      • 非静态方法

        • 锁对象是this
      • 静态方法

        • 锁对象是静态方法所属的类的字节码对象
        • 类名.class
      • run方法也可以使用同步关键字修饰,但修饰后为单线程
  • 3、显示锁

    • jdk5.0中的接口,叫做Lock,可以显示加锁和显示释放锁,java.util.concurrent.locks

    • 方法分别是

      • lock()添加锁
      • unLock()释放锁
    • 使用其子类

      • ReentrantLock

死锁

多线程并发执行时,各自持有资源,都想要对方的资源,但是持有资源的线程又不释放资源,

导致程序不能向下执行,就会出现所谓的”卡”的现象,叫做死锁。

  • 实际方法中避免写出死锁

模拟实现死锁

  • 同步嵌套

工具

  • 可以检测是否有死锁
  • 打开命令行窗口,
    cmd---->tasklist
    找java/javaw pid编号
  • jstack+pid编号

线程生命周期

生命周期

  • 是一个过程,从创建到终止的过程,在过程中可以有不同的阶段

线程生命周期

  • 线程从创建到消亡的过程,在这个过程中,也会有不同阶段,这些阶段可以进行转换

    • 别名:线程的状态图、线程的声明状态图

新建状态

  • 线程对象被创建,执行了new Thread()后

就绪状态(可运行)

  • 调用start方法后的状态,有运行资格,等待cpu

运行状态

  • 执行run方法的状态

消亡状态

  • run方法结束,出现异常、调用stop/destory方法

阻塞状态

  • 主动放弃执行资格,cpu不在调度,sleep方法

线程各个状态都是理论上的状态,实际上线程某一个时刻只能处于一种状态,这个状态可以通过方法getState获取到,方法的返回值是Thread.State,State是一个枚举类

wait和sleep方法的区别

  • sleep方法不会释放锁对象,时间一到,回到准备状态

枚举

引用类型

  • 数组 类 接口 枚举

枚举类型

  • 也是一个类类型,是一个特殊的类,jdk5.0

格式

  • 修饰符 class 类名{}
  • 修饰符 interface 接口名{}
  • 修饰符 enum 枚举类名{
    1.枚举值,该类对象
    2.成员变量,要在枚举值的下边
    3.构造方法,要私有化,可以重载,调用重载需在枚举值后边传参
    4.成员方法
    5.抽象方法,一定要重写(每个枚举值都要进行重写,重写格式有点类似于匿名内部类)

}

- enum Level{
//枚举值
A(100){
    @Override
    public void method() {
        System.out.println("method");
    }
},B{
        @Override
        public void method() {
        }
};
//,C,D,E;
//成员变量
int  score;
//构造方法---私有的
private Level(){
    System.out.println("执行了~~~");
}
private Level(int score){
    System.out.println("执行了----"+score);
}
//方法
public  void   show(){
    System.out.println("show");
}
//抽象方法
public abstract void  method();

}

switch语句

  • 表达式结果是byte short int String char 枚举

注意

  • 枚举值名按照常量名的命名规范编写,通常都是大写字母或单词表示。

常用方法

  • toString()

    • 打印枚举值名
  • ordinal()

    • 返回序号
  • name()

    • 返回枚举值名
  • compareTo()

    • 序号的差值比较
  • values()

    • 获取所有的枚举值

线程池

  • 容器。

线程池

  • 存储线程的

原因

  • 创建线程和销毁线程都需要调用底层资源,这个过程是很耗费资源的,尤其是线程任务比较多但是有比较小的情况下,性能损耗比较严重。

解决方式

  • 提前准备好线程对象,任务过来从线程池出出来一个线程来执行任务,任务结束也不销毁该线程,让该线程回到线程池,继续等待下一个任务。

在java中,有现有的线程池供我们使用,不需要自己创建(可以自己自定义线程)。

java.util.concurrrent包。

Executors类,创建线程池

  • 线程池默认线程个数是0,最大线程个数是:int的最大值

    • ExecutorService es = Executors.newCachedThreadPool();
  • 线程池默认线程个数是0,最大线程个数是:指定的个数,2

    • ExecutorService es = Executors.newFixedThreadPool(2);
  • 向下转型

    • ThreadPoolExecutor tpe = (ThreadPoolExecutor)es;
  • 获取当前线程池中的线程个数

    • tpe.getPoolSize()
    • Integer.MAX_VALUE
  • 提交任务,让线程中的线程执行任务

    • es.submit()
    • submit参数是Runnable类型或Callable类型
  • 关闭线程池,会将已提交的任务执行完,不在接受新任务

    • es.shutdown()
  • 关闭线程池,试图停止正在执行的任务,将已提交并出于等待的线程作不处理

    • es.shutdownNow()

ThreadPoolExecutor对象,创建线程池

  • 通过Executors创建的线程还是有些使用的灵活度问题,建议使用ThreadPoolExecutor对象。

  • 参数

    • corePoolSize - 池中所保存的线程数,包括空闲线程。
    • maximumPoolSize - 池中允许的最大线程数。
    • keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
    • unit - keepAliveTime 参数的时间单位。
    • workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。
    • threadFactory - 执行程序创建新线程时使用的工厂。
    • handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
    • 注意: corePoolSize <0 maximumPoolSize <= 0 corePoolSize > maximumPoolSize
      keepAliveTime < 0 这样设置不可以,如果这样设置会出现非法参数异常。
      workQueue、threaDFactory、handler 都不能为null,如果为null这出异常。
  • TimeUnit

    • 时间单位,是一个枚举类,直接类名.枚举值访问
  • workQueue

    • 提交队列

      • SynchronousQueue,默认工作模式,任务执行阻塞式的,该任务执行完,才会执行下一个
    • 无界队列

      • LinkedBlockingQueue,maximumPoolSize,设置无效,任务处理依赖于corePoolSize
    • 有界队列

      • ArrayBlockingQueue ,最多能处理的线程数:maximumPoolSize+容量(手动指定的)
  • handler

    • 任务超出范围时,采用的拒绝模式

    • static class ThreadPoolExecutor.AbortPolicy

      • 用于被拒绝任务的处理程序,它将抛出 RejectedExecutionException.
    • static class ThreadPoolExecutor.CallerRunsPolicy

      • 用于被拒绝任务的处理程序,它直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。
    • static class ThreadPoolExecutor.DiscardOldestPolicy

      • 用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试 execute;如果执行程序已关闭,则会丢弃该任务。
    • static class ThreadPoolExecutor.DiscardPolicy

      •       用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。 
        
  • 创建线程池对象

    • //参数顺序:核心线程数 最大线程数 空闲线程存在时长 时间单位 工作队列 线程工程 拒绝策略
    • ThreadPoolExecutor tpe = new ThreadPoolExecutor(1,3,20, TimeUnit.SECONDS,new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardPolicy());