java多线程编程问题以及解决办法

java多线程编程问题以及解决办法

    多线程编程虽然可以提高程序的性能和响应速度,但也带来了许多复杂的问题,如竞态条件、死锁、线程安全问题、内存一致性错误等。常用的解决方法包括使用同步机制(如 synchronized 和 ReentrantLock)、线程池、volatile 关键字、以及合适的线程间通信机制(如 wait/notify 和 Condition)。

   下面多线程编程中常见的一些问题以及对应的解决办法

   一、竞态条件(Race Conditions)

   竞态条件是指多个线程同时访问和修改共享数据时,由于操作顺序的不确定性,可能导致数据的不一致或程序行为异常。竞态条件是多线程编程中最常见的问题之一。

   1. 示例

 1 public class Counter {
 2     private int count = 0;
 3 
 4     public void increment() {
 5         count++;
 6     }
 7 
 8     public int getCount() {
 9         return count;
10     }
11 
12     public static void main(String[] args) {
13         Counter counter = new Counter();
14         Runnable task = () -> {
15             for (int i = 0; i < 1000; i++) {
16                 counter.increment();
17             }
18         };
19         Thread thread1 = new Thread(task);
20         Thread thread2 = new Thread(task);
21         thread1.start();
22         thread2.start();
23 
24         try {
25             thread1.join();
26             thread2.join();
27         } catch (InterruptedException e) {
28             e.printStackTrace();
29         }
30 
31         // 预期值是2000,但是可能输出不正确的结果
32         System.out.println(counter.getCount());
33     }
34 }

   说明:上面的运行结果是不稳定的

   2. 解决办法

   使用同步机制,如 synchronized 关键字或显式锁(ReentrantLock)来确保对共享数据的访问是互斥的。

   1) synchronized

 1 public class Counter {
 2     private int count = 0;
 3 
 4     public synchronized void increment() {
 5         count++;
 6     }
 7 
 8     public synchronized int getCount() {
 9         return count;
10     }
11 }

   2)ReentrantLock  

 1 public class ReentrantLockCounter {
 2     public static ReentrantLock lock = new ReentrantLock();
 3     public static int count = 0;
 4 
 5     public void increment() {
 6         count++;
 7     }
 8 
 9     public int getCount() {
10         return count;
11     }
12     /**
13      * @param args
14      * @throws InterruptedException
15      */
16     public static void main(String[] args) throws InterruptedException {
17         ReentrantLockCounter counter = new ReentrantLockCounter();
18         Runnable task = () -> {
19             for (int i = 0; i < 1000; i++) {
20                 lock.lock();
21                 try {
22                     System.out.println(Thread.currentThread().getName()  + " " + i);
23                     counter.increment();
24                 } finally {
25                     lock.unlock();
26                 }
27             }
28         };
29         Thread thread1 = new Thread(task);
30         Thread thread2 = new Thread(task);
31         thread1.start();
32         thread2.start();
33 
34         try {
35             thread1.join();
36             thread2.join();
37         } catch (InterruptedException e) {
38             e.printStackTrace();
39         }
40         
41         System.out.println(counter.getCount());
42     }
43 }

   二、死锁(Deadlock)

   死锁是指两个或多个线程相互等待对方释放锁,从而导致线程永久阻塞。死锁通常发生在多个锁的情况下,当线程获取锁的顺序不一致时容易产生死锁。

   1. 示例

 1 public class DeadlockDemo {
 2     private static final Object lock1 = new Object();
 3     private static final Object lock2 = new Object();
 4 
 5     public static void main(String[] args) {
 6         Thread thread1 = new Thread(() -> {
 7             synchronized (lock1) {
 8                 System.out.println("Thread 1: Holding lock 1...");
 9                 try { Thread.sleep(10); } catch (InterruptedException e) {}
10                 synchronized (lock2) {
11                     System.out.println("Thread 1: Holding lock 1 & 2...");
12                 }
13             }
14         });
15 
16         Thread thread2 = new Thread(() -> {
17             synchronized (lock2) {
18                 System.out.println("Thread 2: Holding lock 2...");
19                 try { Thread.sleep(10); } catch (InterruptedException e) {}
20                 synchronized (lock1) {
21                     System.out.println("Thread 2: Holding lock 2 & 1...");
22                 }
23             }
24         });
25 
26         thread1.start();
27         thread2.start();
28     }
29 }

    2. 解决办法

    避免嵌套锁定,尽量减少锁的数量,或通过使用 tryLock 方法来避免死锁。

 1 public class AvoidDeadlockDemo {
 2     private static final Lock lock1 = new ReentrantLock();
 3     private static final Lock lock2 = new ReentrantLock();
 4 
 5     public static void main(String[] args) {
 6         Thread thread1 = new Thread(() -> {
 7             try {
 8                 if (lock1.tryLock() && lock2.tryLock()) {
 9                     System.out.println("Thread 1: Acquired locks...");
10                 }
11             } finally {
12                 lock1.unlock();
13                 lock2.unlock();
14             }
15         });
16 
17         Thread thread2 = new Thread(() -> {
18             try {
19                 if (lock2.tryLock() && lock1.tryLock()) {
20                     System.out.println("Thread 2: Acquired locks...");
21                 }
22             } finally {
23                 lock2.unlock();
24                 lock1.unlock();
25             }
26         });
27 
28         thread1.start();
29         thread2.start();
30     }
31 }

   三、线程安全问题

   多线程访问共享数据时,如果没有适当的同步机制,可能导致数据的不一致性和程序行为的不可预知性。这类问题统称为线程安全问题。

   使用同步机制确保线程安全,如 synchronized 关键字、显式锁(ReentrantLock)或使用线程安全的类(如 AtomicInteger)。

   还是以竞态条件中的累加计算例子来说明:

 1 public class SafeCounter {
 2     private AtomicInteger count = new AtomicInteger(0);
 3 
 4     public void increment() {
 5         count.incrementAndGet();
 6     }
 7 
 8     public int getCount() {
 9         return count.get();
10     }
11 }

   三、 上下文切换开销

   线程切换(Context Switching)是指CPU从一个线程切换到另一个线程的过程。频繁的线程切换会导致CPU时间片的浪费,降低程序性能。上下文切换的开销包括保存和恢复线程状态,以及处理操作系统调度器。

   示例:大量创建和销毁线程,或者频繁的线程切换,都会增加上下文切换的开销。

   代码如下:

1 public class ContextSwitchDemo {
2     public static void main(String[] args) {
3         for (int i = 0; i < 10000; i++) {
4             new Thread(() -> {
5                 System.out.println("Thread " + Thread.currentThread().getId());
6             }).start();
7         }
8     }
9 }

    说明:上面的代码会创建10000个线程

    解决方法: 使用线程池来重用线程,减少创建和销毁线程的开销。

    代码如下:

 1 public class ThreadPoolDemo {
 2     public static void main(String[] args) {
 3         ExecutorService executor = Executors.newFixedThreadPool(10);
 4         for (int i = 0; i < 10000; i++) {
 5             executor.submit(() -> {
 6                 System.out.println("Thread " + Thread.currentThread().getId());
 7             });
 8         }
 9         executor.shutdown();
10     }
11 }

    说明:这里面只会创建10线程来循环执行任务

    五、内存一致性错误

    内存一致性错误(Memory Consistency Errors)是指由于缺乏适当的同步机制,不同线程对共享变量的修改在内存中的可见性不一致,导致线程读取到过期或不正确的数据。

    1. 示例

 1 public class MemoryConsistencyDemo {
 2     private static boolean ready = false;
 3     private static int number;
 4 
 5     private static class ReaderThread extends Thread {
 6         public void run() {
 7             while (!ready) {
 8                 Thread.yield();
 9             }
10             System.out.println(number);
11         }
12     }
13 
14     public static void main(String[] args) {
15         new ReaderThread().start();
16         number = 42;
17         ready = true;
18     }
19 }

   2. 解决办法

   使用 volatile 关键字或同步机制来确保变量的可见性。

   将ready属性设置为:private static  volatile boolean ready = false;

 

    参考链接:

    https://www.aolifu.org/article/thread_question

posted @ 2024-06-22 14:07  欢乐豆123  阅读(5)  评论(0编辑  收藏  举报