Java 基础巩固-线程

80 阅读5分钟

Java 基础巩固-线程

一. 线程

1.1 线程相关概念

  • 单线程:同一个时刻,只允许执行一个线程
  • 多线程,同一时刻,可以执行多个线程。例如:一个qq进程,可以开多个窗口
  • 并发(来回切换):同一时刻,多个任务交替执行,造成一种"貌似同时"的错觉,简单的说,单核cpu实现的多任务就是并发
  • 并行(同时执行):同一时刻,多个任务同时执行。多核cpu可以实现并行

1.2 创建线程的几种方式

  1. 使用Thread类
  2. 实现 Runnable 接口
  3. 使用Callable和Future来创建线程
  4. 使用线程池
  5. 使用定时器
  6. 使用java8新特性 stream 实现并发
  7. 使用匿名内部类

1.3 为什么用start方法而不是直接用run方法

  • 使用run :相当于直接用主线程调用run方法,只有当run执行完毕以后,才会执行后面的代码
  • 使用start:相当于在主线程后面开了子线程,真正实现了多线程
  • 源码分析
    1. 首先会调用 start 方法
         public synchronized void start()
    2. 接着调用 native 修饰的 start0 本地方法,该方法由 jvm 调用
         start0()
    

1.4 案例:使用 Runnable 接口方式实现多线程

public class Thread01 {
    public static void main(String[] args) {
        Tiger tiger = new Tiger();
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
    }
}

class Animal{}
class Tiger extends Animal implements Runnable {

    @Override
    public void run() {
        System.out.println("老虎叫");
    }
}

class ThreadProxy implements Runnable {

    Runnable target = null;

    public ThreadProxy(Runnable target) {
        this.target = target;
    }

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

    public void start(){
        start0();
    }

    public void start0() {
        run();
    }
}

1.5 继承 Thread vs 实现 Runnable 接口的区别

  1. 从 java 的设计来看,通过继承 Thread 或者实现 Runnable 接口来创建线程没有本质区别,因为 Thread 类本身就实现了 Runnable 接口
  2. 实现 Runnable 接口的方式更加适合多个线程共享一个资源的情况,避免了单继承的限制
    T1 t1 = new T1();
    
    Thread thread = new Thread(t1);
    Thread thread1 = new Thread(t1);
    
    thread.start();
    thread1.start();
    

1.6 线程常用方法

1.6.1 第一组常用方法

setName() 设置线程名称
getName() 返回该线程名称
start()   使线程开始执行
run()     调用线程对象的 run 方法
setPriority() 更改线程优先级
getPriority() 获取线程优先级
sleep()   在指定的毫秒数内让当前正在执行的线程休眠
interrupt() 中断线程

1.6.2 第二组常用方法

yield() :让出当前进程的执行,不一定礼让成功
join()  :线程插队

1.7 用户线程和守护线程

1.7.1 简介

  • 用户线程:也叫工作线程,当线程的任务执行完毕或用通知方式结束
  • 守护线程:一般为工作线程服务,当所有的用户线程结束,守护线程自动结束
  • 常见守护线程:垃圾回收机制

1.7.2 守护线程案例

public class Method01 {
    public static void main(String[] args) throws InterruptedException {
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        // 将该线程设置为守护进程,会随着用户进程结束而结束
        myDaemonThread.setDaemon(true);
        myDaemonThread.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("宝强辛苦的工作");
            Thread.sleep(1000);
        }
        System.out.println("宝强回家啦");
    }
}

class MyDaemonThread extends Thread {
    @Override
    public void run() {
        while(true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("马蓉和宋喆快乐聊天");
        }
    }
}

1.8 线程生命周期

1.8.1 生命周期介绍

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

1.8.2 生命周期图

image.png

1.9 Synchronized

1.9.1 线程同步机制

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

1.9.2 同步具体方法 - Synchronized

1. 同步代码块

synchronized(对象) { // 得到对象的锁,才能操作同步代码
    // 需要被同步的代码
}

2. synchronized 还可以放到方法声明中,表示该方法为同步方法

public synchronized void m(String name){
    // 需要被同步的代码
}

1.10 互斥锁

1.10.1 互斥锁介绍

  • Java 语言中,引入了对象互斥锁的概念,来保证共享数据的完整性
  • 每个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
  • 同步的局限性:导致程序执行效率降低
  • 同步方法(非静态)的锁可以是this,也可以是其他对象(要求是同一对象)
  • 同步方法(静态)的锁为当前类本身

1.10.2 注意事项和细节

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

1.11 死锁

多个线程都占用了对象的锁资源,都不肯让,导致了死锁

1.12 释放锁

1.12.1 以下操作会释放锁

  1. 当前线程的同步方法、同步代码快执行结束
  2. 当前线程在同步代码快、同步方法中遇到 break 、return
  3. 当前线程在同步代码快、同步方法中出现了未处理的 errorException,导致异常结束
  4. 当前线程在同步代码快、同步方法中执行了wait方法,当前线程暂停

1.12.2 以下操作不会释放锁

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