美文网首页
JAVA多线程-什么是线程安全

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

作者: philcoulso_b627 | 来源:发表于2019-08-05 21:22 被阅读0次
    什么是线程

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

    • 并发:同一时间段内,多个任务都在执行(单位时间内不一定同时执行)
      -并行:单位时间内,多个任务同时执行
    Java中如何创建线程

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

    • 通过implement Runable 接口
    • 通过extents Thread 类
    • 通过Callable和Future创建线程
      1.implement Runnable方式
    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;
        }
    }
    

    相关文章

      网友评论

          本文标题:JAVA多线程-什么是线程安全

          本文链接:https://www.haomeiwen.com/subject/jmdsdctx.html