
本文共 12217 字,大约阅读时间需要 40 分钟。
Java多线程编程技术入门指南
如今软件编程越来越复杂,尤其在线程编程方面。对于Java开发人员而言,掌握多线程编程的核心概念至关重要。本文将从线程的基础知识入手,逐步深入探讨线程的创建方式、线程间通信以及如何解决常见的同步问题。
线程的基础知识
在Java中,线程的创建有两种主要方式:一种是直接继承Thread
类并重写run()
方法,另一种是实现Runnable
接口并通过Thread
类来执行。两种方式的本质都是调用start()
方法来启动线程。
Thread和Runnable的区别
继承 Thread 类:
- ADVANTAGES:更简单直接,适合大多数场景。
- DISADVANTAGES:由于Java单继承机制,当类已经继承其他类时无法继承
Thread
。 - 示例:
class MyThread extends Thread { @Override public void run() { System.out.println("MyThread running in " + Thread.currentThread().getName()); }}Thread thread = new MyThread();thread.start();
实现 Runnable 接口:
- ADVANTAGES:支持多个线程创建,适用于类已继承其他类的情况。
- DISADVANTAGES:稍微复杂,需要手动管理线程。
- 示例:
Runnable target = new Runnable() { @Override public void run() { System.out.println("Runnable running in " + Thread.currentThread().getName()); }};Thread thread = new Thread(target);thread.start();
线程的同步问题
多线程编程的难点在于确保线程之间的通信和数据的正确同步。数据同步可以通过两种方式实现:摘要对象锁(synchronized关键字)和锁对象(Lock)。
使用 synchronized 关键字
synched
关键字可以修饰方法或代码块,确保在使用这些资源时只允许一个线程访问。
对静态方法的同步
public class MyClass { private static final Object鎢器 = new Object(); // 万能锁 private static int tickets = 1000; public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(new MyRunnable()).start(); } } private static class MyRunnable implements Runnable { @Override public synchronized void run() { System.out.println("Thread [" + Thread.currentThread().getId() + "] Selling ticket " + tickets); tickets--; System.out.println("Remaining tickets: " + tickets); } }}
注意:不要在匿名对象上使用同步,否则无法工作。
使用同步方法
public class MyClass { private static final Object鎢器 = new Object(); public synchronized static void print() { System.out.println("Print called by thread " + Thread.currentThread().getId()); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Print completed by thread " + Thread.currentThread().getId()); } public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(() -> print()).start(); } }}
如果方法是静态的,锁会默认使用
Class
对象作为锁对象;如果是非静态的,默认使用this
对象作为锁对象。
共享变量问题
在多线程环境下,使用共享变量需要加以同步。以下是一个典型的线程安全案例:
public class MyClass { private static Object lock = new Object(); private static int tickets = 1000; public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { try { while (true) { synchronized (lock) { System.out.println("Thread [" + Thread.currentThread().getId() + "] Selling ticket " + tickets); if (tickets <= 0) break; tickets--; System.out.println("Remaining tickets: " + tickets); Thread.sleep(100); } } } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }}
注意:在以上实例中,
tickets
变量上无需修饰为static
,否则所有线程共享同一副票据。
使用 ThreadLocal 机制
如果你想确保每个线程有自己的资源,可以用ThreadLocal
机制:
public class MyClass { private static final ThreadLocalthreadSafeCounter = new ThreadLocal<>(); public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(new Runnable() { @Override public void run() { try { threadSafeCounter.set(0); System.out.println("Thread [" + Thread.currentThread().getId() + "] Counter value: " + threadSafeCounter.get()); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }}
线程间通信
线程间通信是多线程编程中的一项核心任务。常用的方法包括:
使用 Object类方法
wait()
和notify()
方法可以让线程在特定条件下进入等待状态或唤醒状态。
单向通信示例
public class MyClass { private static final Object lock = new Object(); public static void main(String[] args) { final MyPrint print = new MyPrint(); new Thread(() -> { try { while (true) { synchronized (lock) { System.out.println("Printer A: Print A"); Thread.sleep(200); } } } catch (InterruptedException e) { e.printStackTrace(); } }).start(); new Thread(() -> { try { while (true) { synchronized (lock) { System.out.println("Printer B: Print B"); Thread.sleep(200); } } } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } private static class MyPrint {}}
在上述示例中,你需要确保A和B交替执行,避免同时打印产生混乱。
使用 Condition 组合
对于更复杂的通信需求,可以使用Condition
类配合ReentrantLock
:
import java.util.concurrent.ReentrantLock;import java.util.concurrentCondition;public class MyClass { private static final ReentrantLock lock = new ReentrantLock(); private static final Condition condition = lock.newCondition(); private static int state = 1; public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { try { while (true) { System.out.println("Thread " + Thread.currentThread().getId() + ": State is " + state); if (state != 1) { condition.await(); } state++; System.out.println("Thread " + Thread.currentThread().getId() + ": New state is " + state); Thread.sleep(100); } } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { while (true) { System.out.println("Thread " + Thread.currentThread().getId() + ": Checking state"); condition.await(); System.out.println("Thread " + Thread.currentThread().getId() + ": Got signal"); state++; System.out.println("Thread " + Thread.currentThread().getId() + ": New state is " + state); Thread.sleep(100); } } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { while (true) { System.out.println("Thread " + Thread.currentThread().getId() + ": Checking state"); condition.await(); System.out.println("Thread " + Thread.currentThread().getId() + ": Got signal"); state--; System.out.println("Thread " + Thread.currentThread().getId() + ": New state is " + state); Thread.sleep(100); } } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); }}
每次等待时检查条件,收到通知后才会继续执行。这种方式可以避免 busy waiting (忙等待) 问题。
ReentrantLock 的高级特性
import java.util.concurrent.ReentrantLock;public class MyClass { private static final ReentrantLock lock = new ReentrantLock(); private static final Condition condition = lock.newCondition(); private static int allowed = 1; public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { try { while (true) { allowed--; if (allowed < 0 || allowed <= 0) break; if (allowed != 1) { condition.await(); } allowed = 1; System.out.println("Step 1: allowed = " + allowed); Thread.sleep(100); } } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { while (true) { allowed--; if (allowed < 0 || allowed <= 0) break; if (allowed != 1) { condition.await(); } allowed = 1; System.out.println("Step 2: allowed = " + allowed); Thread.sleep(100); } } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); }}
在这种情况下,ReentrantLock 允许同一个线程多次占锁,最大重入次数由具体实现决定。
suspicious deadlock prevention
有时候两个或多个线程希望互相等待对方完成某些操作。这种情况需要谨慎处理。如果这种等待可能持续很长时间,导致资源耗尽,我们需要添加机制来打破可能的死锁状态。
示例:死锁检测和打破
public class DeadLock3 { private static Lock lock1 = new ReentrantLock(); private static Lock lock2 = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { startDeadlock(); System.out.println("Waiting for deadlock to be detected..."); TimeUnit.SECONDS.sleep(5); checkAndBreakDeadlock(); } private static void checkAndBreakDeadlock() { try { ThreadMXBean mbean = ManagementFactory.getThreadMXBean(); long[] deadlockedThreads = mbean.findDeadlockedThreads(); if (deadlockedThreads != null && deadlockedThreads.length > 0) { Thread thread = mbean.getThreadInfo(deadlockedThreads[0]); if (thread != null) { System.out.println("Found deadlocked thread: " + thread.getName()); thread.interrupt(); } } } catch (Exception e) { e.printStackTrace(); } } private static void startDeadlock() { new Thread(new Runnable() { @Override public void run() { try { lock1.lockInterruptibly(); TimeUnit.SECONDS.sleep(1); lock2.lockInterruptibly(); System.out.println("Thread 1 is deadlocked"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock2.unlock(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { lock2.lockInterruptibly(); TimeUnit.SECONDS.sleep(1); lock1.lockInterruptibly(); System.out.println("Thread 2 is deadlocked"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock1.unlock(); } } }).start(); }}
使用
ThreadMXBean
可以帮助检测死锁状态。
注意事项
- 死锁是指一个线程在等待其他线程完成某项任务时,其他线程也在等待它完成另一项任务。
- 使用
ReentrantLock.lockInterruptibly()
可以避免死锁,因为当线程被中断时会立即释放锁。
总结
多线程编程是一项复杂但极为重要的技能。掌握线程的创建方式、线程间通信机制和同步策略,是成为一名优秀开发人员的关键。在实际应用中,需要根据项目需求选择合适的线程模型,并注意处理线程安全和资源管理问题。
希望以上内容能够帮助你在Java多线程编程领域走得更远!如果对某些内容还有疑问,欢迎留言与我交流。
发表评论
最新留言
关于作者
