文章目录
一、线程安全问题
1.1为什么出现线程安全问题?
- 线程安全问题是指会造成数据不一致。比如说商品库存就100,结果卖出了101份甚至更多。
- 在单线程下对共享资源(如静态变量)的修改操作,不会引发安全问题。
- 在多线程环境下,读操作不会有安全问题,写操作会造成安全问题,因为在执行时不是原子操作
- 原子操作:是指一种不可分割的操作,cpu执行一次就能完成,即操作在执行过程中不会被其他线程中断。
二、使用sychronized关键字实现同步减库存
2.1 不加同步锁
class TicketThread implements Runnable{
private static int ticketCount = 8;
Object o = new Object();
@Override
public void run(){
while (true){
if(ticketCount > 0){
System.out.println(Thread.currentThread().getName() + "售出第 " + ticketCount + " 张票");
ticketCount--;
} else {
break;
}
}
}
}
public class Demo07 {
public static void main(String[] args) {
TicketThread ticketThread = new TicketThread();
Thread thread1 = new Thread(ticketThread);
thread1.setName("窗口1");
TicketThread ticketThread2 = new TicketThread();
Thread thread2 = new Thread(ticketThread2);
thread2.setName("窗口2");
thread1.start();
thread2.start();
}
}
窗口1售出第 8 张票
窗口1售出第 7 张票
窗口2售出第 8 张票
窗口1售出第 6 张票
窗口2售出第 5 张票
窗口1售出第 4 张票
窗口2售出第 3 张票
窗口1售出第 2 张票
窗口2售出第 1 张票
分析:一共八张票,结果第八张重复售出两次,存在线程安全问题。
2.2 加synchronized同步代码块
- 格式
synchronized (锁对象){
//处理逻辑
}
- 锁对象必须是引用类型,如String、Integer、Object、Class等
- 必须是同一个锁对象,哪个线程获取到锁对象,就可以执行该代码块
- 案例代码:
class TicketThread implements Runnable{
private static int ticketCount = 8;
Object o = new Object();
@Override
public void run(){
while (true){
synchronized (TicketThread.class){
if(ticketCount > 0){
System.out.println(Thread.currentThread().getName() + "售出第 " + ticketCount + " 张票");
ticketCount--;
} else {
break;
}
}
}
}
}
public class Demo07 {
public static void main(String[] args) {
TicketThread ticketThread = new TicketThread();
Thread thread1 = new Thread(ticketThread);
thread1.setName("窗口1");
TicketThread ticketThread2 = new TicketThread();
Thread thread2 = new Thread(ticketThread2);
thread2.setName("窗口2");
thread1.start();
thread2.start();
}
}
窗口1售出第 8 张票
窗口1售出第 7 张票
窗口1售出第 6 张票
窗口1售出第 5 张票
窗口1售出第 4 张票
窗口1售出第 3 张票
窗口1售出第 2 张票
窗口1售出第 1 张票
分析:
- 正常售出,没有超卖
- 使用Class 类对象作为锁对象最保险,因为该对象始终只有一个,例如TicketThread.class
- 也可以使用类的静态变量(引用类型)作为锁对象,也最保险,因为该对象始终只有一个
- 其次使用this,前提是该类只有一个实例。比如通过 new TicketThread()实例化出了多个对象,那么就是多个this、多把锁,不安全。实例变量也是这个情况。在本例中,有两个TicketThread()对象,若使用this和实例变量作为锁对象,则出现线程安全问题
2.3 同步方法
2.3.1 同步实例方法
class TicketThread implements Runnable{
private static int ticketCount = 8;
Object o = new Object();
@Override
public void run(){
sellTicket();
}
public synchronized void sellTicket(){ // 锁对象是this
while (true){
if(ticketCount > 0){
System.out.println(Thread.currentThread().getName() + "售出第 " + ticketCount + " 张票");
ticketCount--;
} else {
break;
}
}
}
}
}
public class Demo07 {
public static void main(String[] args) {
TicketThread ticketThread = new TicketThread();
Thread thread1 = new Thread(ticketThread);
thread1.setName("窗口1");
Thread thread2 = new Thread(ticketThread);
thread2.setName("窗口2");
thread1.start();
thread2.start();
}
}
窗口1售出第 8 张票
窗口1售出第 7 张票
窗口1售出第 6 张票
窗口1售出第 5 张票
窗口1售出第 4 张票
窗口1售出第 3 张票
窗口1售出第 2 张票
窗口1售出第 1 张票
分析:
- synchronized修饰实例方法,锁对象是this,确保只有一个实例对象,因此new TicketThread()了一个对象
- 若 Thread thread2 = new Thread(ticketThread);修改为Thread thread2 = new Thread(new TicketThread());则出现安全问题,两个对象的this不是同一个实例对象,也就是不是同一把锁,出现超卖:
窗口2售出第 8 张票
窗口2售出第 7 张票
窗口2售出第 6 张票
窗口2售出第 5 张票
窗口2售出第 4 张票
窗口1售出第 8 张票
窗口2售出第 3 张票
窗口1售出第 2 张票
窗口2售出第 1 张票
2.3.2 同步静态方法
class TicketThread implements Runnable{
private static int ticketCount = 8;
Object o = new Object();
@Override
public void run(){
sellTicketStatic();
}
public synchronized static void sellTicketStatic(){ // 锁对象是TicketThread.class
while (true){
synchronized (TicketThread.class){
if(ticketCount > 0){
System.out.println(Thread.currentThread().getName() + "售出第 " + ticketCount + " 张票");
ticketCount--;
} else {
break;
}
}
}
}
}
public class Demo07 {
public static void main(String[] args) {
TicketThread ticketThread = new TicketThread();
Thread thread1 = new Thread(ticketThread);
thread1.setName("窗口1");
TicketThread ticketThread2 = new TicketThread();
Thread thread2 = new Thread(ticketThread2);
thread2.setName("窗口2");
thread1.start();
thread2.start();
}
}
窗口1售出第 8 张票
窗口1售出第 7 张票
窗口1售出第 6 张票
窗口1售出第 5 张票
窗口1售出第 4 张票
窗口1售出第 3 张票
窗口1售出第 2 张票
窗口1售出第 1 张票
分析:
- synchronized修饰类静态方法,锁对象是TicketThread.class,只有一把锁,因此是安全的