概念
线程安全问题主要指的是多个线程操作共享数据的时候出现的数据安全性问题,也就是可能不符合预期数据。
比如说,多个线程进行买票可能出现重票的情况。
代码demo:
public class Thread1 {
public static void main(String[] args) {
TicketWindow ticketWindow = new TicketWindow();
Thread t1 = new Thread(ticketWindow);
Thread t2 = new Thread(ticketWindow);
Thread t3 = new Thread(ticketWindow);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class TicketWindow implements Runnable {
private int ticketNum = 10;
public void run() {
while (true) {
if (ticketNum > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ", 正在卖第" + ticketNum + "号票");
ticketNum--;
} else {
break;
}
}
}
}
结果:
窗口2, 正在卖第10号票
窗口3, 正在卖第10号票
窗口1, 正在卖第10号票
窗口2, 正在卖第7号票
窗口1, 正在卖第7号票
窗口3, 正在卖第7号票
窗口1, 正在卖第4号票
窗口2, 正在卖第4号票
窗口3, 正在卖第4号票
窗口2, 正在卖第1号票
窗口3, 正在卖第1号票
窗口1, 正在卖第1号票
很明显,有重复票的,这是线程安全问题。
出现线程的安全性问题原因:在多个线程正在执行代码的过程中,某个线程执行尚未完成的时候,别的线程参与进来执行代码。
线程安全问题的解决
方法:当某个线程在操作共享数据的时候,不让其他线程参与进来。直到这个线程操作完共享数据后,其他线程才可以操作这个共享数据。可以通过同步机制来解决线程安全问题。
使用synchronized
所有的线程都共用一把锁。
同步代码块
synchronized(监视器){
需要同步的代码逻辑
}
锁的种类:
- 自行创建对象
- 使用类当作锁
- 使用this表示类的实例对象,当作锁
代码demo:
public class Thread1 {
public static void main(String[] args) {
TicketWindow ticketWindow = new TicketWindow();
Thread t1 = new Thread(ticketWindow);
Thread t2 = new Thread(ticketWindow);
Thread t3 = new Thread(ticketWindow);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class TicketWindow implements Runnable {
private int ticketNum = 10;
Object obj = new Object();
public void run() {
while (true) {
synchronized(obj) {
if (ticketNum > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ", 正在卖第" + ticketNum + "号票");
ticketNum--;
} else {
break;
}
}
}
}
}
运行结果:
窗口1, 正在卖第10号票
窗口3, 正在卖第9号票
窗口3, 正在卖第8号票
窗口3, 正在卖第7号票
窗口2, 正在卖第6号票
窗口2, 正在卖第5号票
窗口2, 正在卖第4号票
窗口2, 正在卖第3号票
窗口2, 正在卖第2号票
窗口3, 正在卖第1号票
同步方法
将要同步的代码放到一个方法中,将方法声明为synchronized同步方法,在run方法中调用这个方法。
public class Thread1 {
public static void main(String[] args) {
TicketWindow ticketWindow = new TicketWindow();
Thread t1 = new Thread(ticketWindow);
Thread t2 = new Thread(ticketWindow);
Thread t3 = new Thread(ticketWindow);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class TicketWindow implements Runnable {
private int ticketNum = 10;
Object obj = new Object();
public void run() {
while(ticketNum > 0) {
sell();
}
}
// 监视器为this
private synchronized void sell() {
if (ticketNum > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ", 正在卖第" + ticketNum + "号票");
ticketNum--;
}
}
}
使用ReentrantLock
在需要同步的代码语句前,调用ReentrantLock对象的lock方法加锁,在同步代码语句结束处,调用unlock方法解锁。
代码:
import java.util.concurrent.locks.ReentrantLock;
public class Thread1 {
public static void main(String[] args) {
TicketWindow ticketWindow = new TicketWindow();
Thread t1 = new Thread(ticketWindow);
Thread t2 = new Thread(ticketWindow);
Thread t3 = new Thread(ticketWindow);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class TicketWindow implements Runnable {
private int ticketNum = 10;
private ReentrantLock lock = new ReentrantLock();
public void run() {
while(ticketNum > 0) {
sell();
}
}
private void sell() {
try {
lock.lock(); // 加锁
if (ticketNum > 0) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ", 正在卖第" + ticketNum + "号票");
ticketNum--;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 解锁
}
}
}