多线程安全问题
在多线程程序中,安全问题主要是由于CPU在执行程序期间,切换线程而导致的。下面是一段售票案例的代码,代码中存在多线程的安全隐患:
开启三个线程,执行售票操作
class ThreadDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t0 = new Thread(ticket);
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
t0.start();
t1.start();
t2.start();
}
}
Runnable接口实现类实现业务逻辑
class Ticket implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "出售了第" + ticket--);
}
}
}
}
上述代码在执行后打印如下结果(由于是多线程程序,可能需要多次程序才会暴露出安全问题)
Thread-0出售了第100
Thread-1出售了第100
Thread-1出售了第99
Thread-1出售了第98
Thread-0出售了第99
Thread-1出售了第97
可以看到,不同的线程出售了同一张票,这就是安全问题
多线程安全问题解决思路
当一个线程在执行数据操作时,无论是否休眠或拥有CPU资源,其他线程只能等待它完成操作才能再次操作,这样就能解决安全问题。
JAVA中提供了一种同步技术,用来解决线程安全问题,公式如下:
synchronized(任意对象){
线程要操作的共享数据
}
套用在上面的例子中,Ticket类代码如下:
class Ticket implements Runnable {
private int ticket = 100;
private Object obj = new Object();
@Override
public void run() {
synchronized (obj) {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "出售了第" + ticket--);
}
}
}
}
}
同步代码块的工作原理
同步代码块关键字synchronized后需要有一个任意对象,这个对象被称为同步锁,又叫对象监视器。当线程执行同步代码时,首先会检查同步锁是否存在,如果存在就进入同步代码块执行代码,如果不存在,则无法进入同步代码块。
在上面的例子中,当线程0执行到同步代码块时,线程0就会占有该同步锁,此时同步代码块中就没有同步锁了,如果线程0此时休眠或失去CPU,那么其他的线程会因为没有同步锁而无法进入同步代码块,线程0在执行完毕之后,会将同步锁释放。然后线程开始再次根据CPU的调度运行。这样就保证了数据的安全性和同步性。
总结
同步代码块的工作原理可以总结为两点:
- 没有同步锁的线程,无法进入同步代码块;
- 在同步中的线程,执行完同步代码之后,才会释放同步锁;