# JAVA 多线程笔记总结

# 线程简介

  1. 进程与线程

    进程(process) 线程 (Thread)
    进程是程序的一次执行,是系统进行资源分配和调度的一个独立单位 线程作为资源调度的基本单位,是程序的执行单元,执行路径
  2. 线程的 3 个基本状态

    1. 执行

    2. 就绪

    3. 阻塞

  3. 线程的 5 种基本操作

    1. 创建

    2. 可运行

    3. 阻塞

    4. 调度

    5. 被终止

  4. 线程的两个基本类型

    1. 用户级线程 :管理过程全部由用户程序完成,操作系统内核心只对进程进行管理。

    2. 系统级线程 (核心级线程):由操作系统内核进行管理

  5. 何为多任务?

    生活中有很多例子,如:在开车时打电话吃零食;即一个对象同时进行多项任务。

  6. 何为多线程?

    在打游戏时,游戏中出现的视频、音频、字幕、震动反馈等都是一个个的线程,在游戏这个应用程序下分支为多个线程,而游戏就是一个进程!

# Java 实现多线程

  • 方法一:通过继承 Thread 类调用线程
  1. 继承 Thread 类

    public class MyThread extends Thread {
    		// 线程体
        @Override// 重写 run 方法
        public void run() {
            for (int x = 0; x < 200; x++) {
                System.out.println(x);
            }
        }
    }
  2. start () 方法启动线程

    public static void main(String[] args) {
            //main 线程
    		//TextThread01 该代码的类
            // 创建一个线程对象
            TextThread01 textThread01 = new TextThread01();
            // 调用 start () 方法启动线程
            textThread01.start();
           
            for (int i = 0; i < 2000; i++) {
                System.out.println("打游戏  "+i);
            }
        }
  • 方法二:实现 Runnable 接口
  1. 实现 Runnable 接口

    public class TextThread03 implements Runnable {
        @Override
        public void run() {
            //run 方法线程体
            for (int i = 0; i < 200; i++) {
                System.out.println("正在敲代码---"+i);
            }
        }
  2. 在主线程下创建 Runnable 接口实现类对象 ,并使用 new Thread (对象名).start (); 启动线程

    public static void main(String[] args) {
            // 创建 Runnable 接口实现类对象
            TextThread03 thread03 = new TextThread03();
      // 启动方式一:
    //        Thread thread = new Thread();
    //
    //        thread.start();
     // 启动方式二:
            new Thread(thread03).start();
        }
  • 方式三:实现 Callable 接口,需要返回值类型
  1. 实现 callable 接口,重写 call 方法

    public class Text_Callable implements Callable<Boolean> {// 此处的返回值类型为布尔类型
        private String url;// 网络图片地址
        private String name;
        public Text_Callable(String url, String name) {
            this.url = url;
            this.name = name;
        }
    	
        @Override
        public Boolean call() {
            WebDownloader webDownload = new WebDownloader();
            webDownload.downloader(url, name);
            System.out.println("下载了文件名为" + name + "的文件");
            return  true;
        }
  2. 创建线程对象 (在 main 方法中)

    Text_Callable t1 = new Text_Callable("https://pic2.zhimg.com/80/v2-517bd06e7b6fdaec9b9a0b3df525fc29_1440w.jpg", "1.jpg");
            Text_Callable t2 = new Text_Callable("https://pic3.zhimg.com/80/v2-c9b19e6c66d08934922ed266d71fca5d_720w.jpg", "2.jpg");
            Text_Callable t3 = new Text_Callable("https://pic4.zhimg.com/80/v2-c5eeefcb0c6c9f86553ace622a7e8329_720w.jpg", "3.jpg");
  3. 创建执行服务:创建线程池

    ExecutorService ser = Executors.newFixedThreadPool(3);
  4. 配合 future 接口执行服务

    Future<Boolean> r1 = ser.submit(t1);
    Future<Boolean> r2 = ser.submit(t2);
    Future<Boolean> r3 = ser.submit(t3);
    
  5. 获取结果

    boolean rs1 = r1.get();
      boolean rs2 = r2.get();
      boolean rs3 = r3.get();

    6. 关闭服务

    ser.shutdownNow();

# 并发与并行

  • 并行:
    • 并行性是指同一时刻发生两个或多个事件
      • 并行是在不同的实体上的多个事件
  • 并发:
    • 并发性是指同一时间间隔内发生两个或多个事件
      • 并发是同一实体上的多个事件

# Thread 类源码分析

  1. 设置线程名

    Thread.currentThread().getName()
  2. 守护线程

    thread.setDaemon(true);// 默认为 false,表示用户线程
            thread.start();// 启动守护线程
            new Thread(you).start();// 启动用户线程
  3. 优先级线程

    调用 Thread.currentThread ().getPriority () 获得优先级线程的级别(int)

    创建 MyPriority 实现 runnable 接口

    class MyPriority implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
        }
    }

    new 一个 MyPriority 对象,创建线程

    MyPriority myPriority = new MyPriority();
    Thread t2 = new Thread(myPriority);

    先设置优先级再启动线程

    t2.setPriority(1);// 线程优先级范围 1-10
            t2.start();
  4. 线程生命周期

  • 线程休眠 (Thread.sleep ())

    调用 sleep 方法会进入计时等待状态,等时间到了,进入的是就绪状态而并非是运行状态

    // 模拟延时
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
  • 线程礼让 (Thread.yield ())

  • 让当前执行的线程执行但不阻塞,若让 CPU 重新调度,不一定会礼让

    此时线程由运行态变为就绪态

    public class TextYield {
        public static void main(String[] args) {
            MyYield myYield = new MyYield();
            new Thread(myYield,"a").start();// 线程 a
            new Thread(myYield,"b").start();// 线程 b
        }
    }
    class MyYield implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"线程开始执行");
            Thread.yield();// 线程礼让
            System.out.println(Thread.currentThread().getName()+"线程停止执行");
        }
    }
  • 线程霸占 (Thread.join ())

    • java 合并线程(即插队),其他线程阻塞,调用 join 方法,会等待该线程执行完毕后再执行别的线程
    public class TextJoin implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 200; i++) {
                System.out.println("线程vip来了"+i);
            }
        }
    	//vip 线程霸占主线程
        public static void main(String[] args) throws InterruptedException{
            //main 线程
            TextJoin textJoin = new TextJoin();
            Thread thread = new Thread(textJoin);
            thread.start();
            for (int i = 0; i < 200; i++) {
                if(i == 100){
                    thread.join();// 插队
                }
                System.out.println("main"+i);
            }
        }
    }
  • interrupt 方法

  • 我们一般使用的是 interrupt 来请求终止线程

    • 要注意的是:interrupt 不会真正停止一个线程,它仅仅是给这个线程发了一个信号告诉它,它应该要结束了 (明白这一点非常重要!)
    • 也就是说:Java 设计者实际上是想线程自己来终止,通过上面的信号,就可以判断处理什么业务了。
    • 具体到底中断还是继续运行,应该由被通知的线程自己处理
    Thread t1 = new Thread( new Runnable(){
        public void run(){
            // 若未发生中断,就正常执行任务
            while(!Thread.currentThread.isInterrupted()){
                // 正常任务代码……
            }
            // 中断的处理代码……
            doSomething();
        }
    } ).start();

# 线程同步

# 同步方法

public synchronized void method(int args){}
  • synchronized 方法都必须获得该方法的对象的锁才能执行,否则线程会出现阻塞
  • 方法一旦执行就会独占该锁,指导该方法返回才释放锁

# 同步块

synchronized (obj){}
  • obj 称为同步监视器
  • obj 可以是任何对象但推荐使用共享资源作为同步监视器

# 死锁(DeadLock)

造成死锁的原因可以概括成三句话:

  • 当前线程拥有其他线程需要的资源
  • 当前线程等待其他线程已拥有的资源
  • 都不放弃自己拥有的资源

# 1.1 锁顺序死锁

Makeup(int choice,String girlname){
        this.choice=choice;
        this.girlname=girlname;
    }
    @Override
    public void run() {
        // 化妆
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    // 化妆:互相持有对方的锁,就是需要拿到对方的资源
    private void makeup() throws InterruptedException {
        if (choice == 0){
            synchronized (lipstick){
                System.out.println(this.girlname+"获得口红的锁");
                Thread.sleep(1000);
                synchronized (mirror){
                    System.out.println(this.girlname+"获得镜子的锁");
                }
            }
        }else {
            synchronized (mirror){
                System.out.println(this.girlname+"获得镜子的锁");
                Thread.sleep(2000);
                synchronized (lipstick){
                    System.out.println(this.girlname+"获得口红的锁");
                }
            }
        }
    }

# 1.2 避免死锁的方法

避免死锁可以概括成三种方法:

  • 固定加锁的顺序 (针对锁顺序死锁)
  • 开放调用 (针对对象之间协作造成的死锁)
  • 使用定时锁–> tryLock()
    • 如果等待获取锁时间超时,则抛出异常而不是一直等待

# 1.3 线程池

线程池可以看做是线程的集合。在没有任务时线程处于空闲状态,当请求到来:线程池给这个请求分配一个空闲的线程,任务完成后回到线程池中等待下次任务 **(而不是销毁)。这样就实现了线程的重用 **。