Java多线程安全问题
线程的安全问题
案例
需求 :某电影院目前正在上映国产大片,共有100张票,而他有3个窗口卖票,请设计一个程序模拟该电影院卖票
思路 :
- 定义一个类Ticket实现Runnable接口,里面定义一个成员变量:
private int ticketCount = 100;
- 在Ticket类中重写run()方法实现卖票,步骤如下:
- 判断票数如果大于0,就卖票,并告知哪个窗口购买
- 票数自动减一
- 卖光之后,线程停止
- 定义一个测试类TicketDemo,步骤如下:
- 创建Ticket类的对象
- 创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称
- 启动线程
代码实现
public class Ticket implements Runnable {
private int ticketCount = 100;
@Override
public void run() {
while(true){
if (ticketCount<=0){
break;
}else{
this.ticketCount -= 1;
System.out.println(Thread.currentThread().getName()+"正在卖票,还剩有"+this.ticketCount+"张");
}
}
}
}
public class Demo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket,"一号窗口");
Thread t2 = new Thread(ticket,"二号窗口");
Thread t3 = new Thread(ticket,"三号窗口");
t1.start();
t2.start();
t3.start();
}
}
卖票案例的思考
看似这个案例没有什么问题,但是在实际生活中,售票时候出票是需要一定的时间的,所以在出售一张票的时候需要一点时间的延迟,接下来就修改卖票程序中的动作,每次出票时间为100毫秒,用sleep()方法实现。
此时出现了问题
- 相同的票出现了多次
- 出现的负数的票
为什么出现这个问题(这也是我们判断多线程程序是否会有数据安全问题的标准)
- 多线程操作共享数据
如果解决多线程安全问题?
- 基本思想:让程序没有安全问题的环境
实现方法
- 把多条语句操作共享数据的代码锁起来,让任意时刻只能有一个先吃执行。
同步代码块
锁多条语句操作共享数据,可以使用同步代码块实现
- 格式:
synchronized(任意对象){ 多条语句操作共享数据的代码 }
- 默认情况是打开的,只要有一个线程进去执行代码了,锁就会关闭。
- 当线程执行完了之后,锁就会自动打开。
同步的好处和弊端
- 好处:解决了多线程的数据安全问题
- 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是非常浪费资源的,无形中降低了程序的运行效率
下面我们更新一下Ticket类。
public class Ticket implements Runnable {
private int ticketCount = 100;
private Object o = new Object();
@Override
public void run() {
while(true){
//锁对象,任意对象就可以.
//多个线程必须使用同一把锁
synchronized (o){
if (ticketCount==0){
break;
}else{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.ticketCount -= 1;
System.out.println(Thread.currentThread().getName()+"正在卖票,还剩有"+this.ticketCount+"张");
}
}
}
}
}
此时的运行结果变得非常自然。
synchronized的锁对象必须是唯一的
同步方法
同步方法:就是把synchronized
关键字加到方法上。
- 格式:
修饰符 synchronized 返回值类型 方法名(方法参数){ }
同步静态方法:就是把synchronized关键词加到静态方法上
- 格式:
修饰符 static synchronized 返回值类型 方法名(方法参数){ }
主要记住:
- 同步方法到底锁对象是什么? this
- 同步静态方法的锁对象是什么? 类名.class
同步代码块和同步方法的区别
- 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码。
- 同步代码块可以指定锁对象,同步方法不能指定锁对象。
★Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更加清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法
- void lock() 获得锁
- void unlock() 释放锁
Lock是接口不能直接实例化,可以采用它的实现类ReentrantLock
来实例化
ReentrantLock的构造方法
- ReentrantLock() 创建一个ReentrantLock的实例
注意:一般释放锁unlock要放入finally中。
下面我们使用Lock来更新一下Ticket类
public class Ticket implements Runnable {
private int ticketCount = 100;
//锁对象Lock
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//开启Lock锁
lock.lock();
try {
if (ticketCount == 0) {
break;
} else {
Thread.sleep(100);
this.ticketCount -= 1;
System.out.println(Thread.currentThread().getName() + "正在卖票,还剩有" + this.ticketCount + "张");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放Lock锁
lock.unlock();
}
}
}
}
死锁
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
建议:不要写锁的嵌套。
- 《Redis设计与实现》读书笔记(三) ——Redis中的链表
- 《Redis设计与实现》读书笔记(四) ——Redis中的跳跃表
- 解析Linux中的VFS文件系统之文件系统的注册(二)
- vivi虚拟摄像头驱动程序
- 系统架构 | 基于微服务架构,改造企业核心系统之实践
- 《Redis设计与实现》读书笔记(五) ——Redis中的整数集合
- 《Redis设计与实现》读书笔记(七) ——Redis对象综述及字符串对象实现原理
- 自动登录脚本
- 极致之处,精彩无限 - 优化了一半的SQL
- 编程修炼 | Scala中Stream的应用场景及其实现原理
- Linux之内存描述符mm_struct
- 大数据 | Spark的现状与未来发展
- 信号量、互斥锁、自旋锁、原子操作
- 理解OAuth 2.0
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 你想了解的JDK 10版本更新都在这里
- Linux Page Cache调优在 Kafka 中的应用
- 声明式 UIKit 在有赞美业的实践
- 一个@Transaction哪里来这么多坑?
- 绘图代码|10种绘制热图方法,你想要的全都有!
- 有赞DB连接池性能优化
- 有赞移动热修复平台建设
- 有赞零售智能硬件体系搭建历程
- 有赞移动如何做到并行灰度的复杂场景?
- 微商城订单模块重构实践
- 有赞移动应用如何给页面安上“任意门”
- 无用的设计模式-上篇
- JavaScript简介及JavaScript中的关键保留字、变量和数据类型
- 有赞iOS精准测试实践
- 聊聊java中的哪些Map:(一)HashMap(1.8)源码分析