java多线程之Lock锁原理以及案例实现电影院卖票

简介: java多线程之Lock锁原理以及案例实现电影院卖票

为什么会出现Lock锁?

我们知道 synchronized 给代码加锁或解锁时,我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock锁使用

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作

Lock中提供了获得锁和释放锁的方法:

  • void lock();//获得锁
  • void unlock();//释放锁
  • Lock类是接口,不能直接实例化,这里采用他的实现类ReentrantLock来实例化
    ReentrantLock的构造方法
    ReentrantLock():创建一个ReentrantLock的实例

代码实战

需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

package com.heima.thread001;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyRun implements Runnable {
    int ticket = 0;
    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            lock.lock();
            try {
                if (ticket == 100) {
                    break;
                }else {
                    ticket ++;
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票 ...");
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}
package com.heima.thread001;
import java.util.concurrent.ExecutionException;
public class TestDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyRun myRun = new MyRun();
        //创建线程对象
        Thread t1 = new Thread(myRun);
        Thread t2 = new Thread(myRun);
        Thread t3 = new Thread(myRun);
        //起名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        //开启线程
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果如下:

窗口1正在卖第1张票 ...
窗口3正在卖第2张票 ...
窗口3正在卖第3张票 ...
窗口3正在卖第4张票 ...
窗口3正在卖第5张票 ...
窗口3正在卖第6张票 ...
窗口3正在卖第7张票 ...
窗口3正在卖第8张票 ...
窗口3正在卖第9张票 ...
窗口3正在卖第10张票 ...
窗口3正在卖第11张票 ...
窗口3正在卖第12张票 ...
窗口3正在卖第13张票 ...
窗口3正在卖第14张票 ...
窗口3正在卖第15张票 ...
窗口3正在卖第16张票 ...
窗口3正在卖第17张票 ...
窗口3正在卖第18张票 ...
窗口3正在卖第19张票 ...
窗口3正在卖第20张票 ...
窗口3正在卖第21张票 ...
窗口3正在卖第22张票 ...
窗口3正在卖第23张票 ...
窗口3正在卖第24张票 ...
窗口3正在卖第25张票 ...
窗口3正在卖第26张票 ...
窗口3正在卖第27张票 ...
窗口3正在卖第28张票 ...
窗口3正在卖第29张票 ...
窗口3正在卖第30张票 ...
窗口3正在卖第31张票 ...
窗口3正在卖第32张票 ...
窗口3正在卖第33张票 ...
窗口3正在卖第34张票 ...
窗口3正在卖第35张票 ...
窗口3正在卖第36张票 ...
窗口3正在卖第37张票 ...
窗口3正在卖第38张票 ...
窗口3正在卖第39张票 ...
窗口3正在卖第40张票 ...
窗口3正在卖第41张票 ...
窗口3正在卖第42张票 ...
窗口3正在卖第43张票 ...
窗口3正在卖第44张票 ...
窗口3正在卖第45张票 ...
窗口3正在卖第46张票 ...
窗口3正在卖第47张票 ...
窗口3正在卖第48张票 ...
窗口3正在卖第49张票 ...
窗口3正在卖第50张票 ...
窗口3正在卖第51张票 ...
窗口3正在卖第52张票 ...
窗口3正在卖第53张票 ...
窗口3正在卖第54张票 ...
窗口3正在卖第55张票 ...
窗口3正在卖第56张票 ...
窗口3正在卖第57张票 ...
窗口3正在卖第58张票 ...
窗口3正在卖第59张票 ...
窗口3正在卖第60张票 ...
窗口3正在卖第61张票 ...
窗口3正在卖第62张票 ...
窗口3正在卖第63张票 ...
窗口3正在卖第64张票 ...
窗口3正在卖第65张票 ...
窗口3正在卖第66张票 ...
窗口3正在卖第67张票 ...
窗口3正在卖第68张票 ...
窗口3正在卖第69张票 ...
窗口3正在卖第70张票 ...
窗口3正在卖第71张票 ...
窗口3正在卖第72张票 ...
窗口3正在卖第73张票 ...
窗口3正在卖第74张票 ...
窗口3正在卖第75张票 ...
窗口3正在卖第76张票 ...
窗口3正在卖第77张票 ...
窗口3正在卖第78张票 ...
窗口3正在卖第79张票 ...
窗口3正在卖第80张票 ...
窗口3正在卖第81张票 ...
窗口3正在卖第82张票 ...
窗口3正在卖第83张票 ...
窗口3正在卖第84张票 ...
窗口3正在卖第85张票 ...
窗口3正在卖第86张票 ...
窗口3正在卖第87张票 ...
窗口3正在卖第88张票 ...
窗口3正在卖第89张票 ...
窗口3正在卖第90张票 ...
窗口3正在卖第91张票 ...
窗口3正在卖第92张票 ...
窗口3正在卖第93张票 ...
窗口3正在卖第94张票 ...
窗口3正在卖第95张票 ...
窗口3正在卖第96张票 ...
窗口3正在卖第97张票 ...
窗口3正在卖第98张票 ...
窗口3正在卖第99张票 ...
窗口3正在卖第100张票 ...

知识扩展

1、synchronized和Lock的区别?

  • synchronized是关键字,Lock是接口。
  • synchronized无法获取锁的状态,Lock可以。
  • synchronized会自动释放锁,Lock需要手动。
  • synchronized没有Lock锁灵活(Lock锁可以自己定制)。

2、ReentrantLock 和 Synchronized 对比

  • ReentrantLock 通过 lock() 和 unlock() 加解锁,Synchronized会自动解锁,ReentrantLock需要手动解锁
  • ReentrantLock相比Synchronized的优势是: 可中断,公平锁,多个锁

3、Lock 接口的主要方法

  • void lock() — 如果lock是空闲的,当前线程就会获取到锁
  • boolean tryLock() — 如果lock可用,则获取锁并且返回true,否则返回false
  • void unlock() — 当前线程释放持有的锁
  • Condition newCondition() — 条件对象: 获取等待通知组件
  • getHoldCount() — 查询当前线程保持此锁的次数
  • getQueueLength() — 返回正在等待此锁的线程数量
  • getWaitQueueLength() — 返回正在等待与此锁相关给定条件的线程数量
  • hasWaiters(Condition condition) — 查询在特定Condition下是否有线程等待
  • hasQueuedThread(Thread thread) — 查询给定线程是否等待获取此锁
  • hasQueuedThreads — 是否有线程等待此锁
  • isFair() — 该锁是否是公平锁
  • isHeldByCurrentThread() — 当前线程是否保持锁定
  • isLock() — 此锁是否有任意线程占用
  • lockInterruptibly() — 如果当前线程未被中断,获取锁
  • tryLock() — 尝试获取锁,仅在调用时锁未被线程占用,获得锁
  • tryLock(long timeout, TimeUnit unit) — 如果锁在给定等待时间没有被另一个线程保持,获取锁

锁优化

  • 减少锁持有的时间 — 只在有线程安全要求的程序上加锁
  • 减少锁粒度 — 将大的对象拆成小对象,增加并行度,减少锁竞争
  • 锁分离 — 读写锁的思想,读的时候加read lock, 写的时候加 write lock
  • 锁粗化 — 保证线程持有锁的时间尽量短
  • 锁消除 — 如果发现不能被共享的对象,需要消除这些对象的锁操作


相关文章
|
6天前
|
存储 监控 Java
JAVA线程池有哪些队列? 以及它们的适用场景案例
不同的线程池队列有着各自的特点和适用场景,在实际使用线程池时,需要根据具体的业务需求、系统资源状况以及对任务执行顺序、响应时间等方面的要求,合理选择相应的队列来构建线程池,以实现高效的任务处理。
81 12
|
19天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
4天前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
21天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
21天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
Java
Java中需要注意的一些案例
Java中需要注意的一些案例
125 0
|
9天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
49 17
|
21天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
45 3
|
21天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
116 2