线程卖票案例分析
有线程安全问题代码
java
public class Ticket implements Runnable {
//成员变量
private int ticketCount = 100;
@Override
public void run() {
while (true) {
if (ticketCount > 0) {
//模拟出票时间,让当前线程休息100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "售出票号:" + ticketCount);
ticketCount--;//票数减一
}
if(ticketCount==0){
break;//票数为0时退出循环
}
}
}
}
public class TicketDemo {
public static void main(String[] args) {
//创建线程任务对象
Ticket ticket = new Ticket();
//创建三个买票窗口(三个线程在执行同一个任务)
Thread t1 = new Thread(ticket, "窗口1 - ");
Thread t2 = new Thread(ticket, "窗口2 - ");
Thread t3 = new Thread(ticket, "窗口3 - ");
t1.start();
t2.start();
t3.start();
}
}
线程安全问题:卖票案例中重复票出现的原因,负数票出现的原因
发生线程安全问题的原因: 多个线程对同一个数据,进行读写操作,造成数据错乱


线程安全问题的解决
基本思想:
让共享数据存在安全的环境中 , 当某一个线程访问共享数据时其他线程是无法操作的
怎么实现呢?
- 把多条线程操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
- java语言基于线程安全问题,提供了:同步机制
线程的同步
java允许多线程并发执行,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证该变量的唯一性和准确性。
三种实现同步的方式:【同步机制之所以能够实现,是因为其加了同步锁】
-
同步代码块
-
同步方法
-
锁机制。Lock锁
1. 同步代码块
格式:
java
synchronized(任意对象) {
多条语句操作共享数据的代码
}
synchronized:关键字,表示同步
任意对象:锁对象
a.默认情况锁是打开的,只要有一个线程进去执行代码了,锁就会关闭
b.当线程执行完出来了,锁才会自动打开
c.锁对象可以是任意对象 , 但是多个线程必须使用同一把锁
解决上面的售票安全问题:将对共享数据操作的代码都放到同步代码块中
java
public class Ticket implements Runnable {
//成员变量
private int ticketCount = 100;
@Override
public void run() {
while (true) {
//使用同步代码块解决,线程安全问题
// this:锁对象,是任意类型的对象,也可以是"锁对象"在括号中
synchronized (this) {
if (ticketCount > 0) {
//模拟出票时间,让当前线程休息100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "售出票号:" + ticketCount);
ticketCount--;//票数减一
}
if (ticketCount == 0) {
break;//票数为0时退出循环
}
}
}
}
}
public class TicketDemo {
public static void main(String[] args) {
//创建线程任务对象
Ticket ticket = new Ticket();
//创建三个买票窗口(三个线程在执行同一个任务)
Thread t1 = new Thread(ticket, "窗口1 - ");
Thread t2 = new Thread(ticket, "窗口2 - ");
Thread t3 = new Thread(ticket, "窗口3 - ");
t1.start();
t2.start();
t3.start();
}
}
同步的好处和弊端
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
2. 同步方法
同步方法:就是把synchronized关键字加到方法上,保证线程执行该方法的时候,其他线程只能在方法外等着【该方法多个线程并发访问时只能唯一访问】
格式:
java
格式:
修饰符 synchronized 返回值类型 方法名(方法参数) { }
同步方法也有对象锁,不需要我们自己写:
1. this锁 (当前对象锁)
2. 类名.class (静态方法上的对象锁)
注意 : 同步方法时不能指定锁对象的 , 但是有默认存在的锁对象的。
- 对于非static方法,同步锁就是this。
- 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。 Class类型的对象
解决上面的售票安全问题:使用同步方法解决问题
java
//线程任务类
public class Ticket implements Runnable {
//成员变量
private int ticketCount = 100;
@Override
public void run() {
//模拟卖票
while(true){
if(ticketCount <= 0){
break;
}
method();
}
}
//使用同步方法解决线程安全问题(方法中的代码都是同步代码)
public synchronized void method(){
if(ticketCount > 0){
//模拟出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "出售票号:" + ticketCount);
ticketCount--;
}
}
}
public class TicketDemo {
public static void main(String[] args) {
//创建线程任务对象
Ticket ticket = new Ticket();
//创建三个买票窗口(三个线程在执行同一个任务)
Thread t1 = new Thread(ticket, "窗口1 - ");
Thread t2 = new Thread(ticket, "窗口2 - ");
Thread t3 = new Thread(ticket, "窗口3 - ");
t1.start();
t2.start();
t3.start();
}
}
同步代码块和同步方法的区别:
同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
同步代码块可以指定锁对象,同步方法不能指定锁对象
3. 锁机制。Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock中提供了获得锁和释放锁的方法
java
void lock():获得锁
void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
java
ReentrantLock的构造方法
ReentrantLock():创建一个ReentrantLock的实例
注意:多个线程使用相同的Lock锁对象,需要多线程操作数据的代码放在lock()和unLock()方法之间。一定要确保unlock最后能够调用
解决上面的售票安全问题:使用Lock锁解决问题
java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Ticket implements Runnable {
//成员变量
private int ticketCount = 100;
//锁对象
Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
//使用Lock锁解决,线程安全问题
//获取锁
l.lock();
if (ticketCount > 0) {
//模拟出票时间,让当前线程休息100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "售出票号:" + ticketCount);
ticketCount--;//票数减一
}
if (ticketCount == 0) {
break;//票数为0时退出循环
}
//释放锁
l.unlock();
}
}
}
public class TicketDemo {
public static void main(String[] args) {
//创建线程任务对象
Ticket ticket = new Ticket();
//创建三个买票窗口(三个线程在执行同一个任务)
Thread t1 = new Thread(ticket, "窗口1 - ");
Thread t2 = new Thread(ticket, "窗口2 - ");
Thread t3 = new Thread(ticket, "窗口3 - ");
t1.start();
t2.start();
t3.start();
}
}
使用Lock锁实现同步安全的步骤
1.创建Lock锁对象
2.调用lock()方法获得锁
3.调用unLock()释放锁