线程与线程池笔记(一)线程、同步锁、线程间通信简单使用
发布日期:2021-05-10 06:28:57 浏览次数:25 分类:精选文章

本文共 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 ThreadLocal
    threadSafeCounter = 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多线程编程领域走得更远!如果对某些内容还有疑问,欢迎留言与我交流。

    上一篇:线程与线程池笔记(二)Runnable,Callable,Future和FutureTask
    下一篇:AsyncTask

    发表评论

    最新留言

    关注你微信了!
    [***.104.42.241]2025年05月08日 02时31分24秒