高薪程序员必修课-java并发编程的bug源头

前言

Java并发编程虽然强大,但也容易引发复杂的bug。并发编程的bug主要源自以下几个方面:竞态条件、死锁、内存可见性问题和线程饥饿。了解这些bug的源头及其原理,可以帮助开发者避免和解决这些问题。以下是详细的讲解和相应的示例。

1. 竞态条件(Race Condition)

原理

竞态条件发生在多个线程同时访问和修改共享资源时,由于操作的交错顺序不同,导致程序的行为和结果不可预测。具体表现为多个线程在没有适当同步的情况下访问和修改同一变量。

示例

下面是一个竞态条件的示例,演示多个线程同时修改共享变量 counter 的问题。

java 复制代码
public class RaceConditionExample {
    private static int counter = 0;

    public static void main(String[] args) {
        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                counter++;
            }
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final counter value: " + counter);  // 预期结果应为2000
    }
}

2. 死锁(Deadlock)

原理

死锁是指两个或多个线程相互等待对方持有的资源,导致所有线程都无法继续执行。死锁通常发生在多线程程序中使用多个锁时,锁获取的顺序不一致导致循环等待。

示例

下面是一个简单的死锁示例,两个线程尝试获取相同的锁,但顺序不同,导致死锁。

java 复制代码
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 lock 1...");

                try { Thread.sleep(100); } catch (InterruptedException e) {}
                System.out.println("Thread 1: Waiting for lock 2...");

                synchronized (lock2) {
                    System.out.println("Thread 1: Holding lock 1 & 2...");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread 2: Holding lock 2...");

                try { Thread.sleep(100); } catch (InterruptedException e) {}
                System.out.println("Thread 2: Waiting for lock 1...");

                synchronized (lock1) {
                    System.out.println("Thread 2: Holding lock 1 & 2...");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

3. 内存可见性问题(Memory Visibility Issues)

原理

内存可见性问题指的是一个线程对共享变量的修改,另一个线程可能看不到。Java内存模型(JMM)允许线程将变量缓存到寄存器或CPU缓存中,而不是立即写入主内存。这会导致不同线程看到的变量值不一致。

示例

下面是一个内存可见性问题的示例,展示了一个线程对变量 running 的修改,另一个线程可能看不到。

java 复制代码
public class MemoryVisibilityExample {
    private static boolean running = true;

    public static void main(String[] args) {
        Thread worker = new Thread(() -> {
            while (running) {
                // Busy-wait loop
            }
            System.out.println("Worker thread stopped.");
        });

        worker.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        running = false;
        System.out.println("Main thread set running to false.");
    }
}

4. 线程饥饿(Thread Starvation)

原理

线程饥饿发生在某些线程长期得不到执行机会,通常是因为高优先级线程不断占用CPU时间,低优先级线程无法获取CPU资源。导致某些线程长期处于等待状态。

示例

下面是一个线程饥饿的示例,展示了低优先级线程可能永远得不到执行机会。

java 复制代码
public class ThreadStarvationExample {
    public static void main(String[] args) {
        Thread highPriorityThread = new Thread(() -> {
            while (true) {
                // High priority task
            }
        });
        highPriorityThread.setPriority(Thread.MAX_PRIORITY);

        Thread lowPriorityThread = new Thread(() -> {
            while (true) {
                System.out.println("Low priority thread running...");
            }
        });
        lowPriorityThread.setPriority(Thread.MIN_PRIORITY);

        highPriorityThread.start();
        lowPriorityThread.start();
    }
}

总结

竞态条件
  • 原理:多个线程同时访问和修改共享资源。
  • 示例:多个线程同时增加共享变量。
死锁
  • 原理:两个或多个线程相互等待对方持有的资源。
  • 示例:线程1持有锁1等待锁2,线程2持有锁2等待锁1。
内存可见性问题
  • 原理:一个线程对共享变量的修改,另一个线程可能看不到。
  • 示例 :一个线程修改变量 running,另一个线程看不到变化。
线程饥饿
  • 原理:某些线程长期得不到执行机会。
  • 示例:高优先级线程不断占用CPU时间,低优先级线程无法获取CPU资源。

理解并发编程中的这些bug源头和原理,并采用适当的同步机制(如 synchronizedLockvolatile)以及并发工具(如 CountDownLatchSemaphoreConcurrentHashMap),可以有效避免和解决这些问题。

相关推荐
秋雨雁南飞1 分钟前
C# 字符串占位
开发语言·c#
拾贰_C1 分钟前
[Python | pytorch | torchvision ] models like ResNet... 命名变量说明
开发语言·pytorch·python
傅里叶的耶1 分钟前
C++ Primer Plus(第6版):第三章 处理数据
开发语言·c++
3 分钟前
TIDB——PD(placement Driver)
java·数据库·分布式·tidb·
TG:@yunlaoda360 云老大4 分钟前
配置华为云国际站代理商OBS跨区域复制时,如何编辑委托信任策略?
java·前端·华为云
计算机毕设指导66 分钟前
基于微信小程序的鸟博士系统【源码文末联系】
java·spring boot·mysql·微信小程序·小程序·tomcat·maven
C雨后彩虹13 分钟前
斗地主之顺子
java·数据结构·算法·华为·面试
CC.GG21 分钟前
【C++】AVL树
java·开发语言·c++
闲人编程21 分钟前
基础设施即代码(IaC)工具比较:Pulumi vs Terraform
java·数据库·terraform·iac·codecapsule·pulumi
QQ_216962909628 分钟前
Spring Boot大学生社团管理平台 【部署教程+可完整运行源码+数据库】
java·数据库·spring boot·微信小程序