Java高并发系列(读书笔记)——关键字synchronized的功能扩展:重入锁

重入锁可以完全替代关键字synchronized。在JDK5.0的早期版本中,重入锁的性能远远优于关键字synchronized,但从JDK6.0开始,JDK在关键字synchronized上做了大量优化,使得两者的性能差距并不大

重入锁的几个重要方法如下:

  • lock( ):获得锁,如果锁已经被占用,则等待
  • lockInterruptibly( ):获取锁,但优先相应中断
  • tryLock( ):尝试获得锁,如果成功,则返回true,失败返回false。该方法不等待,立即返回
  • unlock( ):释放锁

重入锁使用java.util.concurrent.locks.ReentrantLock类来实现。下面是一段使用简单锁实现多线程安全的代码

public class ReenterLock implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;

    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            //使用重入锁保护临界区资源i,确保多线程对i操作的安全性
            lock.lock();//加锁
            try {
                i++;
            } finally {
                //在退出临界区时,必须记得释放锁。
                //否则其他线程就没有机会再访问临界区
                lock.unlock();//释放锁
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ReenterLock t1 = new ReenterLock();//线程实例
        Thread th1 = new Thread(t1);
        Thread th2 = new Thread(t1);
        th1.start();
        th2.start();
        th1.join();
        th2.join();
        System.out.println("i = " + i);
    }
}

重入锁可以提供中断处理的能力

与synchronized相比,如果一个线程在等待锁,那么结果只要两种情况,1 获得这把锁执行, 2 他保持等待状态。
而使用重入锁,则提供了另外一种可能性,那就是线程可以被中断,也就是在等待过程中,程序可以根据需要取消对锁的请求。

下面代码就产生了一个死锁,但得益于锁中断,我们可以很轻易地解决这个死锁

public class IntLock implements Runnable {
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    int lock;

    /**
     * 控制加锁顺序,方便构造死锁
     * @param lock
     */
    public IntLock(int lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            if (lock == 1) {
                //对锁lock1先申请获取请求,这是一个可以对中断进行想要的锁申请动作!
                lock1.lockInterruptibly();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //线程休眠500毫秒后对再对锁lock2申请获取请求
                lock2.lockInterruptibly();
            } else {
                //对锁lock2先申请获取请求,这是一个可以对中断进行想要的锁申请动作!
                lock2.lockInterruptibly();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock1.lockInterruptibly();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock1.isHeldByCurrentThread()) {
                //判断持有自己锁的线程是否是当前线程
                lock1.unlock();
            }
            if (lock2.isHeldByCurrentThread()) {
                lock2.unlock();
            }
            System.out.println(Thread.currentThread().getId() + ":线程退出");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //线程实例 1
        IntLock r1 = new IntLock(1);
        //线程实例 2
        IntLock r2 = new IntLock(2);
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        //开启t1线程后由于lock=1因此先获取lock1锁,并且线程睡眠500ms
        t1.start();
        //开启t2线程后由于lock=2因此先获取lock2锁
        t2.start();
        //由于上述中t1线程占用了lock1,t2线程占用了lock2
        //导致接下来t1线程无法获取lock2,而t2线程无法获取lock1
        //使主线程处于休眠状态,且此时t1,t2线程处于死锁状态
        Thread.sleep(1000);
        //此时中断t2线程,t2则会放弃对lock1的申请,并且是否已经获得的lock2
        //这样t1线程就可以顺利得到lock2而继续执行下去
        t2.interrupt();
    }
}

控制台输出结果

12:线程退出
13:线程退出
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at com.sakura.JDKPACKET.IntLock.run(IntLock.java:39)
	at java.lang.Thread.run(Thread.java:748)

可以看出,中断后t1,t2线程双双退出,但真正完成工作的只有t1,而t2线程则放弃任务直接退出,释放资源。


重入锁可以申请限时等待

下面这个代码中展示了限时等待锁的使用

public class TimeLock implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    
    @Override
    public void run() {
        try {
            if (lock.tryLock(5, TimeUnit.SECONDS)) {
                //试图获取锁,等待5秒 如果超时那就false
                Thread.sleep(6000);
            } else {
                System.out.println("get lock failed");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        TimeLock r1 = new TimeLock();
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r1);
        //t1获取lock并持有长达6秒
        t1.start();
        //此时t2再获取lock锁但由于t2只等待5秒因此请求锁会失败
        t2.start();
    }
}

下面该代码中如果锁被其他线程占用,则当前线程不会进行等待,而是立即返回false。

public class TryLock implements Runnable {
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    int lock;

    public TryLock(int lock) {
        this.lock = lock;
    }
    
    @Override
    public void run() {
        if (lock == 1) {
            while (true) {
                if (lock1.tryLock()) {
                    try {
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        if (lock2.tryLock()) {
                            try {
                                System.out.println(Thread.currentThread().getId() + ":My Job done");
                                return;
                            } finally {
                                lock2.unlock();
                            }
                        }
                    } finally {
                        lock1.unlock();
                    }
                }
            }
        } else {
            while (true) {
                if (lock2.tryLock()) {
                    try {
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        if (lock1.tryLock()) {
                            try {
                                System.out.println(Thread.currentThread().getId() + ":My Job done");
                                return;
                            } finally {
                                lock1.unlock();
                            }
                        }
                    } finally {
                        lock2.unlock();
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        TryLock r1 = new TryLock(1);
        TryLock r2 = new TryLock(2);
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
    }
}

上述代码中.采用了非常容易死锁的加锁顺序,在一般情况下,这会导致t1和t2互相等待. 但是使用tryLock()后,就会得到大大改善,线程不会傻傻的等待,而是不停的尝试.因此只要时间足够的长,线程总是会得到所有需要的资源.


公平锁

在大多情况下.锁的申请都是非公平的.两个线程同时申请锁a,谁先获得锁a呢 这是不一定的,系统只是会从这个锁的等待队列中随机挑选一个.而公平的锁,则不是这样的,他会按照时间的先后顺序,保证先到先得,后到后的。

扫描二维码关注公众号,回复: 11536998 查看本文章

公平锁的一大特点就是,不会产生饥饿现象,只要你排队,最终还是可以得到资源的, 如果我们使用synchronized关键字进行锁控制,那么产生的锁就是非公平的,而重入锁循序我们对其公平性进行设置,它的构造函数如下:public ReentrantLock(boolean fair) 当参数为fair为true时,表示锁是公平的。

以下代码很好地突出了公平锁的特点

public class FairLock implements Runnable {
    public static ReentrantLock fairlock = new ReentrantLock(true);
    @Override
    public void run() {
        while (true){
            try {
                fairlock.lock();
                System.out.println(Thread.currentThread().getName()+"获得锁");
            } finally {
                fairlock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        FairLock r1 = new FairLock();
        Thread t1 = new Thread(r1,"Thread_t1");
        Thread t2 = new Thread(r1,"Thread_t2");
        t1.start();
        t2.start();
    }
}

控制台输出的结果如下

Thread_t1获得锁
Thread_t2获得锁
Thread_t1获得锁
Thread_t2获得锁
Thread_t1获得锁
Thread_t2获得锁
Thread_t1获得锁
Thread_t2获得锁

猜你喜欢

转载自blog.csdn.net/weixin_43517302/article/details/106250046
今日推荐