synchronized的功能拓展:重入锁(读书笔记)

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

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

    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object‘s
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            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);
    }
}
 

这里 我们使用重入锁保护临界区资源i 确保多线程对i操作的安全性,我们可以看出,与synchronized相比,重入锁有着显示的操作过程,开发人员必须手动指定何时加锁,何时释放锁,也正因为这样,重入锁对于逻辑控制的灵活性要远远好于synchronized,但是要必须释放锁,否则 其他线程就没有机会现在方法临界区资源了!另外重入锁允许一个线程连续几次获取同一把锁,但是释放锁的时候也要释放相同次数.

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

下面代码产生了一个死锁,但是得益于锁的中断,我们可以很轻松的解决死锁.

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

    public IntLock(int lock) {
        this.lock = lock;
    }

    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object‘s
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @Override
    public void run() {
        try {
            if (lock == 1) {
                lock1.lockInterruptibly();//锁1 这是一个可以对中断进行想要的锁申请动作!
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock2.lockInterruptibly();//锁2 加锁
            } else {
                lock2.lockInterruptibly();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock1.lockInterruptibly();//锁1 加锁
            }
        } 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 {
        IntLock r1 = new IntLock(1);//线程实例 1
        IntLock r2 = new IntLock(2);//线程实例 2
        Thread t1 = new Thread(r1);//线程1
        Thread t2 = new Thread(r2);//线程2
        t1.start();
        t2.start();
        Thread.sleep(1000);//Main线程 休眠1s

        t2.interrupt();//中断其中一个线程

    }
}

线程t1和t2启动后,t1占用rock1 在占用rock2  t2先占用rock2 然后请求rock1 因此很容易形成互相等待,  当我们让t2中断时,他放弃了申请lock1 然后释放了lock2 实际上是 t1线程 完成任务正常退出,而t2 是中断的,

  • 锁申请等待限时  除了等待通知之外,要避免死锁还有另外一种方法,那就是限时等待, 就是规定一个时间,超出时间没有拿到锁 就退出
public class TimeLock implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();

    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object‘s
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @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.start();
        t2.start();
    }
}

在本例中,由于占用锁的线程会持有锁长达6s,故另一个线程无法在5s的等待时间内获得锁,因此,请求锁会失败!

ReentrantLock.tryLock()方法可以不带参数直接运行,在这种情况下,当前线程会尝试获得锁,如果锁并未被其他线程占用,则申请会成功!并立即返回true,如果锁被其他线程占用,则档期你先吃不会进行任何等待,而是立即返回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;
    }

    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object‘s
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @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呢 这是不一定的,系统只是会从这个锁的等待队列中随机挑选一个.而公平的锁,则不是这样的,他会按照时间的先后顺序,保证先到先得,后到后的,公平锁的一大特点就是,不会产生饥饿现象,只要你排队,最终还是可以得到资源的, 如果我们使用synchronized关键字进行锁控制,那么产生的锁就是非公平的,而重入锁循序我们对其公平性进行设置,下面是他的构造器

public ReentrantLock(boolean fair) {

sync = fair ? new FairSync() : new NonfairSync();
}

当参数fair为true时,表示锁是公平的,公平锁必须要求系统维护一个有序的队列,因此实现成本比较高,性能相对低下,因此默认锁是非公平的.

时间: 2024-10-24 15:57:24

synchronized的功能拓展:重入锁(读书笔记)的相关文章

浅谈Java中的锁:Synchronized、重入锁、读写锁

Java开发必须要掌握的知识点就包括如何使用锁在多线程的环境下控制对资源的访问限制 ◆ Synchronized ◆ 首先我们来看一段简单的代码: 12345678910111213141516171819 public class NotSyncDemo { public static int i=0; static class ThreadDemo extends Thread { @Override public void run() { for (int j=0;j<10000;j++)

可重入锁

可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响.在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁. 下面是使用实例: public class Test implements Runnable{ public synchronized void get(){ System.out.println(Thread.currentThread().getId()); set(); } public sy

可重入锁(good)

可重入锁,也叫做递归锁,是指在一个线程中可以多次获取同一把锁,比如:一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法[即可重入],而无需重新获得锁:https://www.zhihu.com/question/23284564/answer/29633571 对于不同线程则相当于普通的互斥锁. 在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁 最大的作用是避免死锁.在很多情况下线程需要多次进入锁内执行任务

可重入锁 &amp; 自旋锁 &amp; Java里的AtomicReference和CAS操作 &amp; Linux mutex不可重入

之前还是写过蛮多的关于锁的文章的: http://www.cnblogs.com/charlesblc/p/5994162.html <[转载]Java中的锁机制 synchronized & 偏向锁 & 轻量级锁 & 重量级锁 & 各自> http://www.cnblogs.com/charlesblc/p/5935326.html <[Todo] 乐观悲观锁,自旋互斥锁等等> http://www.cnblogs.com/charlesblc/

Java并发编程-可重入锁

可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍可以获取该锁而不受影响.在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁. [java]public class Test implements Runnable{ public synchronized void get(){ System.out.println(Thread.currentThread().getId()); set(); } public synchroni

Java可重入锁

锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) .这些已经写好提供的锁为我们开发提供了便利,但是锁的具体性质以及类型却很少被提及.本系列文章将分析JAVA下常见的锁名称以及特性,为大家答疑解惑. 四.可重入锁: 本文里面讲的是广义上的可重入锁,而不是单指JAVA下的ReentrantLock. 可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响. 在JAV

聊聊高并发(十六)实现一个简单的可重入锁

可重入锁指的是如果一个线程已经获得了一个锁,那么它可以多次进入这个锁,当然前提是线程需要先获得这个锁. 可重入锁是最常使用的锁,Java的内置锁就是可重入锁,使用synchronized关键字可以启用内置锁机制,比如说一个类有两个synchronized方法A和B,在A方法中调用了B方法,如果锁不是可重入的,那么访问B时需要再次竞争锁,这样会带来死锁. public synchronized void A(){ B(); } public synchronized void B(){ } 可重入

Java锁之可重入锁介绍

锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) .这些已经写好提供的锁为我们开发提供了便利,但是锁的具体性质以及类型却很少被提及.本系列文章将分析JAVA下常见的锁名称以及特性,为大家答疑解惑. 四.可重入锁: 本文里面讲的是广义上的可重入锁,而不是单指JAVA下的ReentrantLock. 可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响. 在JAV

zbb20180929 thread 自旋锁、阻塞锁、可重入锁、悲观锁、乐观锁、读写锁、对象锁和类锁

1.自旋锁自旋锁可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得锁,则继续执行.若线程依然不能获得锁,才会被挂起.使用自旋锁后,线程被挂起的几率相对减少,线程执行的连贯性相对加强.因此,对于那些锁竞争不是很激烈,锁占用时间很短的并发线程,具有一定的积极意义,但对于锁竞争激烈,单线程锁占用很长时间的并发程序,自旋锁在自旋等待后,往往毅然无法获得对应的锁,不仅仅白白浪费了CPU时间,最终还是免不了被挂起的操作 ,