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 接口来完成多线程
- java.lang.Thread
-
多线程启动
- 调用 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; } }
- 废弃的API 官方不推荐使用的
- 线程暂停/终止
- 线程被动暂停和终止
- 依靠别的线程来拯救自己(自己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 查看线程执行情况
浙公网安备 33010602011771号