《Java多线程编程核心技术(第2版)》 —1.2.8 实例变量共享造成的非线程安全问题与解决方案

举报
华章计算机 发表于 2020/02/08 13:47:51 2020/02/08
【摘要】 本节书摘来自华章计算机《Java多线程编程核心技术(第2版)》 一书中第1章,第1.2.8节,作者是高洪岩。

1.2.8 实例变量共享造成的非线程安全问题与解决方案

自定义线程类中的实例变量针对其他线程可以有共享与不共享之分,这在多个线程之间交互时是很重要的技术点。

1.不共享数据的情况

不共享数据的情况如图1-18所示。

 image.png

图1-18 不共享数据的情况

下面通过一个示例来看一下不共享数据的情况。

创建实验用的Java项目,名称为t3,MyThread.java类代码如下:

public class MyThread extends Thread {

    private int count = 5;

 

    public MyThread(String name) {

        super();

        this.setName(name);//设置线程名称

    }

 

    @Override

    public void run() {

        super.run();

        while (count > 0) {

            count--;

            System.out.println("由 " + this.currentThread().getName()

                    + " 计算,count=" + count);

        }

    }

}

运行类Run.java代码如下:

public class Run {

    public static void main(String[] args) {

        MyThread a=new MyThread("A");

        MyThread b=new MyThread("B");

        MyThread c=new MyThread("C");

        a.start();

        b.start();

        c.start();

    }

}

程序运行结果如图1-19所示。

由图1-19可以看到,该程序一共创建了3个线程,每个线程都有各自的count变量,自己减少自己的count变量的值,这样的情况就是变量不共享,此示例并不存在多个线程访问同一个实例变量的情况。

如果想实现3个线程共同去对一个count变量进行减法操作,则代码该如何设计呢?

2.共享数据的情况

共享数据的情况如图1-20所示。

 image.png

共享数据的情况就是多个线程可以访问同一个变量,例如,在实现投票功能的软件时,多个线程同时处理同一个人的票数。

下面通过一个示例来看下共享数据的情况。

创建t4测试项目,MyThread.java类代码如下:

public class MyThread extends Thread {

 

    private int count=5;

   

    @Override

    public void run() {

        super.run();

            count--;

            //此示例不要用while语句,会造成其他线程得不到运行的机会

            //因为第一个执行while语句的线程会将count值减到0

            //一直由一个线程进行减法运算

            System.out.println("由 "+this.currentThread().getName()+" 计算,count="+count);

    }

}

运行类Run.java代码如下:

public class Run {

    public static void main(String[] args) {

        MyThread mythread=new MyThread();

 

        Thread a=new Thread(mythread,"A");

        Thread b=new Thread(mythread,"B");

        Thread c=new Thread(mythread,"C");

        Thread d=new Thread(mythread,"D");

        Thread e=new Thread(mythread,"E");

        a.start();

        b.start();

        c.start();

        d.start();

        e.start();

    }

}

程序运行结果如图1-21所示。

从图1-21可以看到,A线程和B线程输出的count值都是3,说明A和B同时对count进行处理,产生了“非线程安全”问题。而我们想要得到输出的结果却不是重复的,应该是依次递减的。

出现非线程安全的情况是因为在某些JVM中,count--的操作要分解成如下3步,(执行这3个步骤的过程中会被其他线程所打断):

1)取得原有count值。

2)计算count-1。

3)对count进行赋值。

在这3个步骤中,如果有多个线程同时访问,那么很大概率出现非线程安全问题,得出重复值的步骤如图1-22所示。

 image.png

A线程和B线程对count执行减1计算后得出相同值4的过程如下。

1)在时间单位为1处,A线程取得count变量的值5。

2)在时间单位为2处,B线程取得count变量的值5。

3)在时间单位为3处,A线程执行count--计算,将计算后的值4存储到临时变量中。

4)在时间单位为4处,B线程执行count--计算,将计算后的值4也存储到临时变量中。

5)在时间单位为5处,A线程将临时变量中的值4赋值给count。

6)在时间单位为6处,B线程将临时变量中的值4也赋值给count。

7)最终结果就是A线程和B线程得到相同的计算结果4,非线程安全问题出现了。

其实这个示例就是典型的销售场景,5个销售员,每个销售员卖出一个货品后不可以得出相同的剩余数量,必须在每个销售员卖完一个货品后,其他销售员才可以在新的剩余物品数上继续减1操作,这时就需要使多个线程之间进行同步操作,即用按顺序排队的方式进行减1操作,更改代码如下:

public class MyThread extends Thread {

    private int count=5;

    @Override

    synchronized public void run() {

        super.run();

            count--;

            System.out.println("由 "+this.currentThread().getName()+" 计算,count="+count);

    }

}

重新运行程序后,便不会出现值一样的情况了,如图1-23所示。

image.png

通过在run()方法前加入synchronized关键字,使多个线程在执行run()方法时,以排队的方式进行处理。一个线程调用run()方法前,先判断run()方法有没有被上锁,如果run()方法被上锁,则说明其他线程正在调用run()方法,必须等其他线程对run()方法调用结束后,该线程才可以执行run()方法,这样也就实现了排队调用run()方法的目的,从而实现按顺序对count变量减1的效果。synchronized可以对任意对象及方法加锁,而加锁的这段代码称为“互斥区”或“临界区”。

当一个线程想要执行同步方法里面的代码时,线程会首先尝试去申请这把锁,如果能够申请到这把锁,那么这个线程就会执行synchronized里面的代码。如果不能申请到这把锁,那么这个线程就会不断尝试去申请这把锁,直到申请到为止,而且多个线程会同时去争抢这把锁。


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。