Java 多线程基础

Java 多线程

  • 多线程创建

    • java.lang.Thread
      • 线程继承 Thread类, 实现 run 方法
      • 示例代码
            public class Thread1 extends Thread {
                public void run() {
                    System.out.pinntln("hello");
                }
            }
        
    • java.lang.Runnable 接口
      • 线程实现 Runnable 接口, 实现 run 方法
      • 示例代码
            public class Thread2 implements Runnable {
                public void run() {
                    System.out.println("hello");
                }
            }
        
    • 线程创建方式比较: Thread vs Runable
      • Java是单继承的,所以继承 Thread 占据了父类的名额, 不如 Runnable 方便.
      • Thread 类实现了 Runnable
      • Runnable 启动时需要 Thread 类的支持, 需要放到一个 Thread 中才能 start.
      • Runnable 更容易实现多线程资源共享
      • 结论: 建议实现 Runnable 接口来完成多线程
  • 多线程启动

    • 调用 start 方法, 会自动以新线程调用run方法.
    • 不能直接调用run方法, 直接调用 run 方法, 将变成串行执行
    • 同一个线程, 多次 start 会报错, 只执行第一次 start 方法.
    • 多个线程启动, 其启动的先后顺序是随机的
    • 线程无需关闭, 只要其 run 方法执行结束后, 自动关闭
    • main函数(线程) 可能早于新线程结束, 整个程序并不终止
    • 整个程序终止是等所有的线程都终止(包括main函数线程)
  • 多线程信息共享

    • 通过共享变量达到信息共享
      • static 变量
      • 同一个 Runnable 类的成员变量
      • JDK原生库暂不支持点对点的信息通讯(类似 MPI 并行库直接发送消息, MPI(Message_Passing_Interface) 是一个信息传递应用程序接口, 目标是高性能/大规模性和可移植性, MPI在今天仍为高性能计算的主要模型. 具体请参考 https://en.wikipedia.org/wiki/Message_Passing_Interface)
      • 每个线程都有自己独立的工作缓存副本(从主存RAM里复制的副本), 其他线程改了数据之后当前的工作缓存副本还是之前旧的.
      • 变量副本问题, 采用 volatile 关键字修饰变量, 保证不同线程对共享变量操作时的可见性
      • 示例代码:
            // ThreadVar_volatile_Demo.java
            package hello;
            import java.time.LocalDateTime;
            public class ThreadVar_volatile_Demo {
                public static void main(String[] args) throws InterruptedException {
                    Thread_TestVolatile t1 = new Thread_TestVolatile();
                    t1.start();
                    Thread.sleep(2000); // 等待毫秒
                    t1.runFlag = false; // 主线程告诉子线程 你该终止辣.
                    System.out.println(LocalDateTime.now() + " main thread is exiting...");
                }
            }
            class Thread_TestVolatile extends Thread {
                //    boolean runFlag = true; // 子线程不会停止
                volatile boolean runFlag = true; //[重点] 用 volatile 修饰的变量会通知子线程刷新子线程的缓存副本
        
                public void run() {
                    int i = 0;
                    while (runFlag) {
                        i++;
                    }
                    System.out.println(LocalDateTime.now() + " Thread_TestVolatile is exit... ");
                }
            }
        
    • 代码段加锁 synchronized
      • 关键步骤加锁限制
        • 互斥: 某一个线程运行到某一代码段(关键代码), 其他线程不能同时运行这个代码段.
          • 代码段互斥的关键字是 synchronized, synchronized 修饰一个代码块或函数, 只能一个线程进入., synchronized 加大性能负担(其他线程都要排队等待), 但是使用简便.
          • 代码示例
                // Thread_Synchronized.java
                package hello;
            
                import static java.lang.Thread.sleep;
            
                public class Thread_Synchronized {
                    public static void main(String[] args) {
                        Thread_TestSynchronized t1 = new Thread_TestSynchronized();
                        new Thread(t1, "售票员小张").start();
                        new Thread(t1, "售票员小王").start();
                        new Thread(t1, "售票员小李").start();
                    }
                }
            
                class Thread_TestSynchronized implements Runnable {
                    private volatile int ticketAmount = 100; // 多线程共享的"剩余票数量"
                    final String str = new String("");
            
                    @Override
                    public void run() {
                        while (true) {
                            synchronized (str) { // [重点!!!]同步代码块, 一次只能有一个线程进入该代码块
                                sale();
                            }
            
                            try {
                                sleep(10); // 等待毫秒
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
            
                            if (ticketAmount <= 0) {
                                break;
                            }
                        }
                    }
            
                    private synchronized void sale() {
                        if (ticketAmount > 0) {
                            System.out.println(Thread.currentThread().getName()
                                    + " 卖出了一张票, 还剩余 " + --ticketAmount);
                        }
                    }
                }
            
        • 同步: 多个线程的运行, 必须按照某一种规定的先后顺序来运行.
        • 互斥是同步的一种最简单粗暴的特例.
  • 多线程管理

    • 线程类
      • 通过继承 Thread 或实现 Runnable
      • 通过 start 方法, 调用 run 方法, run方法工作
      • 线程 run 结束后, 线程退出
    • 粗粒度: 子线程之间, 和 main 线程之间缺乏同步
    • 细粒度: 线程之间有同步协作
      • 等待
      • 通知/唤醒
      • 终止
    • 线程状态
      • NEW 刚创建(new) 一个 Thread 对象被创建出来, 还没有开始 start
      • RUNNABLE 就绪(start) 可运行的就绪态, 在 start 调用之后, 等待被 CPU 调度
      • RUNNING 运行中(run)
      • BLOCK 阻塞(sleep) 某一个条件没有满足, 暂停
      • TERMINATED 结束, 线程被终止
    • 线程阻塞/唤醒
      • 废弃的API 官方不推荐使用的
        • 暂停和恢复 suspend/resume
        • 消亡 stop/destroy
      • sleep 时间一到, 会自己醒来
      • wait/notify/notifyAll 等待, 需要别人来唤醒
      • join 等待另一个线程结束, 跟着开始
      • interrupt 向另一个线程发送中断信号, 该线程收到信号, 会触发 InterruptedException(可解除阻塞), 并进行下一步处理
      • 示例代码
            package ThreadDemo;
        
            import java.util.Random;
        
            /**
             * 经典的生产者与消费者问题
             * 生产者不断往仓库里存放产品, 消费者从仓库里取出产品
             * 其中生产者和消费者都可以有若干个。
             * 仓库规则:容量有限,库满时不能存放,库空时不能取产品。
             */
            public class ProductTest {
                public static void main(String[] args) throws InterruptedException {
                    // 创建一个仓库
                    Storage storage = new Storage();
        
                    // 开始生产
                    new Thread(new Producer111(storage), "生产者1").start();
                    new Thread(new Producer111(storage), "生产者2").start();
        
                    // 开始消费
                    new Thread(new Consumer111(storage), "消费者1").start();
                    new Thread(new Consumer111(storage), "消费者2").start();
                }
            }
        
            /**
             * 仓库
             */
            class Storage {
                private Product[] products = new Product[10]; // 仓库容量为 10
                private int top = 0;
        
                // 生产者向仓库里放入产品
                public synchronized void push(Product product) {
                    while (top == products.length) {
                        try {
                            System.out.println("仓库满了 生产者请等待..." + Thread.currentThread().getName());
                            wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 把产品放入仓库
                    products[top++] = product;
                    System.out.println(Thread.currentThread().getName() + "生产了产品" + product);
                    System.out.println("producer notifyAll");
                    notifyAll(); // 唤醒等待线程
                }
        
                // 取出产品
                public synchronized Product pop() {
                    while (top == 0) {
                        try {
                            System.out.println("仓库里没货了, 消费者请等待.." + Thread.currentThread().getName());
                            wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
        
                    // 从仓库中取出物品
                    --top;
                    Product p = new Product(products[top].getId(), products[top].getName());
                    products[top] = null;
                    System.out.println("consumer notifyAll");
                    notifyAll();
                    return p;
                }
            }
        
            /**
             * 生产者
             */
            class Producer111 implements Runnable {
                private Storage storage;
        
                public Producer111(Storage storage) {
                    this.storage = storage;
                }
        
        
                @Override
                public void run() {
                    Random r = new Random();
                    int i = 0;
                    while (i < 10) {
                        i++;
                        Product p = new Product(i, "产品" + r.nextInt(1000));
                        this.storage.push(p);
                    }
        
                }
            }
        
            /**
             * 消费者
             */
            class Consumer111 implements Runnable {
                private Storage storage;
        
                public Consumer111(Storage storage) {
                    this.storage = storage;
                }
        
                @Override
                public void run() {
                    int i = 0;
                    while (i < 10) {
                        i++;
                        Product p = storage.pop();
                        System.out.println(Thread.currentThread().getName() + " 消费了产品 " + p);
                        try {
                            Thread.sleep(100); // 等待毫秒
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
        
                    }
                }
            }
        
            /**
             * 产品
             */
            class Product {
                private int id; // 产品id
                private String name; // 产品名字
        
                public Product(int id, String name) {
                    this.id = id;
                    this.name = name;
                }
        
                @Override
                public String toString() {
                    return "(产品ID: " + id + " 产品名称: " + name + ")";
                }
        
                public int getId() {
                    return id;
                }
        
                public void setId(int id) {
                    this.id = id;
                }
        
                public String getName() {
                    return this.name;
                }
        
                public void setName(String name) {
                    this.name = name;
                }
            }
        
    • 线程暂停/终止
      • 线程被动暂停和终止
        • 依靠别的线程来拯救自己(自己wait了只能等到别人来叫醒~)☹☹☹
        • 没有及时释放资源
      • 线程主动暂停和终止
        • 定期检测共享变量
        • 如果需要暂停或终止, 先释放资源, 再主动动作🙂🙂🙂
        • 暂停: Thread.sleep(), 休眠
        • 终止: run 方法结束, 线程终止
      • 代码示例
            // InterruptTest.java
            package ThreadDemo;
        
            import java.time.LocalDateTime;
        
            /**
             * t1 直接interrupt 在 睡眠过程中中断了, 会抛出异常。
             * t2 使用自定义的中断标志,不会抛出异常
             *  如果依赖于interrupt标志位的话 就需要添加异常处理。
             *  监控自定义的变量发生改变, 可以更好的控制资源的释放?
             */
            public class InterruptTest {
                public static void main(String[] args) throws InterruptedException {
                    TestTread1 t1 = new TestTread1();
                    TestThread2 t2 = new TestThread2();
        
                    t1.start();
                    t2.start();
        
                    // 让线程运行一会儿后中断
                    Thread.sleep(5000);
                    t1.interrupt(); // 让 t1 中断, 异常中断
                    t2.flag = false; // 自定义中断, 主动中断
                    System.out.println(LocalDateTime.now() + " main thread is exiting");
                }
            }
        
            class TestTread1 extends Thread {
                @Override
                public void run() {
                    // Thread类的中断信号标志,当前线程是否收到其他线程的 interrupt信号, 如果收到就返回true,否则返回false。
                    while (!interrupted()) { // 没有中断情况下运行。
                        System.out.println(LocalDateTime.now() + " " + "test thread1 is running");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            break;
                        }
                    }
        
                    System.out.println(LocalDateTime.now() + " " + "test thread1 is exiting");
                }
            }
        
            class TestThread2 extends Thread {
                public volatile boolean flag = true;
                @Override
                public void run() {
                    while (flag) { // 自定义判断标志
                        System.out.println(LocalDateTime.now() + " " + "test thread2 is running");
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(LocalDateTime.now() + " " + "test thread2 is exiting");
                }
            }
        
    • 多线程死锁
      • 每个线程互相持有别人需要的锁(线程A锁住了B需要的资源, 线程B锁住了A需要的资源)
      • 预防死锁, 对资源进行等级排序
      • 代码示例
            // ThreadDeadlock.java
            package ThreadDemo;
        
            import java.time.LocalDateTime;
            import java.util.concurrent.TimeUnit;
        
            /**
             * 问题描述:
             *  两个子线程 thread5111 和 thread5222:
             *  1)thread5111 先获取r1再获取r2,thread5222 先获取r2再获取r1。
             *  2)thread5111 要使用r2时,发现r2被其他线程锁住了。thread5222 要使用r1时,发现r1被其他线程锁住了。
             * 解决方法:
             *  用同样的操作顺序,都先获取r1再获取r2 或者 都先获取r2再获取r1.
             */
            public class ThreadDeadLock {
                public static final Integer r1 = 1;
                public static final Integer r2 = 2;
        
                public static void main(String[] args) {
                    TestThread51 t1 = new TestThread51();
                    t1.setName("thread5111");
                    t1.start();
        
                    TestThread52 t2 = new TestThread52();
                    t2.setName("thread5222");
                    t2.start();
        
                }
            }
        
        
            class TestThread51 extends Thread {
                @Override
                public void run() {
                    System.out.println(LocalDateTime.now() + " " + "TestThread51 开始run");
                    // 先获取r1, 再获取r2
                    synchronized (ThreadDeadLock.r1) {
                        System.out.println(LocalDateTime.now() + " " + "51取得了 r1");
                        try {
                            TimeUnit.SECONDS.sleep(3);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (ThreadDeadLock.r2) {
                            System.out.println(LocalDateTime.now() + " " + "51 获得了 r2");
                            System.out.println(LocalDateTime.now() + " " + "TestThread51 is running");
                        }
                    }
                }
            }
        
            class TestThread52 extends Thread {
        
                @Override
                public void run() {
                    System.out.println(LocalDateTime.now() + " " + "TestThread52 开始run");
                    // 先获取r2, 再获取r1
                    synchronized (ThreadDeadLock.r2) {
                        System.out.println(LocalDateTime.now() + " " + "52 获得了 r2");
                        try {
                            TimeUnit.SECONDS.sleep(3);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (ThreadDeadLock.r1) {
                            System.out.println(LocalDateTime.now() + " " + "52 获得了 r1");
                            System.out.println(LocalDateTime.now() + " " + "TestThread52 is running");
                        }
                    }
                }
            }
        
      • 线程查看工具 jvisualvm
        • 正确配置java的环境变量后, win+R 输入 jvisualvm 即可打开.
        • 开启 jvisualvm 后, 在左侧栏目看到ThreadDemo -> 线程 -> "检测到死锁!"" 的 "threaddump" 按钮
              // 查看上一段示例代码造成的死锁
              Found one Java-level deadlock:
              =============================
              "thread5222":
                waiting to lock monitor 0x00000000265c0c88 (object 0x0000000715c7b4e0, a java.lang.Integer),
                which is held by "thread5111" // --- 被另一个thread held 了
              "thread5111":
                waiting to lock monitor 0x00000000265c2128 (object 0x0000000715c7b4f0, a java.lang.Integer),
                which is held by "thread5222" // --- 被另一个thread held 了
          
              Java stack information for the threads listed above:
              ===================================================
              "thread5222":
                      at ThreadDemo.TestThread52.run(ThreadDeadLock.java:59)
                      - waiting to lock <0x0000000715c7b4e0> (a java.lang.Integer)
                      - locked <0x0000000715c7b4f0> (a java.lang.Integer)
              "thread5111":
                      at ThreadDemo.TestThread51.run(ThreadDeadLock.java:38)
                      - waiting to lock <0x0000000715c7b4f0> (a java.lang.Integer)
                      - locked <0x0000000715c7b4e0> (a java.lang.Integer)
          
              Found 1 deadlock.
          
    • 守护(后台)线程
      • 普通线程的结束, 是自身的 run 方法运行结束
      • 守护线程的结束, 是自身的 run 方法运行结束, 或 main 函数结束
      • 守护线程永远不要访问资源, 如文件或数据库等, 因为 main 函数结束了可能导致资源来不及释放等情况.
      • 示例代码
            package ThreadDemo;
        
            import java.time.LocalDateTime;
        
            /**
             * setDaemon 设定为守护线程。
             * 守护线程随 main 函数或自身的 run 方法结束而结束。
             */
            public class ThreadDaemonTest {
                public static void main(String[] args) throws InterruptedException {
                    TestThread4 t = new TestThread4();
                    t.setDaemon(true); // 指定为守护线程
                    t.start();
                    Thread.sleep(5000);
                    System.out.println(LocalDateTime.now() + " " + "main thread is exiting");
                }
            }
        
            class TestThread4 extends Thread {
                @Override
                public void run() {
                    while (true) {
                        System.out.println(LocalDateTime.now() + " " + "TestThread4 is running...");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        
  • 总结

    • 了解线程的多个状态
    • 了解线程协作机制
    • 线程协作尽量简单化, 采用粗粒度协作
    • 了解死锁和后台线程概念
    • 使用 jvisualvm 查看线程执行情况

posted on 2020-04-10 17:20  Sweet小马  阅读(120)  评论(0)    收藏  举报

导航