多线程经典案例:多线程售票
我们定义一个用于售票的线程类,充当售票员的角色
- 定义一个静态变量充当公共票池,并记录票的数量
- 在run方法中实现卖票的功能逻辑
卖票线程类:
public class TicketThread extends Thread{
//公共票池票数
private static int ticketNum = 100;
@Override
public void run() {
while (ticketNum>0){
if(ticketNum>0){
System.out.println(Thread.currentThread().getName() + "卖出票号" + ticketNum);
ticketNum--;
}
}
}
}
创建多个线程卖票:
public static void main(String[] args) {
TicketThread t1 = new TicketThread();
TicketThread t2 = new TicketThread();
TicketThread t3 = new TicketThread();
t1.setName("售票员1");
t2.setName("售票员2");
t3.setName("售票员3");
t1.start();
t2.start();
t3.start();
}
结果:
我们运行多次会发现,有不同线程重复卖同一张票的情况,为什么会出现这种情况,因为产生了线程安全问题
多线程-线程安全问题
线程安全问题:多个线程访问同一个数据,容易出现线程安全问题
java中解决线程安全问题的方式:
- 同步
- 加锁
同步方式
java中同步的关键字 synchronized
,可以修饰代码块和方法
同步代码块方式:
- 使用同步代码块需要有一个监听对象
- 保证所有的线程共享一个同步监听对象的,也就是保证被同步监听对象是被所有线程共享的,如:静态属性,字节码文件对象等
public class TicketThread extends Thread{
//公共票池票数
private static int ticketNum = 100;
@Override
public void run() {
while (ticketNum>0){
//同步代码块
synchronized (TicketThread.class){
if(ticketNum>0){
System.out.println(Thread.currentThread().getName() + "卖出票号" + ticketNum);
ticketNum--;
}
}
}
}
}
同步方法方式:
- 就是在需要被同步的方法上面加关键字 synchronized
- 加的位置:在返回值类型的前面
- 不需要也不能够显示的写同步监听对象
- 如果是一个非static的方法,那么同步监听对象就是this
- 如果是static修饰的方法,那么同步监听对象就是当前方法所在的类的字节码对象
public class TicketThread extends Thread{
//公共票池票数
private static int ticketNum = 100;
@Override
public void run() {
while (ticketNum>0){
test();
}
}
/**
* 同步方法
*/
static synchronized void test(){
if(ticketNum>0){
System.out.println(Thread.currentThread().getName() + "卖出票号" + ticketNum);
ticketNum--;
}
}
}
加锁方式
- 创建一个Lock接口的实现类对象(锁对象)
- 在可能发生线程安全的核心代码加锁
lock()
方法 - 使用try...catch...finally代码块,finally中解锁
unlock()
方法
注: 加锁相对于同步而言灵活性更高,效率较高,同步由jvm维护,加锁代码级别的,需要手动释放
public class TicketThread extends Thread{
//公共票池票数
private static int ticketNum = 50;
//创建锁对象,static修饰,所有卖票线程共享
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (ticketNum>0){
//加锁
lock.lock();
try {
if(ticketNum>0){
System.out.println(Thread.currentThread().getName() + "卖出票号" + ticketNum);
ticketNum--;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//解锁
lock.unlock();
}
}
}
}