<返回更多

为什么线程饥饿比线程死锁更加可怕?

2023-04-25  今日头条  你的老师父
加入收藏

JAVA线程死锁

Java线程死锁发生的常见行为是双方互相持有对方需要的锁:即两个或多个线程在等待彼此持有的锁,导致所有线程都无法继续执行下去。这种情况通常会产生一个循环等待的场景。

举例代码:

public class DeadlockExample {
    private static Object lockA = new Object();
    private static Object lockB = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lockA) {
                    System.out.println("Thread 1 acquired lock A");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (lockB) {
                        System.out.println("Thread 1 acquired lock B");
                    }
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lockB) {
                    System.out.println("Thread 2 acquired lock B");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (lockA) {
                        System.out.println("Thread 2 acquired lock A");
                    }
                }
            }
        });

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

为避免死锁,可以采取以下措施:

 

  1. 避免嵌套锁
  2. 使用同步块代替同步方法
  3. 按照固定顺序获取锁
  4. 可以使用Lock对象代替synchronized关键字
  5. 使用Semaphore避免死锁
  6. 使用ThreadLocal为每个线程提供独立的变量副本,避免多个线程之间共享变量导致的死锁问题
  7. 避免长时间持有锁,尽可能减少持有锁的时间
  8. 使用非阻塞算法或无锁算法

修改上述代码

使用ReentrantLock

使用ReentrantLock时,需要将所有涉及到共享资源的代码放在lock()和unlock()方法之间,才能保证线程安全。在之前的代码示例中,if代码块没有被包括在lock()和unlock()方法之间,因此不具备线程安全性。

 

以下是修改后的示例代码:

public class DeadlockExample {
    private static final ReentrantLock lockA = new ReentrantLock();
    private static final ReentrantLock lockB = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lockA.lock();
                    System.out.println("Thread 1 acquired lock A");
                    Thread.sleep(100);
                    if (lockB.tryLock(100, TimeUnit.MILLISECONDS)) {
                        System.out.println("Thread 1 acquired lock B");
                        Thread.sleep(100);
                    } else {
                        System.out.println("Thread 1 failed to acquire lock B, aborting");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    if (lockB.isHeldByCurrentThread()) {
                        lockB.unlock();
                        System.out.println("Thread 1 released lock B");
                    }
                    lockA.unlock();
                    System.out.println("Thread 1 released lock A");
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lockB.lock();
                    System.out.println("Thread 2 acquired lock B");
                    Thread.sleep(100);
                    if (lockA.tryLock(100, TimeUnit.MILLISECONDS)) {
                        System.out.println("Thread 2 acquired lock A");
                        Thread.sleep(100);
                    } else {
                        System.out.println("Thread 2 failed to acquire lock A, aborting");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    if (lockA.isHeldByCurrentThread()) {
                        lockA.unlock();
                        System.out.println("Thread 2 released lock A");
                    }
                    lockB.unlock();
                    System.out.println("Thread 2 released lock B");
                }
            }
        });

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

        thread1.join();
        thread2.join();
    }
}

以上代码中,在每个线程的run方法中,我们依次获取两把锁,并在获取到锁之后进行相应的操作。在释放锁时,需要先判断当前线程是否持有该锁,如果是,则释放该锁,并打印相应的信息。

这样修改后,就能保证线程安全和避免死锁的问题了。

使用Semaphore

以下是修改后的示例代码:

 

public class DeadlockExample {
    private static final Semaphore semaphoreA = new Semaphore(1);
    private static final Semaphore semaphoreB = new Semaphore(1);

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    semaphoreA.acquire();
                    System.out.println("Thread 1 acquired semaphore A");
                    Thread.sleep(100);

                    while (!semaphoreB.tryAcquire()) {
                        semaphoreA.release();
                        System.out.println("Thread 1 released semaphore A and waiting for semaphore B");
                        semaphoreA.acquire();
                        System.out.println("Thread 1 acquired semaphore A again");
                    }

                    System.out.println("Thread 1 acquired semaphore B");
                    Thread.sleep(100);

                    semaphoreB.release();
                    System.out.println("Thread 1 released semaphore B");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphoreA.release();
                    System.out.println("Thread 1 released semaphore A");
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    semaphoreB.acquire();
                    System.out.println("Thread 2 acquired semaphore B");
                    Thread.sleep(100);

                    while (!semaphoreA.tryAcquire()) {
                        semaphoreB.release();
                        System.out.println("Thread 2 released semaphore B and waiting for semaphore A");
                        semaphoreB.acquire();
                        System.out.println("Thread 2 acquired semaphore B again");
                    }

                    System.out.println("Thread 2 acquired semaphore A");
                    Thread.sleep(100);

                    semaphoreA.release();
                    System.out.println("Thread 2 released semaphore A");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphoreB.release();
                    System.out.println("Thread 2 released semaphore B");
                }
            }
        });

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

        thread1.join();
        thread2.join();
    }
}

以上代码中,如果全部使用acquire(),那么在当前线程获取到第一个资源后,如果无法获取第二个资源,就会释放第一个资源,并进入等待状态。然而,由于此时其他线程可能已经获取了第一个资源并在等待第二个资源,因此当前线程可能永远无法获取到第二个资源,从而导致死锁的发生。

 

为了避免这种情况,可以使用tryAcquire()方法来尝试获取第二个资源,如果无法获取,则先释放第一个资源再进行等待。

这样修改后,就能保证线程安全和避免死锁的问题了。

Java线程饥饿

Java中线程饥饿(Thread Starvation)是指某个或某些线程无法获得所需的资源,从而无法继续执行下去,导致程序出现假死等异常情况。

 

举个例子,如果一个高优先级的线程一直占用某个共享资源,并且低优先级的线程总是无法获取该资源,那么这些低优先级的线程就会一直处于等待状态,无法执行下去,从而导致线程饥饿的发生。

另外,线程饥饿也可能发生在多个线程竞争同一资源时,如果某些线程总是能够比其他线程更快地获取到该资源,那么其他线程就会一直处于等待状态,无法及时完成任务,从而导致线程饥饿的发生。

为避免线程饥饿的发生,可以采取以下措施:

  1. 合理设计线程优先级,尽量保证每个线程都能有机会获取到所需的资源。
  2. 使用公平锁来保证资源的公平分配,避免某个线程长期占用某个资源。
  3. 尽量减少线程等待的时间,例如使用超时机制、避免死锁等方式来尽早释放资源,让其他线程有机会获取到资源。
  4. 对于一些不必要占用大量资源的线程,可以将其设置为低优先级或者使用线程池等方式来限制其数量,从而避免线程饥饿的发生。

总之,在编写多线程程序时需要注意线程饥饿问题,并采取相应措施来保证程序的正常执行。

以下是一个简单的代码示例,模拟了线程饥饿的情况。

 

public class ThreadStarvationExample {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            final int index = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (lock) {
                        System.out.println("Thread " + index + " acquired lock");
                        try {
                            // 模拟某些线程需要占用锁较长时间
                            if (index == 2 || index == 3) {
                                Thread.sleep(5000);
                            } else {
                                Thread.sleep(1000);
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("Thread " + index + " released lock");
                    }
                }
            }).start();
        }
    }
}

以上代码中,我们创建了10个线程,并对它们共享的一个锁进行竞争。其中,第2个线程和第3个线程分别需要占用锁5秒钟和其他线程相比更久的时间。

如果运行以上代码,我们可能会发现第2个线程和第3个线程总是优先获得锁,而其他线程则会等待较长时间才能获取到锁,从而导致这些线程在整个程序执行期间都无法正常执行下去,出现线程饥饿的情况。

为避免线程饥饿的发生,我们可以对占用锁时间较长的线程做出调整,例如将它们设置为低优先级或者减少其持有锁的时间等措施。

另外,我们还可以使用公平锁来保证资源的公平分配,避免某个线程长期占用某个资源。以下是修改后的代码示例:

 

public class ThreadStarvationExample {
    private static final ReentrantLock lock = new ReentrantLock(true);

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            final int index = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (lock) {
                        System.out.println("Thread " + index + " acquired lock");
                        try {
                            if (index == 2 || index == 3) {
                                // 将占用锁时间较长的线程设置为低优先级
                                Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
                                Thread.sleep(5000);
                            } else {
                                Thread.sleep(1000);
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } finally {
                            // 减少持有锁的时间,增加其他线程获取锁的机会
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println("Thread " + index + " released lock");
                        }
                    }
                }
            }).start();
        }
    }
}
声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>