搜索
写经验 领红包
 > 财经

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

导语:

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(&34;);                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    synchronized (lockB) {                        System.out.println(&34;);                    }                }            }        });        Thread thread2 = new Thread(new Runnable() {            @Override            public void run() {                synchronized (lockB) {                    System.out.println(&34;);                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    synchronized (lockA) {                        System.out.println(&34;);                    }                }            }        });        thread1.start();        thread2.start();    }}

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

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

使用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(&34;);                    Thread.sleep(100);                    if (lockB.tryLock(100, TimeUnit.MILLISECONDS)) {                        System.out.println(&34;);                        Thread.sleep(100);                    } else {                        System.out.println(&34;);                    }                } catch (InterruptedException e) {                    e.printStackTrace();                } finally {                    if (lockB.isHeldByCurrentThread()) {                        lockB.unlock();                        System.out.println(&34;);                    }                    lockA.unlock();                    System.out.println(&34;);                }            }        });        Thread thread2 = new Thread(new Runnable() {            @Override            public void run() {                try {                    lockB.lock();                    System.out.println(&34;);                    Thread.sleep(100);                    if (lockA.tryLock(100, TimeUnit.MILLISECONDS)) {                        System.out.println(&34;);                        Thread.sleep(100);                    } else {                        System.out.println(&34;);                    }                } catch (InterruptedException e) {                    e.printStackTrace();                } finally {                    if (lockA.isHeldByCurrentThread()) {                        lockA.unlock();                        System.out.println(&34;);                    }                    lockB.unlock();                    System.out.println(&34;);                }            }        });        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(&34;);                    Thread.sleep(100);                    while (!semaphoreB.tryAcquire()) {                        semaphoreA.release();                        System.out.println(&34;);                        semaphoreA.acquire();                        System.out.println(&34;);                    }                    System.out.println(&34;);                    Thread.sleep(100);                    semaphoreB.release();                    System.out.println(&34;);                } catch (InterruptedException e) {                    e.printStackTrace();                } finally {                    semaphoreA.release();                    System.out.println(&34;);                }            }        });        Thread thread2 = new Thread(new Runnable() {            @Override            public void run() {                try {                    semaphoreB.acquire();                    System.out.println(&34;);                    Thread.sleep(100);                    while (!semaphoreA.tryAcquire()) {                        semaphoreB.release();                        System.out.println(&34;);                        semaphoreB.acquire();                        System.out.println(&34;);                    }                    System.out.println(&34;);                    Thread.sleep(100);                    semaphoreA.release();                    System.out.println(&34;);                } catch (InterruptedException e) {                    e.printStackTrace();                } finally {                    semaphoreB.release();                    System.out.println(&34;);                }            }        });        thread1.start();        thread2.start();        thread1.join();        thread2.join();    }}

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

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

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

Java线程饥饿

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

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

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

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

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

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

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

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(&34; + index + &34;);                        try {                            // 模拟某些线程需要占用锁较长时间                            if (index == 2 || index == 3) {                                Thread.sleep(5000);                            } else {                                Thread.sleep(1000);                            }                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                        System.out.println(&34; + index + &34;);                    }                }            }).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(&34; + index + &34;);                        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(&34; + index + &34;);                        }                    }                }            }).start();        }    }}