头图

第一次听到线程,可能是在接触淘宝后,双十一节日,国民纷纷抢购自己心仪的产品,让多数电商厂家赚的是盆满钵满,在我们欢快的浏览网页,一件下单的时候,后面的操作可能很少有人了解,为了保证每笔订单都能正确成交支付,商品下单成功,减掉库存,增加销量,分配仓库,物流地址,派送人员,以及时间等,一环接着一环,像是行驶在拉萨的火车一样,必须保证安全,快捷,所以这次我们就来聊聊关于下单背后,数据安全的那些事儿?

## 线程

线程,说的线程,我们先要了解一个和它相类似的概念。

进程;对就是计算机的进程,资源调度的最小单位,(下图展示的就是计算机软件运行的进程列表)
而线程CPU调用的最小单位

<进程的列表,微信线程的列表>

举个例子:

把计算机比作中国的铁路,其中每辆运行的火车就是一个进程(软件正在运行的状态),车厢就是一个个线程(其中有包含启动,存储的,数据转换等功能的),组成了可以持续工作的火车🚄;

线程和进程之间是如何工作的呢?

线程是小弟,进程那就是国王,进程是有多个有序逻辑的线程组成的抽象产物,用来保证进程功能的完整使用。

比如我们常用的微信聊天,其实微信的功能远不止聊天,其中打字输入(存储线程)小A, 给老大如何传递数据,不会被其他人所打扰呢

---
场景:你正在输入的时候,为什么其他的消息会给你发送接收到,但是丝毫不影响你正在输入的东西,可以接收到最新的,也可以保存最新的?

因为这个线程中小A,有超能力,当同一时间,小A拿到的信息,存储好之后要给别人发送的时候,保证只有自己能发送,不让别的线程来捣乱,(为了安全,拒绝别人,古代人民发明了🔐)---加锁。

就像是你每次上厕所锁门是一样的,因为自己需要进行一项专业工作,安全,防止别进行破坏,导致不安全的隐患,所以我们开始了如何加锁之路。

## 数据安全-- 加锁

在Java中,我们常见的锁有, 分布式锁,乐观锁与悲观锁,公平锁,自旋锁,以及Synchronized等同步锁机制,都是为了数据安全,但是每个锁的机制、应用范围、使用原理都不一样。

这期呢我们主要从数据可见性安全两个方面来讲讲怎么实现一个简单的线程安全的过程?

答案是:使用volatile

### 数据可见性

因为 volatile是线程底层用同步机制的一个准则,它有三个特性

volatile的三大法宝

#### 可见性
若是某一个线程改变了一个固定的变量,其他线程会立刻知晓;
也就是及时通知

及时的通知其他线程,主物理内存中的值已经被修改;

package com.atguigu;


import java.util.concurrent.TimeUnit;

class volatileTest1{


     //加volatile可以看见结果
    volatile int  number=0;//成员变量默认为0

    public  void addTo60(){
        //将number变成60
      this.number=60;

    }



}

public class volatileDemo {



    //如何理解这个volatile的保证可见性:
      /*
      *   可见性: 保证在线程使用的过程中,将数据修改后,及时的通知其他线程更新数据;
      *
      *     demo的设计原理:  (需要添加的是一个睡眠时间3-不然结果会出错误)
      *      我们运行nto60的方法--看main线程是否能获得已经变化了的数值number
      *       否则将循环下去
      * */
    public static void main(String[] args) {

        //1.资源类的初始化
        volatileTest1 volatileTest1=new volatileTest1();

            new Thread(()->{

                System.out.println(Thread.currentThread().getName()+":come in"+volatileTest1.number);

                //这里必须要睡3秒--不然的结果就是main线程也会同步,因为线程的运行速度太快啦
                try {
                    //休眠3秒钟
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                }
                //2.操作高内聚的方法nTo60
                volatileTest1.addTo60();

                 System.out.println(Thread.currentThread().getName()+":ture后的"+volatileTest1.number);

                },"web").start();

       //上述的web线程已经将数据变成60;


        //测试main线程是否感知到;
        while (volatileTest1.number==0){

            //就一直循环。什么都不会打印出来
        }


        System.out.println(Thread.currentThread().getName() +":"+volatileTest1.number);


         //表示main线程也知晓了number的变化---满足了volatile的可见性的需求

        /*
        *
        * web:come in0
            web:ture后的60
            main:60

        *
        * */

    }

}

不保证原子性:

原子性: 与mysql事务中的类似,不可分割,完整性
也即是某个线程在工作时,中间不可以加塞和分割,要么整体同时成功,要么失败;

比如在计算机常用的经典问题:

----
a++是否支持原子性

思路:详细的展示a++的运行过程,直至cpu调用的指令

分为三步;

①读取,从主物理内存中的a----》拷贝到本地的线程的工作内存;

②加一:

③写回主内存同步数据

不保证原子性就是--可能会在线程操作的过程中会有数据抢占;
随时可能会被打断;

a++的运行过程中指令

### 怎么解决volatile的不保证原子性情况:

①加synchronized的同步机制锁(太重啦)

 注意:atomicInteger的底层就是CAS(比较并交换)的;
 
 

②juc中的atomic包中的一个atomicInteger的类,方法是一个atomicInteger.getAndIncrement(); //每次加1-保证原子性的加1

下面是代码展示:

demo:

package com.atguigu;

import com.sun.org.apache.xpath.internal.operations.Variable;

import javax.lang.model.element.VariableElement;
import java.util.concurrent.atomic.AtomicInteger;

class VolatileTest2{
     //资源类

    volatile int a;  //全局变量的默认为0

     public  void addPlusPlus(){

         this.a++;

     }


     //解决的是volatile不保证原子性的AtomicInteger

    //AtomicInteger是 java.util.concurrent.atomic原子包写的类
      AtomicInteger atomicInteger=new AtomicInteger();

      public void addMyatomic(){

       atomicInteger.getAndIncrement(); //每次加1-保证原子性的加1

       /**
        * Atomically increments by one the current value.
        *原子性增加1
        * @return the previous value
        */
       /*public final int getAndIncrement() {
           return unsafe.getAndAddInt(this, valueOffset, 1);
       }*/
   }




 }



public class VolatileNoAtomic {


    //volatile 不保证原子性的小李子

    public static void main(String[] args) {


        //1.创建资源类的对象

        VolatileTest2 volatileTest2=new VolatileTest2();

          //2.创建线程-开始循环-

      for(int i=1;i<=30;i++) {

          new Thread(()->{

              for (int j = 0; j <100 ; j++) {

                  volatileTest2.addPlusPlus();
                  volatileTest2.addMyatomic();
              }


          },String.valueOf(i)).start();
      }


      //3.main线程是在a++之中有感知的,

        System.out.println(Thread.currentThread().getName()+"addPluePlus"+":"+volatileTest2.a);
                         //得出加过后的最后的值atomiceInteger
         System.out.println(Thread.currentThread().getName()+"atomicInteger"+":"+volatileTest2.atomicInteger);


                     /*   mainaddPluePlus:2797
                        mainatomicInteger:3000
                */

                     //底层就是CAS的; atomicInteger.getAndIncrement()
    }




}

禁止指令重排

禁止指令重排序(保证有序的执行),不会像A++那样去在指令转化给CPU的时候调换位置,

最终保持安全,原子性的同步机制volatile,可见性

一般在什么地方会用到volatile(可见性):面试的时候,

关于volatile的线程的细节,我整理了一个图用来理解,

高清版:Volatile关键的深入解析:

## 总结

线程安全,分为同步数据的加锁,和数据可见的原子操作,也就是当同一时间只允许一个线程去改主线程上的公共数据,(而且这个数据是最新的);关于数据可见性,和原子操作我们通过volatile了解了,那下一期我们来具体聊聊加锁后的线程是如何工作的。

我是卢卡,努力做一个不甘平庸的逆袭者,大家晚安了。


卢卡斯
14 声望0 粉丝

寻找生活答案的旅途者