多线程

进程的相关概念

  1. 进程之运行中的程序。

  2. 进程是程序一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程

线程的相关概念

  1. 线程又进程创建的,是进程的一个实体
  2. 一个进程可以拥有多个线程

其他:

  • 单线程:同一个时刻,只允许执行一个线程
  • 多线程:同一时刻,可以执行多个线程
  • 并发:同一时刻,多个任务交替执行,造成一种”貌似同时“的错觉, 简单的说,单核 cpu 实现的多任务就是并发

    cpu 在不同的进程中反复切换焦点

  • 并行:同一时刻,多个任务同时执行。多核 cpu 可以实现并行

    由不同的核心同时承担任务的执行

线程基本使用

创建线程

  1. 继承 Thread 类,重写 run 方法
  2. 实现 Runnable 接口,重写 run 方法
  3. 使用一个 Thread 对象的 start 方法开启线程
// 如果是实现 Runnable 接口的类,可以使用这个方式开启线程
Test t1 = new Test();
Thread thread = new Thread(t1);
thread.start();

// 代理设计模式

TIP

请尽量使用实现 Runnable 接口的方式,因为 java 是单继承的,如果继承了 Thread 就无法再继承其他类

  1. start()方法底层调用了start0()方法,并由 jvm 执行 start0()
  2. start0()在不同系统下的算法不同。
  3. start0()方法只是让线程变成了可运行状态,具体运行取决于 cpu,由 cpu 统一调度

线程常用方法

  • 常用方法一
  1. setName() 设置线程名称,和 name 参数相同
  2. getName() 返回该线程的名称
  3. start() 是线程开始执行
  4. run() 调用线程对象 run 方法
  5. setPriority() 更改线程的优先级
  6. getPriority() 获取线程的优先级
  7. sleep() 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
  8. interrupt() 中断线程
  1. start 底层会粗行间新的线程,调用 run, run 就是一个简单的方法调用,不会启动新线程
  2. 线程优先级的范围
    • MAX_PRIORITY 10
    • MIN_PRIORITY 1
    • NORM_PRIORITY 5
  3. interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠进程,即提前结束休眠
  4. sleep 线程的静态方法,使当前线程休眠
  • 常用方法二
  1. yield() 线程的礼让。让出 cpu,让其他线程执行,但礼让时间不确定,因此不一定成功
  2. join() 线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
  • 用户进程和守护进程
  1. 用户进程:也叫工作进程,当线程的任务执行完或通知方式结束
  2. 守护进程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
  3. 常见的守护线程:垃圾回收机制
Thread.setDaemon(true)

线程的生命周期

public static enum Thread.State
extends Enum<Thread.State>

线程状态:

  • NEW 尚未启动的线程处于此状态
  • RUNNABLE 在 Java 虚拟机中执行的线程处于此状态
  • BLOCKED 被阻塞等待监视器锁定的线程处于此状态
  • WAITING 正在等待另一个线程执行特定动作的线程处于此状态
  • TIMED_WAITING 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
  • TERMINATED 已退出的线程处于此状态

关系逻辑图:

Synchronized

线程同步机制:

  1. 在多线程编程里,一些敏感数据不运行被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
  2. 当有一个线程对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作

互斥锁

  1. 每个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
  2. 关键字 synchronized 来与对象的互斥锁联系。当某个对象用 sychronized 修饰时,表明该对象在任一时刻只能由一个线程访问
  3. 同步的局限性:导致程序的执行效率要降低
  4. 同步方法(非静态的)的锁可以是 this,也可以是其他对象(要求是同一个对象)
  5. 同步方法(静态的)的锁为当前类本身。

具体方法

//同步代码块
synchronized (对象) {
    //得到对象的锁,才能操作同步代码
    //需要被同步代码
}
//synchronized 还可以放在方法声明中,表示整个方法为同步方法

public synchronized void m (Strring name) {
    //需要被同步的代码
}
class T implements Runnable {

    Object obj = new Object();

    public synchronized void m () {
        System.out.println("m");
    }

    public void m1() {
        synchronized (/* this */ object) {
            System.out.println("m1");
        }
    }

    public static void m2() {
        synchronized (T.class) {
            System.out.println("m2"):
        }
    }
}

TIP

  1. 同步方法如果没有使用 static 修饰:默认锁对象为 this
  2. 如果方法使用 static 修饰,默认锁对象:当前类.class
  3. 实现的落地步骤:
    • 需要先分析上锁的代码
    • 选择同步代码块或同步方法
    • 要求多个线程的锁对象为同一个即可

线程的死锁

基本介绍 多个线程都占用对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生

public class DeadLock extends Thread {

    static Object o1 = new Object();
    static Object o2 = new Object();
    boolean flag;

    public DeadLock(boolean flag) {
        this.flag = flag;
    }

    public void run() {
        if (flag) {
            synchronized (o1) {
                System.out.println(Thread.currentThread().getName() + " 进入了 1");
                synchronized (o2) {
                    System.out.println((Thread.currentThread().getName() + " 进入了 2"));
                }
            }
        } else {
            synchronized (o2) {
                System.out.println(Thread.currentThread().getName() + " 进入了 2");
                synchronized (o1) {
                    System.out.println(Thread.currentThread().getName() + " 进入了 1");
                }
            }
        }
    }

    public static void main(String[] args) {
        DeadLock A = new DeadLock(true);
        A.setName("线程 A");
        DeadLock B = new DeadLock(false);
        B.setName("线程 B");

        A.start();
        B.start();
    }
}

这里两个线程互相抢夺锁,都进入 BLOCKED 状态后无法释放锁,进入死锁状态

释放锁

会释放锁:

  1. 当前线程的同步方法、同步代码块执行结束
  2. 当前线程在同步代码块、同步方法中遇到 break、return
  3. 当前线程在同步代码块、同步方法中出现了未处理的 Error 或 Exception,导致异常结束
  4. 当前线程在同步代码块、同步方法中执行了线程对象的 wait()方法,当线程暂停,并释放锁

不会释放锁:

  1. 线程执行同步代码块或同步方法时,程序调用Thread.sleep()Thread.yield()方法暂停当前线程的执行,不会释放锁
  2. 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁

WARNING

尽量避免使用 suspend()resume() 来控制线程,方法已经不再推荐使用