多线程教学文档
一、引言
欢迎来到本教学文档,本文将详细介绍多线程的概念、实现方式以及常用方法。作为一名从事自媒体行业多年的Java开发工程师,我将以CSDN博主的文章结构为蓝本,以幽默的风格为您呈现多线程编程的精华知识。
二、目录
-
引言
-
目录
-
什么是多线程?
-
多线程的实现方式
-
继承Thread类
-
实现Runnable接口
-
实现Callable接口
-
-
多线程方法
-
守护线程
-
线程强制插入JOIN()
-
线程优先级
-
线程休眠
-
线程的五种状态
-
线程停止
-
线程礼让Yield()
-
线程同步(synchronized和Lock)
-
线程不安全集合
-
死锁
-
线程通信
-
线程池
-
-
总结
三、什么是多线程?
在软件或硬件上实现多个线程并发执行的技术被称为多线程。这种技术能够在同一时间执行多于一个线程,从而提升整体处理性能。了解多线程前,我们需要明确两个术语:
-
进程: 执行程序中的一次执行过程,是系统资源分配的单位。
-
线程: 在系统中,一个程序有一个进程,一个进程中至少有一个线程。线程是CPU调度和执行的单位。
四、多线程的实现方式
4.1 继承Thread类
javaCopy codepublic class TestThread extends Thread { @Override public void run() { // 线程执行的方法体 for (int i = 0; i < 2000; i++) { System.out.println("我在刷抖音!" + i); } } public static void main(String[] args) { TestThread thread = new TestThread(); thread.start(); for (int i = 0; i < 2000; i++) { System.out.println("我在上厕所!" + i); } } }
4.2 实现Runnable接口
javaCopy codepublic class TestRunnable implements Runnable { @Override public void run() { // 线程执行的方法体 for (int i = 0; i < 2000; i++) { System.out.println("我在刷抖音!" + i); } } public static void main(String[] args) { Thread thread = new Thread(new TestRunnable()); thread.start(); for (int i = 0; i < 2000; i++) { System.out.println("我在上厕所!" + i); } } }
4.3 实现Callable接口
javaCopy codeimport java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class TestCallable implements Callable<Boolean> { String name = null; public TestCallable(String name) { this.name = name; } @Override public Boolean call() throws Exception { // 线程执行的方法体 for (int i = 1; i <= 20; i++) { System.out.println(name + i); } return true; } public static void main(String[] args) throws ExecutionException, InterruptedException { // 创建线程 TestCallable t1 = new TestCallable("睡觉!"); TestCallable t2 = new TestCallable("水饺"); // 创建执行服务 ExecutorService ser = Executors.newFixedThreadPool(2); // 提交执行 Future<Boolean> r1 = ser.submit(t1); Future<Boolean> r2 = ser.submit(t2); // 获取结果 Boolean res1 = r1.get(); Boolean res2 = r2.get(); System.out.println(res1); System.out.println(res2); // 关闭服务 ser.shutdown(); } }
案例:龟兔赛跑
javaCopy codepublic class Race implements Runnable { public static String winner; @Override public void run() { for (int i = 1; i <= 100; i++) { String name = Thread.currentThread().getName(); // 兔子线程睡眠,保证每次赢得都是乌龟 if (name.equals("兔子") && i % 10 == 0) { try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } Boolean over = this.isOver(i); if (over) { break; } System.out.println(name + "跑了第" + i + "步"); } } /** * 判断游戏是否结束 * * @param steps * @return */ public Boolean isOver(int steps) { if (winner != null) { return true; } else { if (steps >= 100) { winner = Thread.currentThread().getName(); System.out.println("winner is " + winner); return true; } } return false; } public static void main(String[] args) { Race race = new Race(); new Thread(race, "兔子").start(); new Thread(race, "乌龟").start(); } }
五、多线程方法
5.1 守护线程
守护线程在Java虚拟机中执行,为其他线程提供便利服务。守护线程的经典例子是垃圾回收器(GC)。
案例:妈妈监视我写作业
javaCopy codepublic class TestDaemon { public static void main(String[] args) { Mom mom = new Mom(); You you = new You(); Thread thread1 = new Thread(mom); thread1.setDaemon(true); // 将mom设置为守护线程 thread1.start(); new Thread(you).start(); } } class Mom implements Runnable { @Override public void run() { while (true) { System.out.println("妈妈在监视着你写作业!"); } } } class You implements Runnable { @Override public void run() { for (int i = 0; i < 365 * 100; i++) { System.out.println("好好写作业,离上大学不远了!"); } } }
5.2 线程强制插入JOIN()
案例:模拟售票窗口
javaCopy codepublic class TestJoin implements Runnable { private int ticketNums = 10; @Override public void run() { while (true) { if (ticketNums <= 0) { break; } // 模拟延时 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "票"); } } public static void main(String[] args) { TestJoin ticket = new TestJoin(); new Thread(ticket, "小明").start(); new Thread(ticket, "老师").start(); new Thread(ticket, "黄牛").start(); } }
5.3 线程优先级
javaCopy codepublic class TestPriority { public static void main(String[] args) { // 主线程默认优先级 System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority()); MyPriority myPriority = new MyPriority(); Thread t1 = new Thread(myPriority); Thread t2 = new Thread(myPriority); Thread t3 = new Thread(myPriority); Thread t4 = new Thread(myPriority); Thread t5 = new Thread(myPriority); // 先设置优先级再启动 t1.start(); t2.setPriority(1); t2.start(); t3.setPriority(4); t3.start(); t4.setPriority(Thread.MAX_PRIORITY); // MAX_PRIORITY = 10 t4.start(); t5.setPriority(Thread.MIN_PRIORITY); // MIN_PRIORITY = 1 t5.start(); } } class MyPriority implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority()); } }
5.4 线程休眠
javaCopy codepublic class TestSleep implements Runnable { private int ticketNums = 10; @Override public void run() { while (true) { if (ticketNums <= 0) { break; } // 模拟延时 try { Thread.sleep(200); // 暂停代码执行,让出cpu给其他线程 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "票"); } } public static void main(String[] args) { TestSleep ticket = new TestSleep(); new Thread(ticket, "小明").start(); new Thread(ticket, "老师").start(); new Thread(ticket, "黄牛").start(); } }
5.5 线程的五种状态
-
新建(New): 线程对象被创建后,即进入了新建状态。例如,Thread thread = new Thread()。
-
就绪(Runnable): 线程对象被创建后,其它线程调用了该对象的
start()
方法。线程此时进入就绪状态,但并不意味着立即进入运行状态,它需要等待CPU的调度。 -
运行(Running): 如果处于就绪状态的线程获取了CPU,此时线程就进入了运行状态。具体执行哪个线程由操作系统的调度算法来决定。
-
阻塞(Blocked):
处于运行状态的线程由于某些原因,暂时放弃对CPU的使用权,暂时停止运行。直到线程进入了就绪状态,才有机会再次获得CPU使用权。阻塞的情况分三种:
-
等待阻塞: 运行状态中的线程执行
wait()
方法,使线程进入等待阻塞状态。 -
同步阻塞: 线程在获取对象的同步锁时,若该同步锁已被其他线程占用,则该线程进入同步阻塞状态。
-
其他阻塞: 通过调用线程的
sleep()
或join()
发出了I/O请求时,线程会进入到阻塞状态。
-
-
死亡(Dead): 线程执行完了或者因异常退出了
run()
方法,该线程结束生命周期。
5.6 线程停止
在多线程编程中,线程的停止是一个涉及到安全性和可控性的重要问题。在Java中,通常推荐线程自己停止,而不是强制终止线程。以下是一种推荐的线程停止的方式:
javaCopy code/** * @Author WangYan * @Date 2022/11/9 15:05 * @Version 1.0 * 线程停止 * 1、推荐线程自己停止下来 * 2、建议使用一个标志符来进行终止变量。当flag = false ,则终止线程运行 */ public class TestStop implements Runnable{ private Boolean flag = true; @Override public void run() { int i = 0; while (flag){ System.out.println(Thread.currentThread().getName() + "run......." + i++); } } /** * 线程更改标志符 */ public void stop(){ this.flag = false; } public static void main(String[] args) { TestStop stop = new TestStop(); new Thread(stop).start(); for (int i = 0; i <= 1000000000; i++) { if (i == 999999){ // 更改线程标志符,使线程停止 stop.stop(); System.out.println("线程停止了"); } } } }
在这个例子中,我们创建了一个线程 TestStop
,其中包含一个循环,只有在 flag
为 true
的情况下才会继续执行。通过在主线程中改变 flag
的值,我们能够优雅地停止线程的执行。这种方式避免了强制终止线程,使得线程在执行完当前任务后能够自己优雅地停止。
7. 线程礼让 (Yield()
)
线程礼让是通过 Thread.yield()
方法实现的,它暗示调度器当前线程愿意让出其使用的处理器资源,以便其他线程有机会运行。调用 yield()
并不会释放锁或者资源,仅是一种提示。
7.1 示例代码
javaCopy code/** * @Author WangYan * @Date 2022/11/13 17:30 * @Version 1.0 * 线程礼让示例 */ public class ThreadYieldExample { public static void main(String[] args) { Thread producer = new Thread(() -> { for (int i = 1; i <= 5; i++) { System.out.println("Producer: Producing item " + i); Thread.yield(); // 线程礼让 } }); Thread consumer = new Thread(() -> { for (int i = 1; i <= 5; i++) { System.out.println("Consumer: Consuming item " + i); Thread.yield(); // 线程礼让 } }); producer.start(); consumer.start(); } }
在上述代码中,Producer
和 Consumer
线程在运行过程中调用 Thread.yield()
,表示它们愿意让出一些处理器资源,以便其他线程运行。实际效果可能因操作系统和具体调度器的不同而有所差异。
8. 线程同步 (synchronized
和 Lock
)
线程同步是多线程编程中的关键问题之一,主要涉及到多个线程访问共享资源时的数据一致性问题。Java 提供了 synchronized
关键字和 Lock
接口来实现线程同步。
8.1 使用 synchronized
实现线程同步
javaCopy code/** * @Author WangYan * @Date 2022/11/13 17:45 * @Version 1.0 * 使用 synchronized 实现线程同步示例 */ public class SynchronizedExample { private static int counter = 0; public static synchronized void increment() { counter++; } public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(() -> { for (int i = 0; i < 1000; i++) { increment(); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i < 1000; i++) { increment(); } }); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("Counter: " + counter); } }
在上述代码中,increment()
方法被声明为 synchronized
,保证了对 counter
的访问是原子的,从而避免了竞态条件。
8.2 使用 Lock
实现线程同步
javaCopy codeimport java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @Author WangYan * @Date 2022/11/13 18:00 * @Version 1.0 * 使用 Lock 实现线程同步示例 */ public class LockExample { private static int counter = 0; private static final Lock lock = new ReentrantLock(); public static void increment() { lock.lock(); try { counter++; } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(() -> { for (int i = 0; i < 1000; i++) { increment(); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i < 1000; i++) { increment(); } }); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("Counter: " + counter); } }
在上述代码中,通过 ReentrantLock
创建了一个锁,使用 lock()
和 unlock()
方法实现了对 counter
的线程安全访问。
9. 线程不安全集合
在多线程环境中,使用不安全的集合类可能导致数据不一致性和线程安全问题。Java 提供了一些线程安全的集合类,如 Vector
、Hashtable
、Collections.synchronizedXXX
等,用于在多线程环境中安全地操作集合。
9.1 示例代码
javaCopy codeimport java.util.ArrayList; import java.util.Collections; import java.util.List; /** * @Author WangYan * @Date 2022/11/13 18:15 * @Version 1.0 * 使用线程安全的集合示例 */ public class ThreadSafeCollectionExample { public static void main(String[] args) throws InterruptedException { // 使用 Collections.synchronizedList() 创建线程安全的 List List<String> threadSafeList = Collections.synchronizedList(new ArrayList<>()); Thread thread1 = new Thread(() -> { for (int i = 0; i < 1000; i++) { threadSafeList.add("Item " + i); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i < 1000; i++) { threadSafeList.add("Another Item " + i); } }); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("Thread-safe List size: " + threadSafeList.size()); } }
在上述代码中,通过 Collections.synchronizedList()
创建了一个线程安全的 List
,确保在多线程环境中对集合的操作是线程安全的。
10. 死锁
死锁是多线程编程中常见的问题之一,它发生在两个或多个线程互相等待对方释放锁资源的情况下。这样的情况将导致程序无法继续执行下去。
10.1 死锁的示例
javaCopy code/** * @Author WangYan * @Date 2022/11/13 18:30 * @Version 1.0 * 死锁示例 */ public class DeadlockExample { private static final Object LOCK1 = new Object(); private static final Object LOCK2 = new Object(); public static void main(String[] args) { Thread thread1 = new Thread(() -> { synchronized (LOCK1) { System.out.println("Thread 1: Holding LOCK1..."); try { Thread.sleep(10); // 为了让另一个线程有机会持有 LOCK2 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread 1: Waiting for LOCK2..."); synchronized (LOCK2) { System.out.println("Thread 1: Holding LOCK1 and LOCK2..."); } } }); Thread thread2 = new Thread(() -> { synchronized (LOCK2) { System.out.println("Thread 2: Holding LOCK2..."); try { Thread.sleep(10); // 为了让另一个线程有机会持有 LOCK1 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread 2: Waiting for LOCK1..."); synchronized (LOCK1) { System.out.println("Thread 2: Holding LOCK2 and LOCK1..."); } } }); thread1.start(); thread2.start(); } }
上述代码中,Thread 1
持有 LOCK1
并等待 LOCK2
,而 Thread 2
持有 LOCK2
并等待 LOCK1
,两个线程都在等待对方释放锁,从而形成死锁。
11. 线程通信
线程通信是多线程编程中一种重要的机制,允许线程之间进行协调和合作。常见的线程通信方式包括使用 wait()
、notify()
和 notifyAll()
方法。
11.1 示例代码
javaCopy code/** * @Author WangYan * @Date 2022/11/13 18:45 * @Version 1.0 * 线程通信示例 */ public class ThreadCommunicationExample { private static final Object LOCK = new Object(); private static boolean isDataReady = false; public static void main(String[] args) { Thread producer = new Thread(() -> { // 生产者线程 synchronized (LOCK) { System.out.println("Producer: Producing data..."); isDataReady = true; LOCK.notify(); // 通知等待中的消费者线程 } }); Thread consumer = new Thread(() -> { // 消费者线程 synchronized (LOCK) { while (!isDataReady) { try { LOCK.wait(); // 等待生产者线程通知 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Consumer: Consuming data..."); } }); consumer.start(); producer.start(); } }
在上述代码中,Producer
线程负责生产数据,而 Consumer
线程负责消费数据。通过 wait()
和 notify()
,实现了线程之间的协同工作。
12. 线程池
线程池是一种管理线程的机制,可以有效地复用线程、控制并发线程数,提高程序性能。Java 中通过 Executor
框架提供了线程池的支持。
12.1 示例代码
javaCopy codeimport java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @Author WangYan * @Date 2022/11/13 19:00 * @Version 1.0 * 线程池示例 */ public class ThreadPoolExample { public static void main(String[] args) { // 创建固定大小的线程池 ExecutorService executorService = Executors.newFixedThreadPool(3); // 提交任务给线程池 for (int i = 1; i <= 5; i++) { final int taskId = i; executorService.submit(() -> { System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName()); }); } // 关闭线程池 executorService.shutdown(); } }
在上述代码中,通过 Executors.newFixedThreadPool(3)
创建了一个固定大小为 3 的线程池,然后提交了5个任务给线程池执行。线程池会自动管理线程的生命周期,提高了线程的复用率和性能。