JAVA多线程-什么是线程安全

2019-08-05  本文已影响0人  philcoulso_b627
什么是线程

线程是比进程更小的运行单位,它被包含在进程之中,是进程实际的运行单位,一个线程是指进程单一的控制流,一个进程可以并发多个线程,每条线程并行执行不同的任务。(来源百度)

Java中如何创建线程

Java中创建线程的方式有三种

public class threadSafe{
public void static main(String[] args){
 Thread thread=new Thread(new Runnable() { //第一种方式 直接new一个Runnable 实例
              @Override
              public void run() {
                  System.out.println("this is a thread");
              }
          });
          thread.start();
      mythread mythread=new mythread();
     Thread   thread2=new Thread(mythread);//通过创建mythread实例创建线程
}
}
class mythread implements Runnable{
  @Override
    public void run() {
        for(int i=0;i<10;i++){
            a++;
            System.out.println("running thread"+" a:"+a);
           }
    }
}

后两者有兴趣的同学可去菜鸟教程上学习,在这就不写了.
https://www.runoob.com/java/java-multithreading.html

什么是线程安全

一个类是线程安全的,是指被多个线程访问时,类可以持续进行正确的行为.
当多个线程访问一个对象时,如果我们不考虑线程在运行环境下的交替执行和调度,并且不需要额外的同步及在调用方代码不必做其它的协调,那么我们称这个类是线程安全的(来自java并发编程实战)
多线程中,程序的执行顺序我们是不知道的,比如什么时候执行这段代码


threadSafe.PNG

让我们看看实际代码中的输出

生产者

class  product implements Runnable{
    private int DEAFULT_CUSOTME_NUM=5;
    private Thread thread;
    private String ThreadName;
    private goods goods;
    /**
     * none constructor
     */
    product(){}

    /**
     *
     * @param ThreadName
     * Get threadName for this thread
     */
    product(String ThreadName,goods goods){
        this.ThreadName=ThreadName;
        this.goods=goods;
    }
    public void start(){
        if(thread==null){
            thread =new Thread(this,ThreadName);
            thread.start();
        }
    }
    @Override
    public void run() {
        SimpleDateFormat formatter=new SimpleDateFormat("yyyy/MM/dd hh:mm:ss:SSS");
            for(int i=0;i<5;i++){
                    System.out.println("thread name :" + thread.getName() + "consume goods:"
                            + DEAFULT_CUSOTME_NUM + "remain goods:" +
                            goods.consumeGoods(DEAFULT_CUSOTME_NUM) + "   current time"
                            + formatter.format(new Date()) + "\n i:" + i);
                    ; //default product five goods;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

    }
}

消费者

class  custome implements Runnable{
    private int DEAFULT_CUSOTME_NUM=5;  //default good num
    private Thread thread;
    private String ThreadName;
    private goods goods;

    /**
     * none constructor
     */
    custome(){}

    /**
     *
     * @param ThreadName
     * Get threadName for this thread
     */
    custome(String ThreadName,goods goods){
        this.ThreadName=ThreadName;
        this.goods=goods;
    }
    public void start(){
        if(thread==null){
            thread =new Thread(this,ThreadName);
        thread.start();
        }
        }
@Override
public void run() {
        SimpleDateFormat formatter=new SimpleDateFormat("yyyy/MM/dd hh:mm:ss:SSS");
        for(int i=0;i<5;i++){
        System.out.println("thread name :"+thread.getName()
        +"  product goods:"+DEAFULT_CUSOTME_NUM+"remain goods:"+
        goods.addGoods(DEAFULT_CUSOTME_NUM)+" current time"
        +formatter.format(new Date())+"\n i:"+i);  //default product five goods
 
        }
        }

main方法

public class threadSafe {
    static int DEFAULT_GOODS_NUM=0;
    public static void main(String[] args) {
         goods goods=new goods(DEFAULT_GOODS_NUM);
         custome custome2=new custome("product ",goods);
         product product1=new product("consume ",goods);
        custome custome1=new custome("product 1",goods);
        product product2=new product("consume 2",goods);
         custome2.start();
         product1.start();
         custome1.start();
         product2.start();
     }
}

我们来观察一下输出


threadSafeprint.PNG

由于我加了threadSleep,所以会按从0-5的顺序执行
虽然i:0的时候,输出顺序是0->10->5->5,结果是没有错误的,因为我们无法控制线程什么时候开始输出,比如执行操作的时候,线程一起执行,实际上读入变量顺序是这样的,produc->consumer 2-> consume->product 1
但是执行 i:1的时候,出现了问题,product 和product 1同时输出了5。说明这两个线程发生了像上面一样的图的情况,在product和product 1 执行前,先执行了consume方法,此时变量为0,当product和product1执行时,同时读入变量值0,执行addgood方法,得到了一样的结果。
这就出现了线程不安全的情况

如果将它变为线程安全

1.将goodnum设置为volatile.
为什么设置volatile有效呢,volatile的作用到底是什么
在当前java内存模型下,线程可以把变量保存到本地内存,而不是在主存中进行读写,这就可能造成一个线程在主存中修改了变量的值,而另一个线程还在使用它在寄存器中变量值的拷贝,造成数据的不一致


2.PNG

使用volatile声明变量,目的是告诉jvm,这个变量是不稳定的,每次使用它都应该去主存中读写


3.PNG
volatilel 声明的变量除了保证可见性,还能防止指令重排
2.在修改goodnum的方法中加入 synchronized
synchronized可以保证修饰的代码中,任何时刻只能有一个线程执行。
class goods {
    private  volatile int num;

    /**
     * none constructor
     */
    goods(){

    }
    /**
     *
     */
    goods(int num){
        this.num=num;
    }
    public void setNum(int num){
        this.num=num;
    }
    public synchronized  int getNum(){  //该代码块为同步代码块
        return this.num;
    }
    public  synchronized int addGoods(int num){
      return   this.num+=num;
    }

    public  int consumeGoods(int num){
        return  this.num-=num;
    }
}
上一篇 下一篇

猜你喜欢

热点阅读