首页 > 代码库 > java自学之路-day17

java自学之路-day17

JAVA17

多线程

进程和线程的概念

进程

l  正在运行中的程序

l  程序存储在硬盘中 当进入内存运行时

l  内存中的每一个程序就叫进程

l  并且每一个进程都有一个独立的功能

线程

l  迅雷中的多线程下载

l  main方法中有很多方法一次排列

  1. 如果其中一个方法执行了很多次循环
  2. 那么下一个方法就不会执行
  3. 从入口main到结束,一条路走到底 必须一个一个的执行
  4. 这种程序是单线程程序
  5. 效率慢

 

l  例如

多台电脑网吧上网

多条路通行

线程

概念

l  CPU 中央处理器 inter AWD

l  四核心 八线程

l  360杀毒工具

  1. 进入内存
  2. 有一个进程 360safe.exe
  3. 所有功能进入内存
  4. 所有功能都是程序中的一个方法
  5. 所有功能可以单独同时运行
  6. 所有功能由CPU运行
  7. 每开启一个功能 cpu就会开启新的执行路径
  8. 也就是一个新的线程
  9. 早期cpu一次只能执行一个线程 时间片轮回切换运行
  10. 当前cpu有多核心 那么同一时刻就能执行多个线程
  11.  

 

迅雷下载

l  多线程下载

l  并不是提高了网速 而是多线程提高了速度

 

线程的运行模式

分时调度

l  所有线程轮流使用cpu资源 平均分配对个线程占用CPU时间

抢占式调度

l  优先级高的多使用cpu资源 优先级相同的随机选择一个进行调度

l   

 

main主线程

l  编译

l  JVM运行main方法

l  找操作系统 开线程

l  对于cpu有了一个执行的路径 运行方法main路径有个名字main

l  这个就是主线程

 

 

l  多线程就算一条线程出现错误 其他线程也会执行

Thread类

概述

l  Java.lang包中

l  Jvm允许程序运行多个线程

l  每一个线程都有一个优先级

l  创建线程有两个方法

  1. 继承Thread类 重写run方法
  2. 实现Runable接口 实现run方法

实现线程继承Thread

l  定义一个类继承Thread 重写run方法

 

l  在测试类中实例化这个类的对象 调用start方法

 

 

l  Start方法只能执行一次

l  执行顺序跟之前不一样

l  图解

  1. Jvm开启main主线程
  2. cpu运行主线程
  3. 运行中 方法中开了一个新线程
  4. Cpu执行新线程
  5. 继续执行start方法启动新线程 准备执行run方法
  6. cpu有了两个执行路径
  7. cpu自己控制运行main方法的循环还是run方法的循环
  8. cpu分配时间片给线程

 

为什么要继承Thread

l  调用run和start区别

  1. Start开启线程 并让jvm调用run方法在开启的线程中运行
  2. run就是一个方法而已 等待被执行 没有其他功能

l  Thread是线程类 继承这个类就是 线程类

l  直接创建Thread类对象,run方法是这个类的方法,没有任何操作 不能运行自定义的代码 所以需要继承这个类 重写run方法然后执行我们需要在另一个线程中执行的代码

l  Run方法的作用是为了执行我们要在新线程中执行的代码

线程运行的内存图

l  Main方法先进入

l  然后调用start方法 run方法准备被jvm调用

l  Run方法不进入这个栈 会新开一个栈空间单独执行run方法

l   

 

获取线程名称

l  main主线程名称就是 main

l  控制台获取默认线程名字

 

l  getName获取线程名

 

调用父类方法可以不写super

l  获取main线程名字

Static Thread currentThread();

获取正在运行本方法的线程名字 返回Thread类型

 

简化

 

设置线程名称

l  setName() 

  1. 需要在主线程中改名
  2. main线程名改不了

 

l  构造方法改线程名

  1. 父类中有一个构造方法可以改名称

 

  1.  

 

Sleep方法

l  线程停止特定时间

 

 

 

l  抛异常

因为休眠过程中被唤醒会抛此异常

 Runnable接口

l  实现线程的的另一种方式

l  实现接口Runnable

l  实现方法run

示例

  1. 先创建接口实现类
  2. 然后创建Thread对象 传递实现类对象
  3. 然后调用start方法

原理

 

  1. Thread有一个构造器方法 可以传入Runnable类型对象
  2. 定义一个类实现Runable接口 新建对象传入Thread的构造方法中就可以开启一个新线程运行代码
  3. 避免了单继承局限性
  4. 线程分为了两个部分 一部分是线程对象 一部分是线程任务
  5. 从而降低了耦合度
  6.  

好处

 

匿名内部类实现线程程序

l  前提 继承或接口实现

l  new 父类或者接口实现(){   重写抽象方法     }

l  示例

  1. 继承Thread

 

  1.  实现接口

 

线程的状态图

l  NEW   新建状态 

 

l  RUNNABLE   运行状态 正在JVM虚拟机中执行此线程

 

l  BLOCKED   受阻塞

死锁 CPU资源被抢走

 

 

l  WAITTING        等待 无限休眠

  Object类中的方法

 

l  TIMED_ WAITTING   休眠

 

l  TREMINATED 死亡状态

 

l  受阻塞具有cpu的执行资格 等待CPU的资源

l  休眠等待 线程放弃CPU的执行资格

线程池

概述

l  是一个容器可以存放多个线程

l  程序一开始的时候 创建多个线程 存放到集合中

l  使用时 用remove方法取出线程 使用完毕 再用add重新添加进去

l  之前都是自己开发线程池

l  Jdk1.5之后添加了线程池技术

使用

使用线程池方式 Runable接口

l  由线程池工厂创建

l  再调用线程池中的方法创建线程

l  Executors类

  1. Java.util.concurrent包
  2. 方法

a)         创建多个

 

b)         创建单个

 

c)         返回值是线程池类对象

 

  1. 示例

 

 

控制台没停止 线程用完后有回到了线程池

线程名字

 

停止线程

Shutdown 线程停止

实现线程Callable方法

l  Runnable接口 线程运行完没有结果 不能抛异常

l  Jdk1.5后有个Callable接口 call方法 等同于run

l  call方法有返回值 可以抛异常 

l  先用工厂类静态方法newFixedRhreadPool创建线程池对象

l  线程对象 调用方法submit提交线程任务 传入一个Callable接口实现类

l  示例

 

 

父类接口抛了异常 子类可抛异常也可不抛异常

练习

l  异步计算

线程操作共享数据的安全问题

售票示例

l  多个线程同时运行 同时运行某段代码

l  每次运行结果和单线程运行结果一样

l  售票

l  多种方式购票 多个线程操作同一个数据

l  此时应该数据同时更新 否则会出现安全问题

l  示例

 

 

 

数据正常 但是存在安全隐患

安全问题引发

l  T0判断完毕 准备开始操作

l  此时cpu被t1线程抢占

l  T1判断完毕 准备进行操作

l  此时被t2线程抢占

l  T2判断完毕 准备进行操作

l  此时cpu资源获得 t0执行操作 – 数据为0

l  T1也执行—数据为-1

l  T2也执行--数据为-2

l  此时出现了线程安全问题

l  模拟示例

  1. 执行前停顿一下 sleep
  2.  

 

解决

  1. 同步代码块
  2. 当一个线程进入数据操作是无论是否休眠 其他线程只能等待
  3. Sun公司提供了一个技术实现了这样的解决
  4. 公式

Synchronzied(任意对象){

   线程操作的共享数据

}

  1. 同步代码块
  2. 示例

 

不能写匿名对象

 

变得安全了

 

但是速度变慢了

执行原理

l  同步对象 任意对象

l  对象 :同步锁 对象监视器

l  同步保证安全性 没有锁的线程不能执行 只能等

l  线程遇到同步代码块后 判断同步锁还有没有

l  如果没有就等待

l  如果有 获取锁  将同步锁设为0

l  进入同步代码块 此时如果休眠了

l  另一个线程过来 要执行代码 会判断同步锁是否有 此时显然是没有的 因此就进不去代码块 不能执行代码

l  此时如果第一个线程 唤醒了 就继续执行代码  然后释放同步锁

l  因此一个线程需要判断锁 获取锁 释放锁 所以就延长了很长时间

 

l  同步锁原理和上厕所

  1. 对象就是厕所的门
  2. 同步锁就是厕所门的锁

l  多个线程访问一个共享数据就要设置同步锁

同步方法

l  代码简洁

l  将线程共享数据和同步抽取到一个方法中

 

l  方法的声明加上同步关键字 然后把同步不代码块删除

 

l  StringBuffer 就有这种同步方法 跑的慢

 

l  StringBuilder 是线程不安全 跑的快

 

l  同步方法有锁吗?

  1. 肯定有
  2. 对象锁是本类的对象引用 this

 

l  如果方法是静态的

  1. 静态方法中的锁不是this本类对象引用
  2. 静态不属于对象引用
  3. 静态方法中对象锁是 本类类名.class

 

 

  1. 涉及到反射的原理

JDK1.5新特新 Lock接口

l  释放同步锁 看不到

l  如果代码出现异常 锁就不会释放

l  因此JDK1.5后出现了Lock接口

l  此接口提供了比使用synchornized更多的方法和语句可获得的跟广泛的锁定操作

l  示例

  1. 接口方法

lock 获取锁

unlock 释放锁

  1. 实现类

ReentrantLock

  1. 在成员变量 通过实现类创建lock接口的实现类对象
  2. 在要锁的代码前 调用lock方法获取锁
  3. 在要锁的代码后调用 unlock方法释放锁
  4. 如果有异常在异常后面的finally代码里面写unclock 可保证异常出现时正常释放锁

死锁

原理

l  同步锁里面又写了一个同步

l  程序中出现了无限等待

l  前提必须是多线程出现同步嵌套

l  线程进入同步获取锁 不出去同步不会释放锁

l  第一个人需要第二个人的锁 第二个人有需要第第一个人的锁

模拟实现

l  定义两个锁对象 A B

  1. 需要定义两个类
  2. 并且建立私有构造方法 是外类不能新建实例
  3. 然后提供一个静态final对象 让外类直接使用 但不可更改

 

l  循环用奇数偶数确定线程1 和线程2

 

 

 

l  然后一个测试类

  1. 创建两个线程来执行run方法
  2.  

 

l  程序运行结果 程序永远都不会停止运行

 

  1. 前几次都是抢占成功的
  2. 最后两次 第一次  执行偶数循环,获取了a锁,但是还没进入b 锁,就被奇数循环抢占了,获取了b锁
  3. 此时第一次需要b锁才能继续执行   第二次需要a锁才能继续执行
  4. 那么此时谁都拿不到所需的资源 所以就死锁了

线程等待和唤醒

概述

l  又叫线程通信

l  多个线程处理同一个资源

l  每个线程任务不一样

l  如果要合理的运用资源

l  就需要通过一种手段使各个线程能有效的利用资源

l  这种手段就叫做 等待唤醒机制

l  比如售票 之前只是减票 现在有的线程需要加票

示例

l  资源类

  1. 定义公共两个成员变量

 

l  两个线程类 输入 输出

  1. 输入

 

  1. 输出

 

l  测试类

 

 

因为有两个对象

l  解决

  1. 死锁 资源类中写私有对象 让外类通过调用新建实例
  2. 在输入类和输入类中 写一个可接受资源类对象的构造器
  3. 然后在main方法中 新建一个资源类对象 传入输出类和输入类的构造方法中
  4. 结果

 

  1. 出现了性别乱套

l  解决

  1. 问题出现原理

a)         输入类抢到了cpu 进行赋值  张三 男

b)         赋值 完 输出没有抢到cpu

c)         输入类仍然抢到了cpu  进行赋值 lisi

d)         刚赋值完lisi 还没赋值nv

e)         输出类抢到了cpu资源 就直接输出 了 lisi nv

f)          此时就出现了 性别乱套

 

  1. 解决

a)         只有一种方法加同步锁

b)         找共享数据

c)         在输入类中加锁

 

输出类加入锁

 

还是没解决

 

l  解决

  1. 原因

a)         两个线程是不是同一个锁

  1. 将对象锁this改为对象锁为 资源对象 r
  2. 结果 解决了问题

 

案例分析

l  最终目标 一个输入 一个输出 交替出现

l  线程只会执行run方法 不分赋值和取值

l  理想状态应该是 一次赋值一次打印

l  只有上一次赋值输出后 下一次赋值才能开始

l  反过来 输出一次后 必须等待下一次输入完毕后才能进行输出

l  实现步骤

  1. 输入:赋值后执行方法wait永远等待
  2. 输出 变量值打印输出后 notify输入唤醒 然后输出执行wait永远等待
  3. 输入:被唤醒后,重新对变量赋值 赋值后必须唤醒输出的线程botify 输入wait

实现

l  为了保证程序执行 输入首先拿到cpu执行权

l  在资源类中加一个变量boolean flag

l  flage为真 说明赋值完成

l  flage为假 说明获取值完成

l  输入 需要判断标记 flage是否为真

如果为真  则等待

如果为假 就进行赋值 并把标记改为false

l  输出也是如此

 

l  示例

  1. 加变量值

 

输入

 

输出

 

结果 抛出了异常

 

异常

 

就是说唤醒和等待方法 调用者错了

应该是锁对象 调用

修改

 

 

l  结果

 

java自学之路-day17