java基础--线程的安全问题(卖票问题)以及解决(同步机制)
程序员文章站
2022-05-05 23:13:20
...
问题
卖票问题:
假设有100张车票,分成三个窗口来进行买票。用实现Runnable 的方式来进行创建多线程。(假如用继承类的方式,需要将ticket 设置为static)。
package Test;
class Windows implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":" + "卖票票号:" + ticket);
ticket--;
} else {
break;
}
}
}
}
public class Test {
public static void main(String[] args) {
Windows w = new Windows();
Thread thread1 = new Thread(w);
Thread thread2 = new Thread(w);
Thread thread3 = new Thread(w);
thread1.setName("窗口1");
thread2.setName("窗口2");
thread3.setName("窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
让我们看看运行结果,即买票的情况。会发现票号100分别被三个窗口卖了三次,出现安全问题。
问题出现的原因:
当一个线程操作车票时,尚未完成操作(未完成–ticket),但是其他线程就参与进来,也操作车票。这样就会造成同一张车票重复买票。
如何解决:
当一个线程A在操作ticket的时候,其他线程不能参与进来,直到线程A操作完成ticket时,其他线程才开始操作ticket。这种情况下,即使A发生阻塞,也不能改变。
解决方式
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
//说明:操作共享数据的代码,即为需要被同步的代码
//共享数据:多个线程操作的变量;比如 :ticket
//同步监视器,俗称,锁。任何一个类的对象,都可以充当锁。
//要求:多个线程必须要共用一把锁。
/*同步的方式:解决了线程的安全问题。---好处
*操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低。---局限性
*/
对windows中的内容进行修改,增加synchronized代码块。
private int ticket = 100;
Object o = new Object();
@Override
public void run() {
while(true) {
synchronized (o) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":" + "卖票票号:" + ticket);
ticket--;
} else {
break;
}
}
}
}
此时就解决了问题。
补充:
- 在实现Runnable接口来创建多线程的方式中,我们可以考虑使用 this 来充当同步监视器。
- 在继承Thread类来常见线程的方式中,我们可以考虑使用 当前类 (类.class)来重当同步监视器。
方式二:同步方法
/**
*同步方法仍然涉及到同步监视器,只是不需要我们显示的声明。
*非静态的同步方法,同步监视器是:this
* 静态的同步方法,同步监视器是:当前类本身
**/
class Window extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized static void show() {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":" + "买票" + "票号" + ticket);
ticket--;
}
}
}
测试
public class WindowsTest {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
结果输出正确。
方式三:lock
class Windows2 implements Runnable{
private int ticket = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":" + "卖票票号:" + ticket);
ticket--;
} else {
break;
}
} finally{
lock.unlock();
}
}
}
}
synchronized 和lock 的异同
- 相同 :二者都可以解决线程安全问题
- 不相同:synchronized机制 在执行完全响应的同步代码之后,自动释放同步监视器。
- 不相同:lock需要手动的启动同步(lock()),同时结束也需要手动的实现(unlock())。