买票问题产生线程安全问题
问题产生背景
- 场景1: 一个窗口卖100张票,单线程不会产生共享数据也不会导致线程安全问题
- 场景2: 1号窗口卖(0-33) 2号窗口卖(34-66) 3号窗口卖(67-100)票 不存在共享数据也不会导致线程安全问题
- 场景3: 1-3号窗口同时卖1-100号票 存在共享数据会导致线程安全问题
代码模拟
package com.su27.ThreadSafe;
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
// 设置线程任务*卖票
@Override
public void run(){
while(true){
// 先判断票是否存在
if(ticket>0){
// 提高线程安全问题出现概率
try {
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
// 票存在 卖票ticket--
System.out.println(Thread.currentThread().getName()+"--->正在卖第"+(ticket--)+"张票");
}
}
}
}
package com.su27.ThreadSafe;
// 模拟卖票案例
// 创建3个线程,同时开启,对共享票出售
public class Demo {
public static void main(String[] args) {
// 创建Runnale接口的实现类对象
RunnableImpl run = new RunnableImpl();
// 创建Thread类对象 构造方法中 传递Runnable接口的实现类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
// 调用start调用多线程
t0.start();
t1.start();
t2.start();
}
}
Thread-2--->正在卖第3张票
Thread-2--->正在卖第1张票
Thread-1--->正在卖第-1张票 // 线程安全问题 出现超卖
Thread-0--->正在卖第0张票 // 线程安全问题 出现超卖
分析产生原因
解决线程安全问题
同步代码块
- 使用synchronized关键字传入锁对象并将 有线程安全问题的代码块 放入同步代码块
package com.su27.SolveThreadSafe;
/*
解决线程安全问题方案一:使用同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
1.通过代码块中的锁对象,可以使用任意对象
2. 但必须保证多个线程使用锁对象是同一个
3. 锁对象作用:
把同步代码块锁住,只让一个线程在同步代码块中执行
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
// 创建一锁对象
Object obj = new Object();
// 设置线程任务*卖票
@Override
public void run(){
while(true){
// 同步代码块
synchronized (obj){
// 先判断票是否存在
if(ticket>0){
// 提高线程安全问题出现概率
try {
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
// 票存在 卖票ticket--
System.out.println(Thread.currentThread().getName()+"--->正在卖第"+(ticket--)+"张票");
}
}
}
}
}
同步技术原理
- 同步代码块线程,没有执行完毕不释放锁,同步外线程没有锁进不去同步代码块,保证始终只能有一个线程执行,频繁上/下锁,降低效率,但避免线程安全问题
同步方法
- 定义synchronized修饰的方法将有线程安全问题的代码放入其中
package com.su27.SolveThreadSafe1;
/*
解决线程安全问题方案一:使用同步方法
使用步骤:
1. 把访问了共享数据代码抽取出来,放到方法中
2. 在方法上加synchronized修饰符
格式: 修饰符 synchronized 返回类型 方法名(参数列表){
共享数据代码
}
注意:
1.通过代码块中的锁对象,可以使用任意对象
2. 但必须保证多个线程使用锁对象是同一个
3. 锁对象作用:
把同步代码块锁住,只让一个线程在同步代码块中执行
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
// 创建一锁对象
Object obj = new Object();
// 设置线程任务*卖票
@Override
public void run(){
while(true){
// 同步代码块
payTicket();
}
}
/*定义同步方法*/
public synchronized void payTicket() {
// 先判断票是否存在
if(ticket>0){
// 提高线程安全问题出现概率
try {
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
// 票存在 卖票ticket--
System.out.println(Thread.currentThread().getName()+"--->正在卖第"+(ticket--)+"张票");
}
}
}
- 注意 同步方法的锁对象就是实现对象 new RunnableImpl()也就是this
- 验证this例子
/*定义同步方法*/
public /*synchronized*/ void payTicket() {
synchronized(this){
// 先判断票是否存在
if(ticket>0){
// 提高线程安全问题出现概率
try {
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
// 票存在 卖票ticket--
System.out.println(Thread.currentThread().getName()+"--->正在卖第"+(ticket--)+"张票");
}
}
}
死锁问题
- A B同时执行 A首先加1锁 B首先加2锁 A下一步要2锁 B要1锁 产生
锁的嵌套 就发生死锁情况,互相因为得不到对方手中的资源无法释放当前各种资源。
package com.su27.DeadLock;
public class Demo {
public static void main(String[] args) {
Object objA = new Object();
Object objB = new Object();
new Thread(()->{
while (true){
synchronized (objA){
synchronized (objB){
System.out.println("小康同学在走路");
}
}
}
}).start();
new Thread(()->{
while (true){
synchronized (objB){
synchronized (objA){
System.out.println("小微同学在走路");
}
}
}
}).start();
}
}
|