java线程同步之ReentrantLock

作者: Tifkingsly | 来源:发表于2018-05-28 11:28 被阅读0次

    ReentrantLock初识:

    ReentrantLock与synchronized关键字一样都是用于实现线程之间的同步操作,两者效果基本一直。JDK1.5引入ReentrantLock,因为它相比于synchronized来说显得更加灵活,扩张功能更加强大,例如嗅探锁定,多路分支通知等功能。

    public class MyService {
    
        private ReentrantLock reentrantLock = new ReentrantLock();
    
        public void methodA() {
            try {
                reentrantLock.lock();
                System.out.println("methodA begin lock" + "Thread Name is ---->" + Thread.currentThread().getName() + "  time is " +  System.currentTimeMillis());
                System.out.println();
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
            }
            System.out.println("methodA finish  " + "Thread Name is ---->" + Thread.currentThread().getName() + "  time is " + 
            System.currentTimeMillis());
        }
    
        public void methodB() {
            try {
                reentrantLock.lock();
                System.out.println("methodB begin lock" + "Thread Name is ---->" + Thread.currentThread().getName() + "  time is " + System.currentTimeMillis());
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
            }
            System.out.println("methodB finish  " + "Thread Name is ---->" + Thread.currentThread().getName() + "  time is " + System.currentTimeMillis());
        }
    }
    
    public class ThreadA extends Thread{
    
        private MyService service;
    
        public ThreadA(MyService service) {
            this.service = service;
        }
    
        @Override
        public void run() {
            super.run();
            service.methodA();
        }
    }
    public class ThreadB extends Thread{
    
        private MyService service;
    
        public ThreadB(MyService service) {
            this.service = service;
        }
    
        @Override
        public void run() {
            super.run();
            service.methodB();
        }
    }
    
    public class Main {
    
        public static void main(String[] args) {
            MyService service = new MyService();
            ThreadA threadA = new ThreadA(service);
            threadA.setName("ThreadA");
            ThreadB threadB = new ThreadB(service);
            threadB.setName("ThreadB");
            threadA.start();
            threadB.start();
        }
    
    }
    
    执行结果
    上面例子可以看出,ReentrantLock锁定的是对象,多个线程访问同一对象的同一个Lock对象的代码时会出现互斥。ReentrantLock在使用时lock()与unlock()方法必须成对出现,否则会出现死锁

    ReentrantLock特性:

    Condition使用方式:

    synchronized关键字实现同步时,可以通过Object中的wait(),notify()方法实现等待/通知,但是notify调用时只是会随机唤醒其中一个等待线程,无法唤醒特定线程,如果使用notifyAll方法会将所有等待线程都唤醒。而使用ReentrantLock在这方面具有优势,Lock可以根据不同的Condition唤醒指定线程,实现多路等待。

    public class MyService {
    
        private ReentrantLock reentrantLock = new ReentrantLock();
        private Condition conditionA = reentrantLock.newCondition();
        private Condition conditionB = reentrantLock.newCondition();
    
        public void awaitA() {
            try{
                reentrantLock.lock();
                System.out.println("begin awaitA time is  " + System.currentTimeMillis() + " and Thread is  " + Thread.currentThread().getName());
                conditionA.await();
                System.out.println("end awaitA time is  " + System.currentTimeMillis() + " and Thread is  " + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
            }
        }
    
        public void awaitB() {
            try{
                reentrantLock.lock();
                System.out.println("begin awaitB time is  " + System.currentTimeMillis() + " and Thread is  " + Thread.currentThread().getName());
                conditionA.await();
                System.out.println("end awaitB time is  " + System.currentTimeMillis() + " and Thread is  " + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
            }
        }
    
        public void signalA() {
            try {
                reentrantLock.lock();
                System.out.println("begin signalA time is  " + System.currentTimeMillis() + " and Thread is  " + Thread.currentThread().getName());
                conditionA.signalAll();
            } finally {
                reentrantLock.unlock();
            }
        }
    
        public void signalB() {
            try {
                reentrantLock.lock();
                System.out.println("begin signalB time is  " + System.currentTimeMillis() + " and Thread is  " + Thread.currentThread().getName());
                conditionA.signalAll();
            } finally {
                reentrantLock.unlock();
            }
        }
    ....
    }
    
    public class Main {
    
        public static void main(String[] args) {
            MyService service = new MyService();
            ThreadA threadA = new ThreadA(service);
            threadA.setName("ThreadA");
            ThreadB threadB = new ThreadB(service);
            threadB.setName("ThreadB");
            threadA.start();
            threadB.start();
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            service.signalA();
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            service.signalB();
        }
    
    }
    
    image.png

    ThreadA、ThreadB中run()分别调用service的awaitA、awaitB方法,为了验证使用不同的condition进行await操作时,需要使用对应的condition进行唤醒操作。我们使用了signalAll()方法,该方法与notifyAll方法一样,但是它只是唤醒使用同一个condition的等待线程,对于不同的condition不起作用,这就是ReentrantLock的多路等待实现方式。

    tryLock获取锁:
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
    

    tryLock()方法会立即返回结果,如果可以获得锁则返回true,反之返回false。带参数的tryLock很容易理解,就是在获取锁失败时,会等待一段时间(传入的参数),当等待时间达到传入值时,返回获取锁的结果,同tryLock方法一样。

    公平性与非公平性锁:
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    

    ReentrantLock默认实现为非公平锁。在公平的锁上,线程按照他们发出请求的顺序获取锁,但在非公平锁上,则允许‘插队’:当一个线程请求非公平锁时,如果在发出请求的同时该锁变成可用状态,那么这个线程会跳过队列中所有的等待线程而获得锁。非公平的ReentrantLock 并不提倡插队行为,但是无法防止某个线程在合适的时候进行插队。非公平锁与公平锁相比,性能上会更优。

    在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个所,那么新发出的请求的线程将被放入到队列中。而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中。

    结束语

    本篇仅简单介绍ReentrantLock的使用方式和一些基础知识,关于原理实现会在后面介绍了AbstractQueuedSynchronizer之后再进行分享。

    相关文章

      网友评论

        本文标题:java线程同步之ReentrantLock

        本文链接:https://www.haomeiwen.com/subject/fmywjftx.html