锁(Locks)

锁(Locks)

1 ReentrantLock

应用demo

可重入锁,是一种使用递归无堵塞的同步机制

synchronized 更强大、更灵活的锁机制,可以减少死锁发生的概率

默认为非公平锁,可以自定义为公平锁

底层采用 AQS 实现,通过内部 Sync 集成 AQS

简单应用:

/**
 * @author kenewstar
 * @version 1.0
 * @date 2021/5/14
 */
public class Concurrent07 {
    private int count;
    private final Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Concurrent07 c = new Concurrent07();

        CountDownLatch count = new CountDownLatch(2);

        ExecutorService service = Executors.newCachedThreadPool();

        service.execute(() -> {
            // 加锁
            c.lock.lock();
            try {
                for (int i = 0; i < 100000000; i++) {
                    c.count ++;
                }
                count.countDown();
            } finally {
                // 释放锁
                c.lock.unlock();
            }
        });
        service.execute(() -> {
            c.lock.lock();
            try {
                for (int i = 0; i < 100000000; i++) {
                    c.count ++;
                }
                count.countDown();
            } finally {
                c.lock.unlock();
            }
        });
        count.await();
        service.shutdown();
        System.out.println("计算结果:" + c.count);

    }

}

运行结果:

在这里插入图片描述

如果不对两个线程进行加锁操作,那么最终结果将不是预期的。

公平锁与非公平锁

公平锁的简单应用

/**
 * @author kenewstar
 * @version 1.0
 * @date 2021/5/15
 */
public class Concurrent08 {
    /**
     * 公平锁,fair参数设置为true
     * 非公平锁,fair参数设置为false(默认)
     */
    private static final Lock lock = new ReentrantLock(true);

    public static void runTest(String name, int time) {
        lock.lock();
        try {
            System.out.println("线程 " + name + "获取了锁");
            TimeUnit.SECONDS.sleep(time);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            System.out.println("线程 " + name + "释放了锁");
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 2; i++) {
            executorService.execute(() -> runTest("A", 1));
            executorService.execute(() -> runTest("B", 1));
            executorService.execute(() -> runTest("C", 1));
        }
        executorService.shutdown();
    }
}

运行结果:

在这里插入图片描述

第二次获取锁的顺序和第一次一致,也就是等待锁的时间最长的优先获取锁。

非公平锁的结果可能如下(也可能与公平锁一致,具有不确定性):

在这里插入图片描述

响应中断

我们创建两个线程造成死锁,然后使用其中一个线程中断,然后另一个线程就可以正常获取锁了。

案例如下:

/**
 * @author kenewstar
 * @version 1.0
 * @date 2021/5/15
 */
public class Concurrent09 {
    private static Lock lock1 = new ReentrantLock();
    private static Lock lock2 = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                lock1.lockInterruptibly();
                System.out.println("t1 获取了 lock1");
                lock2.lockInterruptibly();
                System.out.println("t1 获取了 lock2");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock1.unlock();
                lock2.unlock();
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                lock2.lockInterruptibly();
                System.out.println("t2 获取了 lock2");
                lock1.lockInterruptibly();
                System.out.println("t2 获取了 lock1");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock2.unlock();
                lock1.unlock();
            }
        });
        t1.start();
        t2.start();
        Thread.sleep(2000);
        t1.interrupt();
    }
}

运行结果:

在这里插入图片描述

两个线程都在相互等待对方释放锁导致死锁,此时 t1 线程中断,然后t1线程中的lock1释放锁,t2线程就能成功获取到t1锁了。

限时等待

/**
 * @author kenewstar
 * @version 1.0
 * @date 2021/5/15
 */
public class Concurrent10 {
    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> {
            boolean flag = false;
            try {
                flag = lock.tryLock(1000, TimeUnit.MILLISECONDS);
                if (flag) {
                    System.out.println("线程A获取锁成功");
                    TimeUnit.SECONDS.sleep(2);
                } else {
                    System.out.println("线程A获取锁失败");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (flag) {
                    lock.unlock();
                }
            }
        });
        executorService.execute(() -> {
            boolean flag = false;
            try {
                flag = lock.tryLock(1000, TimeUnit.MILLISECONDS);
                if (flag) {
                    System.out.println("线程B获取锁成功");
                    TimeUnit.SECONDS.sleep(2);
                } else {
                    System.out.println("线程B获取锁失败");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // ReentrantLock的一个方法:isHeldByCurrentThread()
                // 可用于判断当前线程是否获取到Lock锁
                if (flag) {
                    lock.unlock();
                }
            }
        });

        executorService.shutdown();
    }
}

运行结果:

在这里插入图片描述

线程B会尝试 1s 后打印获取锁失败信息。

ReentrantLock与Synchronized

synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。

synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。

synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以响应中断。

相比于synchronized,ReentrantLock 在功能上更加丰富,它具有 可重入、可中断、可限时、公平锁 等特点

2 ReentrantReadWriteLock

上一节我们使用了ReentrantLock锁,该锁效果与synchronized的关键字的功能差不多,如果在写多读少的环境下,毋庸置疑的使用它也是比较合适的,但是在写少读多的环境下进行加锁,势必造成不必要的性能浪费。在只读的情况下并不存在线程安全问题,其他线程也可读取,但是此时加上了互斥锁,那么会大大降低性能,因此我们需要一个能够在读线程的情况下,其他线程也可以获取锁。我们可以使用ReentrantReadWriteLock。

读写锁,两把锁:共享锁-读锁,排它锁:写锁

支持公平性、非公平性、可重入、锁降级

锁降级:遵循获取写锁、获取读锁在释放写锁的次序,写锁能够降级成为读锁

读写锁的应用

简单应用如下:

/**
 * @author kenewstar
 * @version 1.0
 * @date 2021/5/16
 */
public class Concurrent11 {
    private static ReadWriteLock lock = new ReentrantReadWriteLock();
    private static Lock readLock =  lock.readLock();
    private static Lock writeLock = lock.writeLock();
    private static Random rand = new Random(100);
    private static int pc;
    public void read() {
        readLock.lock();
        try {
            System.out.println("线程 " + Thread.currentThread() + " 读取数据 ----> " + pc);
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readLock.unlock();
        }
    }
    public void write(String name) {
        writeLock.lock();
        try {
            int s = rand.nextInt(99);
            System.out.println("线程" + name + " 写入数据 ---> " + s);
            pc = s;
        } finally {
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        Concurrent11 c = new Concurrent11();

        ExecutorService service = Executors.newFixedThreadPool(20);
        for (int i = 0; i < 10; i++) {
            service.execute(c::read);
        }
        service.execute(() -> c.write("E"));
        service.execute(() -> c.write("D"));

        service.shutdown();
    }

}

运行结果如下:

在这里插入图片描述

运行过程,前面十个线程是几乎同时打印数据,然后等待大约 1s 后面两个线程才开始打印数据。由此可知,读取数据时,其他读取线程也可以获取读锁,而无法获取写锁。写数据时,读写锁都无法获取,即读锁是共享锁,写锁是排它锁。

锁降级

即是由写锁降级为读锁

/**
 * @author kenewstar
 * @version 1.0
 * @date 2021/5/16
 */
public class Concurrent12 {
    private static ReadWriteLock lock = new ReentrantReadWriteLock();
    private static Lock readLock = lock.readLock();
    private static Lock writeLock = lock.writeLock();
    private int num;
    public static void main(String[] args) {
        Concurrent12 c = new Concurrent12();
        ExecutorService service = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 5; i++) {
            service.execute(c::read);
        }
        service.execute(c::write);
        for (int i = 0; i < 8; i++) {
            service.execute(c::read);
        }
        service.shutdown();
    }

    public void read() {
        readLock.lock();
        try {
            System.out.println(Thread.currentThread() + " 读取 ---> " + num);
            Thread.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readLock.unlock();
        }
    }
    public void write() {
        writeLock.lock();
        try {
            num = 12;
            Thread.sleep(600);
        } catch (Exception e) {
            e.printStackTrace();
        }
        readLock.lock();
        try {
            writeLock.unlock();
            System.out.println(Thread.currentThread() + " 锁降级 ---> " + num);
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readLock.unlock();
        }
    }
}

运行结果如下:

在这里插入图片描述

首先获取读锁,此时无法获取写锁,等待锁释放,而后write获取写锁,修改数据后降级为读锁时,其他的读锁也能获取读锁,结果中锁降级与下面的读取结果同时打印。

ReentrantReadWriteLock不支持锁升级。

3 Condition

Lock提供条件Condition,对线程的等待和唤醒更加详细和灵活

内部维护一个Condition队列。当前线程调用await方法后,将会以当前线程构造为一个结点Node,并将该节点放到该队列的尾部

Condition是个接口,基本的方法就是await()和signal()方法;
Condition依赖于Lock接口,生成一个Condition的方式是lock.newCondition()

我们来看一个使用Lock + Condition多线程顺序打印A B C …的例子

应用demo

/**
 * @author kenewstar
 * @version 1.0
 * @date 2021/5/16
 */
public class Concurrent03 {
    private int count;
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    public static void main(String[] args) {
        Concurrent03 c = new Concurrent03();
        ExecutorService service = Executors.newCachedThreadPool();

        service.execute(c::printA);
        service.execute(c::printB);
        service.execute(c::printC);

        service.shutdown();
    }

    public void printA() {
        lock.lock();
        try {
            while (true){
                if (count % 3 != 0) {
                    condition.await();
                }
                Thread.sleep(300);
                System.out.print("A ");
                count ++;
                condition.signalAll();
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printB() {
        lock.lock();
        try {
            while (true){
                if (count % 3 != 1) {
                    condition.await();
                }
                Thread.sleep(300);
                System.out.print("B ");
                count ++;
                condition.signalAll();
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printC() {
        lock.lock();
        try {
            while (true){
                if (count % 3 != 2) {
                    condition.await();
                }
                Thread.sleep(300);
                System.out.print("C ");
                count ++;
                condition.signalAll();
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

运行结果如下:

在这里插入图片描述

4 LockSupport

当需要阻塞或唤醒一个线程的时候,都会使用LockSupport工具类来完成相应工作,LockSupport为构建同步组件的基础工具。 LockSupport定义了一组 以park开头的方法用来阻塞当前线程,以及unpark(Thread thread)方法来唤醒一个被阻塞的线程。 Park有停车的意思,假设线程为车辆,那么park方法代表着停车,而unpark方法则是指车辆启动离开

简单应用

/**
 * @author kenewstar
 * @version 1.0
 * @date 2021/5/16
 */
public class Concurrent13 {

    static class LockSupportTest extends Thread {

        @Override
        public void run() {
            System.out.println("thread......");

            LockSupport.park();
            System.out.println("执行逻辑......");
        }
    }

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

        Thread thread = new LockSupportTest();
        thread.start();

        Thread.sleep(1000);
        LockSupport.unpark(thread);
        System.out.println("main end.....");

    }
}

运行结果如下:

在这里插入图片描述

当调用park()方法时会阻塞当前线程,调用unpark(Thread t)时,会唤醒指定线程。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值