synchronized缺陷及优化解决(自定义显示锁)

先回顾一下synchronized缺陷:1、无法控制阻塞时长   2、阻塞不可中断,验证代码如下

public class SynchronizedDefect {

    public  void method(){
        synchronized(this){
            try {
                System.out.println(Thread.currentThread().getName() + "进来了");
                TimeUnit.SECONDS.sleep(10);
                System.out.println(Thread.currentThread().getName() + "结束了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    public static void main(String[] args) throws InterruptedException{

        SynchronizedDefect defect = new SynchronizedDefect();
        Thread t1 = new Thread(defect::method,"线程一");
        t1.start();
        TimeUnit.SECONDS.sleep(1);
        Thread t2 = new Thread(defect::method,"线程二");
        t2.start();
        Thread t3 = new Thread(defect::method,"线程三");
        t3.start();
        System.out.println(t1.getState());//TIMED_WAITING
        System.out.println(t2.getState());//BLOCKED
        System.out.println(t3.getState());//BLOCKED
        t2.interrupt();// interupt相当于提前中断了t2的阻塞,会修改interupt标识,所以一进入该休眠方法立即进入异常
    }
//简单分析:启动三个线程分别调用同步方法,同步方法,首先线程一启动,进入睡眠,另外两个线程进入阻塞状态,之后可能线程二可能线程三抢到锁,假设线程二抢到则直接报错(因为你修改了interrupt标识),
//总结:假设有一个线程进入了同步方法,另一个线程在外等候,你试图利用t2.interropt修改打断标识,让其捕获以此打断阻塞的线程二,发现你并不能打断阻塞线程,只能干巴巴的等到他抢到锁。

}

接下来来实现具备可中断并且可控制阻塞时长的lock接口锁的实现

第一步定义接口Lock:

public interface Lock {
    void lock() throws InterruptedException;//可被中断的方法
    void lock(long timeout) throws  InterruptedException, TimeoutException;//可被中断,并且增加了超时的功能
    void unlock();//解锁
    List<Thread> getBlockThreads();//用来获取当前阻塞线程
}

第二步实现接口:

public class BoolLock implements Lock{

    private Thread currentThread;//当前拥有锁的线程
    private Boolean locked = false;
    private final List<Thread> lockList = new ArrayList<>();//存储阻塞线程

    @Override
    public void lock() throws InterruptedException {
        synchronized (this){
            //其它线程能进入此处,因为这不是关联对象调用此方法,而是另一个测试类的方法引用此方法
            while (locked){
                lockList.add(Thread.currentThread());//孤儿线程  阻塞队列线程
                this.wait();//该线程放弃关联引用对象的monitor的所有权,并释放monitor的所有权给其它线程争抢的机会
            }
            System.out.println("当前线程获得锁" + Thread.currentThread().getName());
            lockList.remove(Thread.currentThread());//如果抢到锁了,则从阻塞队列中删除自己(如果没有返回false)
            locked = true;
            this.currentThread = Thread.currentThread();//拿到锁的线程
        }
    }

    @Override
    public void lock(long timeout) throws InterruptedException, TimeoutException {
        synchronized (this){
            if(timeout <= 0){
                try {
                    throw new TimeException();
                } catch (TimeException e) {
                    e.printStackTrace();
                }
            }else {
                long remainMills = timeout;
                long endMills = System.currentTimeMillis() + remainMills;
                while (locked){
                    if (remainMills <= 0){
                        throw  new TimeoutException("当前线程被唤醒或者指定时间wait到了还没获得锁");
                    }
                    if(!lockList.contains(Thread.currentThread())){
                        lockList.add(Thread.currentThread());
                    }
                    this.wait(remainMills);
                    remainMills = endMills - System.currentTimeMillis();//剩余时间
                }
                System.out.println("当前获得锁的线程:"+Thread.currentThread().getName());
                lockList.remove(Thread.currentThread());
                this.locked = true;
                this.currentThread = Thread.currentThread();
            }
        }
    }

    @Override
    public void unlock() {
        synchronized (this){
            //获得锁的线程解锁
            if (currentThread == Thread.currentThread()){
                this.locked = false;
                System.out.println("当前线程解锁" + Thread.currentThread().getName());
                this.notify();//唤醒睡眠线程强锁   这里唤醒一个也可以
            }
        }
    }
    @Override
    public List<Thread> getBlockThreads() {
        return Collections.unmodifiableList(lockList);
    }
}

涉及到的自定义异常TimeException类:

public class TimeException extends  Exception{
    @Override
    public void printStackTrace() {
        System.out.println("您输入的时间小于0");
    }
}

第三步测试类调用:

public class Test {
    private final  Lock lock = new BoolLock();

    public void test(){
        try {
            lock.lock();//这里是Test类调用同步方法
            TimeUnit.SECONDS.sleep(5);//谁调用休眠的方法,谁将睡眠5s,这里指获得锁的线程,因为孤儿线程还是waitting状态,不会往下走
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(lock.getBlockThreads());//在解锁前,输出阻塞队列
            lock.unlock();
        }
    }

    public static void main(String[] args) throws  InterruptedException{
        Test test = new Test();
        //启动五个线程,分别去调用另一个类BolckLock的同步方法,观察线程输出打印信息,这一点与syncronized同步非常类似,接下来验证可中断的特性
        IntStream.range(0,5).mapToObj(value -> new Thread(test::test)).forEach(Thread::start);
        
    }

}

这里为止就已经可以发现已经具备了syncronized关键字类似的功能,线程同步并且一次获取锁和解锁,接下来修改一下main方法,继续观察,实现可中中断,继而避免了syncronized不能打断阻塞线程

 public static void main(String[] args) throws  InterruptedException{
        Test test = new Test();
        Thread t1 = new Thread(test::test,"T1线程");
        t1.start();
        TimeUnit.MILLISECONDS.sleep(100);//睡眠10毫秒确保T2后启动
        Thread t2 =  new Thread(test::test,"T2线程");
        t2.start();
        System.out.println("T1状态" + t1.getState() + "T2状态" + t2.getState() );
        t2.interrupt();//wait和sleep都能接受到中断异常
    }
-----------------------输出信息-----------------------
当前线程获得锁T1线程
T1状态TIMED_WAITING,T2状态RUNNABLE
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at Lock.BoolLock.lock(BoolLock.java:20)
	at Lock.Test.test(Test.java:13)
	at java.lang.Thread.run(Thread.java:748)
[Thread[T2线程,5,main]]//T2线程直接进入finally因为不是解锁线程,所以只输出了一句,T2生命周期结束
[Thread[T2线程,5,]]//T1是解锁线程既输出了阻塞队列,又输出了解锁方法的打印信息
当前线程解锁T1线程
-----------------------修改后输出信息-----------------------
前线程获得锁T1线程
T1状态TIMED_WAITING,T2状态WAITING
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at Lock.BoolLock.lock(BoolLock.java:21)
	at Lock.Test.test(Test.java:13)
	at java.lang.Thread.run(Thread.java:748)
[Thread[T2线程,5,main]]
当前线程解锁T1线程
当前线程获得锁T2线程  //因为catch获取了异常,即这里可以抓住异常,依然会执行内部的循环 
[]
当前线程解锁T2线程

但是此时还存在一个问题,就是阻断的线程还在阻塞的队列中,没有清除,需要将lock()覆写的代码稍加修改,在阻塞线程中加入一个异常捕获即可

 @Override
    public void lock() throws InterruptedException {
        synchronized (this){
            //其它线程能进入此处,因为这不是关联对象调用此方法,而是另一个测试类的方法引用此方法
            while (locked){
               try {
                    lockList.add(Thread.currentThread());//孤儿线程  阻塞队列线程
                    this.wait();//该线程放弃关联引用对象的monitor的所有权,并释放monitor的所有权给其它线程争抢的机会
                } catch (InterruptedException e) {
                    lockList.remove(Thread.currentThread());//如果当前阻塞线程被打断,则移除避免内存泄漏
                    e.printStackTrace();
                }
            }
            System.out.println("当前线程获得锁" + Thread.currentThread().getName());
            lockList.remove(Thread.currentThread());//如果抢到锁了,则从阻塞队列中删除自己(如果没有返回false)
            locked = true;
            this.currentThread = Thread.currentThread();//拿到锁的线程
        }
    }

上面已经讲解过了提前中断阻塞线程的方法,现在说一下可超时的情况:假设某一个线程抢到锁需要5s睡眠,而其它线程在外面等待,假设我设定其其它线程的等待时间,每个人给它1s,一旦超过1s,则集体罢工。

正常情况:等待时间 >= 5s,则我这里不会进入超时,因为你等待的时间一直被重置,但是重置期间另外一个线程已经走完了,你还在等待中,允许你有时间去拿锁。

等待时间<5s,则会进入超时异常,直接结束掉程序,目的是为了减少过长时间的滞留。修改了一下lock(long timeout)方法

BlockLock类可超时的方法,不必等拿到锁的线程过长时间
 @Override
    public void lock(long timeout) throws InterruptedException, TimeoutException {
        synchronized (this){
            if(timeout <= 0){
                try {
                    throw new TimeException();
                } catch (TimeException e) {
                    e.printStackTrace();
                }
            }else {
                long remainMills = timeout;
                long endMills = System.currentTimeMillis() + remainMills;
                while (locked){
                    if (remainMills <= 0){
                        lockList.remove(Thread.currentThread());
                        throw  new TimeoutException("当前线程被唤醒或者指定时间wait到了还没获得锁");//抛出异常:下面代码不执行
                    }
                    if(!lockList.contains(Thread.currentThread())){//时间复杂度o(n)
                        lockList.add(Thread.currentThread());
                    }
                    this.wait(remainMills);
                    remainMills = endMills - System.currentTimeMillis();//剩余时间
                }
                System.out.println("当前获得锁的线程:" +Thread.currentThread().getName());
                lockList.remove(Thread.currentThread());
                this.locked = true;
                this.currentThread = Thread.currentThread();
            }
        }
    }

//Test类的方法
 public void test(){
        try {
            lock.lock(1000);//这里是Test类调用同步方法
            TimeUnit.SECONDS.sleep(5);//谁调用休眠的方法,谁将睡眠5s,这里指获得锁的线程,因为孤儿线程还是waitting状态,不会往下走
        } catch (Exception e) {
                e.printStackTrace();
        } finally {
            System.out.println(lock.getBlockThreads());//在解锁前,输出阻塞队列
            lock.unlock();
        }
    }

    public static void main(String[] args) throws  InterruptedException,TimeoutException{
        Test test = new Test();
        //启动五个线程,分别去调用另一个类BolckLock的同步方法,观察线程输出打印信息,这一点与syncronized同步非常类似,接下来验证可中断的特性
        //IntStream.range(0,5).mapToObj(value -> new Thread(test::test)).forEach(Thread::start);

        Thread t1 = new Thread(test::test,"T1线程");
        t1.start();
        TimeUnit.MILLISECONDS.sleep(100);//睡眠10毫秒确保T2后启动
        Thread t2 =  new Thread(test::test,"T2线程");
        t2.start();
        System.out.println("T1状态" + t1.getState() + ",T2状态" + t2.getState());
        //t2.interrupt();//wait和sleep能接受中断异常
    }
--------------控制台信息-------
当前获得锁的线程:T1线程
T1状态TIMED_WAITING,T2状态RUNNABLE
java.util.concurrent.TimeoutException: 当前线程被唤醒或者指定时间wait到了还没获得锁
	at Lock.BoolLock.lock(BoolLock.java:49)
	at Lock.Test.test(Test.java:15)
	at java.lang.Thread.run(Thread.java:748)
[] //T2线finally打印的,因为不是锁线程就没打印解锁
[]//T1线程打印
当前线程解锁T1线程

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值