多线程

多线程

  • 程序
    • 一系列的cpu指令
    • 执行的时候位于内存中
    • 在外存中储存
  • 进程
    • 运行中的程序
    • 进程之间禁止数据干涉,数据通信异常麻烦
  • 线程
    • 进程中的一个片段
    • 线程之间可以共享数据

Thread类

  • 主要方法
  • 线程信息
  • static Thread currentThread() 获取线程名称 Returns a reference to the currently executing thread object.
  • String getName() 获取线程名称 Returns this thread's name.
  • void setName(String name) 设置线程名称 Changes the name of this thread to be equal to the argument name.
  • boolean isAlive() 线程是否活着 Tests if this thread is alive.
  • void join() 添加进程,等到他运行完成 Waits for this thread to die.
  • 线程调节
  • static void sleep(long millis) 线程休眠,释放cpu资源,但监控器不释放,对象锁不释放 Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and
  • void interrupt() 打断进程 Interrupts this thread.
  • 线程启动
  • void run() 方法体,线程入口 If this thread was constructed using a separate Runnable run object, then that Runnable object's run method is called; otherwise, this method does nothing and returns.
  • void start() 启动线程 Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread.

创建新线程方式

  1. 实现Runnable接口
    1. 通过具体类
      1. 初始化,写一个具体类,实现Runnable接口,run方法就是线程体,是线程的入口
      2. 创建,创建Thread类,具体类为实参
      3. 开始,调用线程的start方法,启动线程,会调用的run方法
      4. 停止,线程直接改变标志变量来停止不同循环,通知的方式停止线程
    2. 通过匿名内部类,的方式来实现Runnable接口,并重写需要执行的方法体
      • 匿名类的缺点是没有保存对象名及类名,不能对重写的匿名类和对象进行操作
    3. 原理
      1. 理解, 通过关联Runnable的方式来调用run方法体,来执行另一个线程
      2. 内存原理, 开辟另一个栈
      3. 代码原理, Thread对象的run方法调用runnable类的run方法,栈里两层run方法
  2. 继承Thread类
    1. 通过具体类继承
      1. 初始化,写一个类继承Thread,重写父类run方法
      2. 创建,创建具体类的对象,相当于创建创建了线程对象
      3. 调用这个对象的Start的方法
    2. 通过匿名内部类,的方式来继承Thread类,创建的同时重写run方法体

调度策略

  1. 调度策略
    • 时间片轮转
    • 抢占式:高优先度线程优先抢占资源
  2. java调度方法
    1. 同级优先度的线程组成先进先出对列(先到先服务),仍然使用抢占式策略
    2. 高优先度优先, 使用优先度的抢占策略
    • 线程的优先级控制
      • MAX_PRIORITY(10);
      • MIN _PRIORITY (1);
      • NORM_PRIORITY (5);
    • 涉及的方法:
      • getPriority() :返回线程优先值
      • setPriority(int newPriority) :改变线程的优先级
        • 线程创建时继承父线程的优先级
      • Thread类方法
        • static void yield():线程让步
          • 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
          • 若队列中没有同优先级的线程,忽略此方法
        • join() :当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止
          • 低优先级的线程也可以获得执行
        • static void sleep(long millis):(指定时间:毫秒)
          • 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
          • 抛出InterruptedException异常
          • 导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态,在调用sleep()方法的过程中,线程不会释放对象锁。
        • stop(): 强制线程生命期结束
        • boolean isAlive():返回boolean,判断线程是否还活着
        • void interrupt() 打断 Interrupts this thread.

生命周期

  1. 新建: 创建Tread类对象
  2. 就绪: 调用start方法,线程进入排队状态
  3. 运行: 线程获取到cpu资源,进行执行
  4. 阻塞: 当前资源得不到满足,无法执行,等待其他进程释放
  5. 死亡: 完成了当前进程所有任务,或被其他进程终止, 一旦死亡,不可以使用start重启,即每个创建的Thread对象只能启动一次

分类

  1. 守护线程
    • 保护用户线程
    • 服务用户线程,通过在start()方法前调用
    • thread.setDatemon(true)可以把一个用户线程变成一个守护线程
    • gc()线程,即垃圾回收器
    • jvm中都是守护线程,没有用户线程运行时当前jvm将退出。
  2. 用户线程
    • 重要

线程同步与安全

  • 问题提出,同时修改同一个数据
    • 多个线程对同一个数据的共享
    • 多个线程对同一账号的操作
    • 当前线程的条件判断和执行不在同一单位执行,导致条件改变依旧执行
  1. sysnchronized(this){ ...} //
    • 对象锁,使用同步代码块,代码块需要一个同步锁对象:同一时间内,只允许一个线程进入此代码
    • 存在于Object里面
    • 一旦被锁定,则锁定内容具有原子性,不可分割
    • sysnchronized可以修饰run函数,表示要执行完才释放资源,不就和单线程就没什么区别了
    • sysnchronized(),参数为多锁对象,当执行到sysnchronized代码块时检查锁对象是否还在,还在则持有执行,没有等待
    • 多线程的对象锁必须时一个才能互斥
    • 最好的锁对象时常量
    • 隐式锁,自动解锁
  2. 缺点,效率降低

死锁问题

  1. 不同的线程分别占用字节所需的资源,等对方释放资源
//死锁问题举例
public class TestDeadLock {
static StringBuffer s1 = new StringBuffer();
static StringBuffer s2 = new StringBuffer();

    public static void main(String[] args) {
    
        new Thread() {                      //线程1,运行需要s1,s2并依次获取
            public void run() {
                synchronized (s1) {         //对象锁S1
                    s2.append("A");
                    synchronized (s2) {     //对象锁s1
                        s2.append("B");
                        System.out.print(s1);
                        System.out.print(s2);
        }}}}.start();
            
        new Thread() {                      //线程2,运行需要s2,s1,并依次获取
            public void run() {
                synchronized (s2) {         //对象锁s2
                    s2.append("C");
                    synchronized (s1) {     //对象锁s1
                        s1.append("D");
                        System.out.print(s2);
                        System.out.print(s1);
        }}}}.start();
    }
}

线程通信&对象锁方法

  1. wait()
    • 令当前线程挂起并放弃cpu资源,监视器,对象锁,使别的线程可以访问并修改共享资源,而当前的线程被唤醒之后再排队等候拿到锁资源之后再次执行
    • 结束等待需要其他线程使用notify唤醒,否则线程死亡。
  2. notify()
    • 唤醒正在排队等待同步资源的线程中优先级最好者结束等待
    • 只能通知一个线程
  3. notifyAll() ,通知多个线程,一般指通过wait方法睡眠的线程
  4. 对象锁方法在sysnchronized语句块中可以调用,其他位置调用报错

线程同步问题(一个线程不间歇的访问另一个线程的标志变量,不会察觉到标志变量的改变)

  • 解决方式加volatile
  • volatile //主存的数据不会子线程储存副本
  • 百度查询
  1. 问题来源&内存模型
    1. cpu速度远远大于主存,为了加速优化,cpu所需要用到的数据会在高速缓冲中保留副本,之行完当前指令会再刷新回去,单线程的化不存在任何问题,但当多线程的时候当主存的内存改变了,而cpu还在使用高速缓存中的数据(不同的cpu有不同的缓存,或者单cpu使用轮回来实现多线程的时候也有不同的缓存)
      • 解决方式:
        1. 总线加锁方式,类似于读写保护,当有cpu进行访问主存一个数据时,其他cpu不允许访问,直达当前cpu完成操作。早期采用方式,但是这种方式会导致效率低
        2. 缓存一致协议,当前cpu写数据的时候发现它时共享变量时,向其他cpu发送消息,表明其他缓冲中的数据无效,当其他cpu需要用到缓存数据时要再次从缓存中读取。例:Intel 的MESI协议
    2. 多线程中的几个概念(多线程会导致的问题)
      1. 原子性
        1. 原子性可保证当前执行不会被其他线程打断
        2. 原子性指当前命令是否由cpu一次执行完
      2. 可见性
        1. 修改共享数据会让相关的cpu立即可知
      3. 有序性
        1. 指令是否重排
        2. cpu为了优化加速,对没有拓扑关系的命令会进行重排,保证结果的正确性,但多线程会打乱这个过程
  2. volatile关键字的两层语义
    • 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义
      • 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
      • 禁止进行指令重排序。
  3. 作用效果
    1. 使用volatile关键字会强制将修改的值立即写入主存;
    2. 使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);
    3. 由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。
posted @ 2018-11-22 21:02  热风轻浮  阅读(105)  评论(0编辑  收藏  举报