(一)问题描述
我们 先通过代码案例看线程执行过程中会发生那些安全问题
模拟电影院卖票:
某电影院目前正在上映国产大片,共有100张票,而它有3个平台卖票,请设计一个程序模拟该电影院卖票。
分析:事:卖票
卖:窗口 【不同的窗口卖相同的票】
程序中窗口就是线程对象【Thread的对象】,卖票是线程任务【Runnable的对象】
具体代码实现:
package com.tlc.thread;
public class SellTicket implements Runnable {
//定义初始票数
int ticket = 100;
@Override
public void run() {
while(ticket > 0) {
try {
//线程睡眠就代表正在出票
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口:" + Thread.currentThread().getName() + "买票成功!"
+ "还剩" + --ticket + "张票");
}
}
}
public class SellTicketTest {
public static void main(String[] args) {
//创建线程任务
SellTicket ticket = new SellTicket();
//创建三个线程对象模拟三个窗口
Thread thread1 = new Thread(ticket, "窗口一");
Thread thread2 = new Thread(ticket, "窗口二");
Thread thread3 = new Thread(ticket, "窗口三");
//启动线程,三个窗口开始卖票
thread1.start();
thread2.start();
thread3.start();
}
}
结果:
(二)案例中出现的问题
从上代码执行的结果中我们发现一张票被不同的窗口买了多次,这就是线程安全问题
产生的原因:
多条线程同时操作同一个资源造成的混乱的执行结果
(三)解决
目的:就是同步线程
解决方案:
1.同步代码块
2.同步方法
3.Lock接口
1)同步代码块
1. 格式: synchronized(锁对象) { 发生线程安全的代码 }
2.锁对象:可以是任意的引用数据类型的对象,但是一旦确定就要保证其唯一性
3.优缺点
优点:解决了多线程的数据安全问题;
缺点:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
代码实例【只是对run方法进行改进,其他代码不变】:
@Override
public void run() {
//开始卖票
while (ticket > 0) {
//这里加锁,只有拿到锁对象的线程才可以进去执行
synchronized ("a") {
//由于买到最后一张票时,三个线程都会进来,这样就会造成票为负数的现象,
//因此在这里要进行判断票数
if (ticket > 0) {
try {
//线程睡眠就代表正在出票
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口:" + Thread.currentThread().getName() + "买票成功!"
+ "还剩" + --ticket + "张票");
}
}
}
}
2)同步方法
1.格式:
修饰符 (static) synchronized 返回值类型 方法名(方法参数){方法体}
锁对象: 成员同步方法锁对象是,this;而静态同步方法锁对象是,类名. class 。
主要代码:
@Override
public void run() {
//开始卖票
while (ticket > 0) {
sellTicket();
}
}
public static synchronized void sellTicket() {
if (ticket > 0) {
try {
//线程睡眠就代表正在出票
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口:" + Thread.currentThread().getName() + "买票成功!"
+ "还剩" + --ticket + "张票");
}
}
3)Lock锁
1.为了更清晰的表达如何加锁和释放锁
- JDK5以后提供了一个新的锁对象Lock,Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化。
2.方法介绍
ReentrantLock构造方法: ReentrantLock( )
加锁解锁方法: lock( ):获得锁
unlock( ):释放锁
具体实现代码:
//定义初始票数
/*static*/ int ticket = 100;
//创建Lock对象
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
//开始卖票
while (ticket > 0) {
//获得锁
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口:" + Thread.currentThread().getName() + "买票成功!"
+ "还剩" + --ticket + "张票");
}//释放锁
lock.unlock();
}
}
以上部分如有错误之处,欢迎各位大佬留言解释,谢谢!
版权声明:本文为CSDN博主「喵了个@汪」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44919512/article/details/119487021
如果看完对你的学习有帮助,感谢点赞支持!