java并发-锁-ReentrantLock(重入锁)和ReentrantReadWriteLock(读写锁)

转载地址:https://www.cnblogs.com/whatadiors/p/8013086.html

  • 同步控制是并发程序必不可少的重要手段,synchronized关键字就是一种简单的控制方式,除此之外,JDK内部并发包中也也提供了Lock接口,该接口中提供了lock()方法和unLock()方法对显式加锁和显式释放锁操作进行支持。

ReentrantLock(重入锁)

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

复制代码

public class Test {
    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;
    public static void increase() {
        try {
            lock.lock();
            i++;
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws Exception {
        Thread t1 = new TestThread();
        Thread t2 = new TestThread();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}
class TestThread extends Thread {
    @Override
    public void run() {
        for (int j = 0; j < 1000; j++) {
            Test.increase();
        }
    }
}

复制代码

从这段代码可以看到与synchronized相比,重入锁有着显示的操作过程,我们需要手动定义核实加锁,核实释放锁,但也就是因为这样,重入锁对逻辑的控制灵活性要好于synchronized。

公平锁

大多数情况下锁的申请都是非公平的。如一个线程1先请求了锁A,然后线程2页也请求了锁A,那么当锁A可用时,是线程1可以获得锁还是线程2是不一定的,系统只是会从这个锁的等待队列中随机挑选一个。

重入锁允许我们对其公平性进行设置。公平锁的一大特点是:它不会产生饥饿现象。只要排队,最终你就可以获得资源。可以使用如下构造函数创建公平锁:

public ReentrantLock(boolean fair)

当参数fair为true,表示锁的公平的,当然由于公平所需要维护有序队列,因此公平锁的实现成本比较高,性能相对也底下,所以默认都是非公平锁

复制代码

public class Test {
    public static ReentrantLock lock = new ReentrantLock(true);
    public static void main(String[] args) throws Exception {
        Thread t1 = new TestThread();
        Thread t2 = new TestThread();
        t1.start();
        t2.start();
    }
}
class TestThread extends Thread {
    @Override
    public void run() {
        while(true)
        try {
            Test.lock.lock();
            System.out.println(Thread.currentThread().getName()+"获得锁");
        } finally{
            Test.lock.unlock();
        }
    }
}

复制代码

可以看到如上代码制定公平锁之后,两个线程交替获得锁

复制代码

Thread-1获得锁
Thread-0获得锁
Thread-1获得锁
Thread-0获得锁
Thread-1获得锁
Thread-0获得锁
Thread-1获得锁
Thread-0获得锁
Thread-1获得锁
Thread-0获得锁
Thread-1获得锁
Thread-0获得锁
Thread-1获得锁
...

复制代码

ReentrantLock的一些其他方法:

public boolean tryLock();
//使用此方法,当前线程会尝试获得锁,如果锁未被其他线程占用,则申请锁成功,返回true,否则会立即返回false.这种模式不会引起线程等待,因此也不会产生死锁。
public boolean tryLock(long timeout, TimeUnit unit)
//在这里tryLock接收两个参数,一个表示等待时长,一个表示计时单位,表示在一段时间范围内如果得到锁就返回true,否则直接返回false,不在继续等待锁。

复制代码

public class Test implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        try {
            if (lock.tryLock(5, TimeUnit.SECONDS)) {
                System.out.println(Thread.currentThread().getName());
                System.out.println("get lock success");
                Thread.sleep(6000);
            } else {
                System.out.println(Thread.currentThread().getName());
                System.out.println("get lock failed");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
    public static void main(String args[]) {
        Test timeLock = new Test();
        Thread thread1 = new Thread(timeLock);
        Thread thread2 = new Thread(timeLock);
        thread1.start();
        thread2.start();
    }
}

复制代码

输出结果:

Thread-0
get lock success
Thread-1
get lock failed

 其他:

关于重入锁的具体原理及这部分源码的分析可以看下这篇文章http://www.cnblogs.com/xrq730/p/4979021.html

ReentrantReadWriteLock(读写锁)

ReadWriteLock是JDK5开始提供的读写分离锁。读写分离开有效的帮助减少锁的竞争,以提升系统性能。用锁分离的机制避免多个读操作线程之间的等待。

读写锁的访问约束:

  • 读-读不互斥:读读之间不阻塞
  • 读-写互斥:读堵塞写,写也阻塞读
  • 写-写互斥:写写阻塞

如果在一个系统中读的操作次数远远大于写操作,那么读写锁就可以发挥明显的作用,提升系统性能

复制代码

public class Test {
    private static Lock lock = new ReentrantLock();
    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private static Lock readLock = reentrantReadWriteLock.readLock();
    private static Lock writeLock = reentrantReadWriteLock.writeLock();
    private static int value;

    public static Object handleRead(Lock lock) throws InterruptedException {
        try {
            lock.lock();
            Thread.sleep(1000);// 模拟读操作
            System.out.println("读操作:" + value);
            return value;
        } finally {
            lock.unlock();
        }
    }

    public static void handleWrite(Lock lock, int index)
            throws InterruptedException {
        try {
            lock.lock();
            Thread.sleep(1000);// 模拟写操作
            System.out.println("写操作:" + value);
            value = index;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws Exception {
        TestReadThread testReadThread = new TestReadThread();
        TestWriteThread testWriteThread = new TestWriteThread();
        for (int i = 0; i < 18; i++) {
            new Thread(testReadThread).start();
        }
        for (int i = 18; i < 20; i++) {
            new Thread(testWriteThread).start();
        }

    }
    
    private static class TestReadThread extends Thread {
        @Override
        public void run() {
            try {
                //Test.handleRead(lock);
                Test.handleRead(readLock);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static class TestWriteThread extends Thread {
        @Override
        public void run() {
            try {
                //Test.handleWrite(lock,new Random().nextInt(100));
                Test.handleWrite(writeLock,new Random().nextInt(100));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

复制代码

这一段代码可以清楚的表达读写锁的作用,如果不使用读写锁,那么整个程序执行时间大概是20s。换用读写锁后只需要2s多,这里读线程完全并行,节省了大部分时间。

猜你喜欢

转载自blog.csdn.net/SeaSky_Steven/article/details/87722655