多线程04-公平锁-可重入锁&死锁-中断(如何优雅停止线程)

本文深入探讨了Java并发编程中的锁机制,包括公平锁与非公平锁的概念及区别,解释了为何默认使用非公平锁的原因。同时,介绍了可重入锁的特性,通过示例展示了死锁的现象及排查方法。此外,还讨论了线程中断的机制,强调了中断是协作而非强制停止,并给出了中断示例。
摘要由CSDN通过智能技术生成

1.公平&非公平

1.1卖票案例(非公平)

class Ticket
{
    private int number = 50;
    private Lock lock = new ReentrantLock(); //默认用的是非公平锁,若想分配的平均一点,=--》公平一点,就构造器参数改为true
    public void sale()
    {
        lock.lock();
        try
        {
            if(number > 0)
            {
                System.out.println(Thread.currentThread().getName()+"\t 卖出第: "+(number--)+"\t 还剩下: "+number);
            }
        }finally {
            lock.unlock();
        }
    }

    /*Object objectLock = new Object();

    public void sale()
    {
        synchronized (objectLock)
        {
            if(number > 0)
            {
                System.out.println(Thread.currentThread().getName()+"\t 卖出第: "+(number--)+"\t 还剩下: "+number);
            }
        }
    }*/
}

/**
 * @auther zzyy
 * @create 2020-07-09 17:48
 */
public class SaleTicketDemo
{
    public static void main(String[] args)
    {
        Ticket ticket = new Ticket();

        new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"a").start();
        new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"b").start();
        new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"c").start();
        new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"d").start();
        new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"e").start();
    }
}

结果:

很容易出现一条线程抢占全部资源,分配不均

2.1三问:

1.1为何会有公平/非公平设计?
恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU 的时间片,尽量减少 CPU 空闲状态时间。
1.2为何默认非公平:
使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,因为不需要考虑是否还有前驱节点,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。

2.公平锁问题:
公平锁保证了排队的公平性,非公平锁霸气的忽视这个规则,所以就有可能导致排队的长时间在排队,也没有机会获取到锁,
这就是传说中的 “锁饥饿

3.何时用公平何时非公平:
吞吐量就非公平,否则就公平锁。

2.可重入锁:

synchronized和ReenctrantLock都是可重入
概念:
一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入。自己可以获取自己的内部锁。

在一个synchronized修饰的方法或代码块的内部调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的。

public class ReEntryLockDemo
{
    static Lock lock = new ReentrantLock();

    public static void main(String[] args)
    {
        new Thread(() -> {
            lock.lock();
            try
            {
                System.out.println("----外层调用lock");
                lock.lock();
                try
                {
                    System.out.println("----内层调用lock");
                }finally {
                    // 这里故意注释,实现加锁次数和释放次数不一样
                    // 由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待。
                    lock.unlock(); // 正常情况,加锁几次就要解锁几次
                }
            }finally {
                lock.unlock();
            }
        },"a").start();

        new Thread(() -> {
            lock.lock();
            try
            {
                System.out.println("b thread----外层调用lock");
            }finally {
                lock.unlock();
            }
        },"b").start();

    }
}

ReenctrantLock加锁几次就一定要解锁几次,否则当前线程用完了没释放干净,下边的线程就阻塞了。

★2.1死锁:

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


    public static void main(String[] args)
    {

        Thread a = new Thread(() -> {
            synchronized (lockA) {
                System.out.println(Thread.currentThread().getName() + "\t" + " 自己持有A锁,期待获得B锁");

                try {TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

                synchronized (lockB) {
                    System.out.println(Thread.currentThread().getName() + "\t 获得B锁成功");
                }
            }
        }, "a");
        a.start();

        new Thread(() -> {
            synchronized (lockB)
            {
                System.out.println(Thread.currentThread().getName()+"\t"+" 自己持有B锁,期待获得A锁");

                try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

                synchronized (lockA)
                {
                    System.out.println(Thread.currentThread().getName()+"\t 获得A锁成功");
                }
            }
        },"b").start();


    }
}

排查证明是死锁:
jps -l → jstack 类名
或者jconsole排查

3.中断

1.首先
一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止
所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。

其次
在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。
因此,Java提供了一种用于停止线程的机制——中断

中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现
若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true;
接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断,
此时究竟该做什么需要你自己写代码实现。

每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;
通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。

★3.1 中断示例1:volatile

 public static void main(String[] args) {
        new Thread(()->{

            while (true){
                if (isStop){
                    System.out.println("========isStop=true,程序结束");
                    break;
                }
                System.out.println("-----hello,");
            }

        },"t1").start();

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(()->{
                isStop=true;
                },"t2").start();
    }
}

结果:

-----hello,
-----hello,
-----hello,
-----hello,
-----hello,
========isStop=true,程序结束

★3.2优雅停止线程:中断示例2–原子类

static AtomicBoolean atomicBoolean=new AtomicBoolean(false);

    public static void main(String[] args) {
        new Thread(()->{

            while (true){
                if (atomicBoolean.get()){
                    System.out.println("========atomicBoolean=true,程序结束");
                    break;
                }
                System.out.println("-----hello,atomicBoolean");
            }

        },"t1").start();

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(()->{
            atomicBoolean.set(true);
        },"t2").start();
    }

结果:

-----hello,atomicBoolean
-----hello,atomicBoolean
-----hello,atomicBoolean
-----hello,atomicBoolean
-----hello,atomicBoolean
========atomicBoolean=true,程序结束

★3.3Thread自带的API实现

public static void main(String[] args) {
        Thread t1 = new Thread(() -> {

            while (true) {
            //isInterrupted自查中断标志位是否为true
                if (Thread.currentThread().isInterrupted()) { 
                    System.out.println("========isInterrupted,程序结束");
                    break;
                }
                System.out.println("-----hello,isInterrupted");
            }

        }, "t1");
        t1.start();

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(()->{
            t1.interrupt();
        },"t2").start();
    }

3.4当前线程的中断标识为true时,并不是立刻停止!

当对一个线程,调用 interrupt() 时:
① 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。
被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt() 并不能真正的中断线程,★★★需要被调用的线程自己进行配合才行

② 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,
那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。

3.4.1 中断为true后,并不是立刻stop程序案例(被调用的线程不配合):

main线程通知t1线程中断,但是此案例的t1线程并未主动响应main线程的终端通知。t1执行完了线程死了才结束。

public static void main(String[] args) {

        //  中断为true后,并不是立刻停止程序.因为此线程没配合isInterrupt()
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 300; i++) {
                System.out.println(("------i: " + i));
            }
            System.out.println("t1.interrupt调用之后02: " + Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();

        System.out.println("t1.interrupt()调用之前t1线程中断标志默认值: "+t1.isInterrupted());
        try { TimeUnit.MILLISECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        //  仅仅设置中断标志位为true,并未真正停止线程
        t1.interrupt();
        // 活动状态,t1线程还在执行
        System.out.println("-----t1.interrupt()调用之后01中断标志默认值:  "+t1.isInterrupted());
        try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
        //  非活动状态,t1线程已经结束执行
        System.out.println("------t1.interrupt()调用之后03中断标志默认值:  "+t1.isInterrupted());
    }

结果:建议自己执行一遍观察

★★3.5修改了中断标志,t1线程也主动响应,但t1程序依旧未停止

public static void main(String[] args) {
        Thread t1 = new Thread(() -> {

            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("========isInterrupted,程序结束");
                    break;
                }
//t2已经把标志位设为true,同时t1也主动响应,但是t1线程sleeptrue打断后,标志位又变为false,所以即使抛异常也继续运行
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
//  ★★★线程的中断标志位为false,无法停止,需要再次调用interrupt(),将标志位设为true!!
                    Thread.currentThread().interrupt();
                    e.printStackTrace();
                }

                System.out.println("-----hello,isInterrupted");
            }

        }, "t1");
        t1.start();

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(()->{
            t1.interrupt();
        },"t2").start();
    }

结论:
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值