Java基础之线程安全

回顾

在上一篇 Java基础之多线程编程,我们讲解了多线程的实现,运行起来似乎也没什么问题,但是我们若加一段代码

class Window implements Runnable{//实现接口
    int ticket=100;
    @Override
    public void run() {
        while (true){
            if(ticket>0){
                try {
                    Thread.currentThread().sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"售票:票号为:"+ ticket--);
            }else{
                break;
            }
        }
    }
}
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");
        t1.start();
        t2.start();
复制代码

相比与上一篇我们的代码里多了

Thread.currentThread().sleep(10);
复制代码

sleep()方法使得当前线程阻塞10毫秒,我们看代码运行效果

票卖超了,怎么回事?按代码逻辑来看,好像并没什么问题?

这就是存在 线程不安全

问题分析

那么我们分析下如何出现的这个现象? 我们假设有两个卖票线程:线程A和线程B ,此时余票还有1张,看下图

文字解释下,当ticket=1时,线程A进入if判断内,接着线程A进入sleep状态,紧接着线程B获得cpu执行权开始执行, 此时ticket=1,进入if判断内 也开始sleep,然后线程A的sleep结束 恢复,开始执行,并把ticket--,此时ticket=0,然后线程B恢复, 打印tickect为0,ticket再次-1.变成了-1. 这就时三个窗口同时卖票,票卖超的原因,也称线程不安全。

线程安全

上面我们分析了导致线程不安全出现的原因?那怎么解决呢?

  • 我们希望一个线程操作共享数据结束以后,其他的线程才有机会参与共享数据的操作。

线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

用java代码来实现,主要有两种方法:

线程的同步机制

方法一 :同步代码块

格式如下 使用synchronized关键字

synchronized(同步监视器){
    //需要被同步的代码块(操作共享数据的代码块)
}
复制代码

以上同步监视器 又称为“锁”,锁需要唯一,代码如下

class Window implements Runnable{//实现接口
    int ticket=100;
    @Override
    public void run() {
        while (true){
            synchronized (this){
                if(ticket>0){
                    try {
                        Thread.currentThread().sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"售票:票号为:"+ ticket--);
                }
            }
        }
    }
}
复制代码

执行后如下,正常!

方法二 :同步方法

将操作共享数据的代码 提取到一个方法内 然后用synchronized 修饰

synchronized void show(){
    //操作共享数据的代码
}
复制代码

修改卖票程序用同步方法实现如下:

class Window implements Runnable{//实现接口
    int ticket=100;
    public synchronized void show(){
        if(ticket>0){
            try {
                Thread.currentThread().sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"售票:票号为:"+ ticket--);
        }
    }
    @Override
    public  void run() {
        while (true){
            this.show();
        }
    }
}

复制代码

注意在同步方法实现中 锁默认为this 也需要唯一, 我们用图解释下线程安全下两个线程如何操作共享数据的:

由上图我们知道,一旦遇到操作共享数据时,线程总是同步执行的。

总结

  • 遇到多个线程操作共享数据时就会出现线程不安全问题
  • 同步方法或者同步代码块 都是为了让线程同步执行
  • 同步方法和同步代码块都需要一个对象 作为锁,这个锁要确保唯一性

喜欢本文的朋友们,欢迎长按下图关注订阅号"我的编程笔记",收看更多精彩内容~~

猜你喜欢

转载自juejin.im/post/5c3d541bf265da61380f7136
今日推荐