线程不安全情况
以买票问题为例
买票问题
三个线程同时开启模拟三个窗口同时售票
public class Demo1 {
public static void main(String[] args) {
//线程不安全演示
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
@Override
public void run() {
while(count>0){
//买票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票"+count);
}
}
}
}
运行结果:
可以明显看到即使循环的条件是票数>0但余票还是出现了负数的情况,此时即出现了线程不安全问题。
解决方案
产生原因
单一的线程在while循环中读到的count数据不是“真实“的count数据,在执行while语句时count的值可能已经被其他的线程”插足“修改过了。
所以解决的根本目的是让某线程在“买票”的时间段内其他线程不得对数据进行处理,即让各个线程排队执行。
以下提供了三种让线程排队执行的方案。
同步代码块
public class Demo1 {
public static void main(String[] args) {
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
//锁对象
private Object o = new Object();
@Override
public void run() {
while(true){
synchronized (o){
if (count>0){
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,余票"+count);
}else {
break;
}
}
}
}
}
}
为什么要加if判断呢,因为上锁的其实就是if语句里面的操作。
运行结果:
可以看到明显没有票数小于0的情况了,但是为什么只有0号线程在卖票呢?其实这么说是不准确的,因为0号线程是最先开启的,它先执行上锁(上锁后其他线程不得操作数据),再解锁,但再解锁之后可以理解为0号线成离锁是最近的,因此它抢到的概率最大,而1号和2号线程抢到的概率较小。
同步方法
同步方法与同步代码块比较相似,皆是用到了synchronize方法来执行上锁操作。
public class Demo1 {
public static void main(String[] args) {
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
public synchronized boolean sale(){
if (count>0){
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,余票"+count);
return true;
}
return false;
}
@Override
public void run() {
while(true){
boolean flag =sale();
if (!flag){
break;
}
}
}
}
}
显式锁Lock
直接通过Lock类的上锁方法lock()和解锁方法unlock()进行手动上锁和解锁。
需要注意的是此方法创建的锁为显示锁,而上面的同步代码块儿和同步方法创建的皆是隐式锁。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo1 {
public static void main(String[] args) {
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
//创建锁
Lock l = new ReentrantLock();
@Override
public void run() {
while(true){
//上锁
l.lock();
if (count>0){
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,余票"+count);
}else {
break;
}
//解锁
l.unlock();
}
}
}
}