欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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分别被三个窗口卖了三次,出现安全问题。
java基础--线程的安全问题(卖票问题)以及解决(同步机制)
问题出现的原因
  当一个线程操作车票时,尚未完成操作(未完成–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)来重当同步监视器。

java基础--线程的安全问题(卖票问题)以及解决(同步机制)
方式二:同步方法

/**
*同步方法仍然涉及到同步监视器,只是不需要我们显示的声明。
*非静态的同步方法,同步监视器是: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())。