线程的同步与死锁,是整个多线程中需要重要理解的概念。这个问题的产生说白了就是资源占用的问题。
问题的引出: 卖票程序
假设现在一共有10张票,由很多进程进行卖票,那么当剩下最后一张票的时候就产生了竞争。 如下图所示,产生问题的原因在于网络延迟。当多个用户代表的多个线程依赖买票程序的判断时,多个进程以极小的时间差异进入到判断中,此时,由于此时还剩一张票,所有的线程都是能够检测到1的,都满足了进入判断的条件。此时,由于不同线程的网络延迟不同,必然有线程最先将最后一张票取走,此时另外几个已经进入到判断的程序却又进行了买票操作,就会产生票数为负数的情况。这就产生了“线程不同步”问题。
卖票问题引出线程同步问题
由于有延迟,线程不同步的话就会使得访问速度快,毕竟是多线程同时执行的。但是,线程不同步必然是一种不安全的操作方法。
线程的同步
要解决线程的同步问题,说白了就是让线程一个个排队进入到判断中,而不是一起进入。方法就是,当一个线程进入判断中,立刻上一把锁。
同步问题的解决
实现线程的同步,必须使用Java中的关键字:
synchronized
。这个关键字有两种使用同步代码块的方式:
1. 直接在业务逻辑上锁住该段业务逻辑
- 如果使用同步代码块,必须设置一个要锁住的对象,这个对象一般就是当前对象:this。
这种实现方式是在方法中进行的,也就意味着所有的进程还是可以并发进入到方法,只是被拦截在某一步骤之外进行了同步。
class SalerThread implements Runnable {
private int ticketNum = 10;
@Override
public void run() {
for (int i = 0; i < 20; i++) {
synchronized (this){
if (ticketNum > 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+",ticket = " + ticketNum--);
}
}
}
}
}
public class ThreadSync {
public static void main(String args[]) {
SalerThread saler = new SalerThread();
new Thread(saler,"窗口A").start();
new Thread(saler,"窗口B").start();
new Thread(saler,"窗口C").start();
}
}
同步代码块范例1执行结果
2. 直接在方法前面加上synchronize关键字,代表每次只允许一个进程进入这个方法。
class SalerThread implements Runnable {
private int ticketNum = 10;
@Override
public void run() {
sale();
}
public synchronized void sale() {
for (int i = 0; i < 20; i++) {
if (ticketNum > 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",ticket = " + ticketNum--);
}
}
}
}
public class ThreadSync {
public static void main(String args[]) {
SalerThread saler = new SalerThread();
new Thread(saler, "窗口A").start();
new Thread(saler, "窗口B").start();
new Thread(saler, "窗口C").start();
}
}
同步代码块范例2执行结果
线程同步固然能够保证数据的完整性,使得线程可以一个一个依次进行业务逻辑的执行。但是,这也带来一个新的问题,如果几个线程互相等待,那程序岂不是不能继续执行了吗?A等待B执行完成,B等待C执行完成,C等待A执行完成。那么,此时程序就陷入了死锁。
多线程的死锁问题
数据的完整性必须使用同步,但是过多的同步会产生死锁!因此,不要动不动就加同步!