Java多线程安全问题——同步机制

Java的多线程会有什么安全问题呢?
我们来看下面一个案例:
有一个景点总共有100张门票,有三个窗口同时在卖票。
我们用程序来模拟一下。

class Window implements Runnable{
    private  int ticket = 100; //总票数100张,三个窗口共用,称为共享数据
    @Override
    public void run() {
        while (true){
            if(ticket > 0){
                //Thread.currentThread().getName() 获取当前线程的名字
                System.out.println(Thread.currentThread().getName()+ ":卖票,票号为:" + ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}

public class WindowTest {
    public static void main(String[] args) {
        Window window = new Window();

        Thread window1 = new Thread(window);
        Thread window2 = new Thread(window);
        Thread window3 = new Thread(window);

        window1.setName("窗口1"); //setName()设置线程的名字
        window2.setName("窗口2");
        window3.setName("窗口3");

        window1.start();
        window2.start();
        window3.start();

    }

}

看起来好像就是这样,创建三个多线程模拟三个窗口,然后卖票,票是共享数据,三个窗口共用,没有什么问题。
可是实际上呢?我们一运行就会发现问题了。

如图上所看到的,出现了重票!
三个窗口都卖了张票号 为100 的票。
这是怎么回事呢?
我们用张图来说明:

这就是线程的安全问题。
也就是在一个线程执行操作还未完成时,另一个线程就进来了。
相信大家都能想到解决的办法那就是 在一个线程执行操作还未完成时,阻止其他线程的去执行操作。


在Java中,有三种方法实现这个办法。

方式一:同步代码块

synchronized(同步监视器/锁){
    //需要被同步的代码
    //不能包含多了 或 少了
}

1.同步监视器,也称为 锁 :任何一个类的的对象,都可以充当锁。
2.需要被同步的代码:指 操作共享数据的代码 (共享数据,多个线程共同操作的变量,比如 多个窗口卖的 票)
看起来似乎很简单,我们来试试

没有重票了!也没有错票了!



就这么简单,你学会了吗?
 

 

方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,可以直接将此方法声明为同步的
例如:

注意:1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
          2. 非静态的同步方法,同步监视器是:this
              静态的同步方法,同步监视器是:当前类本身


方式三:Lock(锁) ——JDK5.0新增
 


class Window implements Runnable{

    private int ticket = 100;

    //1.实例化
    private ReentrantLock lock = new ReentrantLock();


    @Override
    public void run() {
        while (true){
            try {
                //2.调用锁定方法lock()
                lock.lock();
                if (ticket > 0){
                    System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
                    ticket--;
                }else{
                    break;
                }
            }finally {
                //3.调用解锁的方法
                lock.unlock();

            }
        }
    }
}

public class  LockTest {
    public static void main(String[] args) {
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}


注意点:

  1. 使用 同步代码块 和 同步方法 时,一定确保,多个线程共用一把锁
  2. 实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
    继承Thread类创建多线程的方式中,慎用this充当同步监视器考虑使用当前类充当同步监视器

同步方式的利弊:利,解决了线程的安全问题。
                             弊,降低了效率。(操作同步代码时,只能一个线程操作,其他线程等待)

synchronized 与 Lock的异同
相同:都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
          Lock需要手动的启动同步(lock(),同时结束同步也需要手动的实现(unlock())

 

 

 

Java多线程的其他内容,将在后续文章中更新。

 

全部评论

相关推荐

Hello_WordN:咱就是说,除了生命其他都是小事,希望面试官平安,希望各位平时也多注意安全
点赞 评论 收藏
分享
点赞 1 评论
分享
牛客网
牛客企业服务