zoukankan      html  css  js  c++  java
  • 多线程的原子性,可见性,排序性

    1、原子性(Atomicity)

    原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,
    要不就不执行。如果一个操作时原子性的,那么多线程并发的情况下,就不会出现变量被修改的情
    况.a++;
    这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安
    全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作
    ,那么我
    们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这
    些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

    2、可见性(Visibility)

    可见性就是指当一个线程修改了线程共享变量的值,其它线程能够立即得知这个修改.Java内存模型是
    通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介
    的方法来实现可见性的,无论是普通变量还是volatile变量都是如此,普通变量与volatile变量的
    区别是volatile的特殊规则保证了新值能立即同步到主内存以及每使用前立即从内存刷新。

    1.Java还有两个关键字能实现可见性,它们是synchronized。同步块的可见性是由“对一个变量执行
    unlock操作之前,必须先把此变量同步回主内存中(执行store和write操作)”这条规则获得的.


    2.而final关键字的可见性是指:被final修饰的字段是构造器一旦初始化完成,并且构造器没有把“
    this”引用传递出去,那么在其它线程中就能看见final字段的值。
    用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直
    接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰
    内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这
    个变量a具有可见性,但是a++ 依然是一个非原子操作,也就这这个操作同样存在线程安全问题。

    3、有序性(Ordering)

    Java内存模型中的程序天然有序性可以总结为一句话:如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。

    4、volatile关键字

     不能解决 原子性的原因 https://blog.csdn.net/xdzhouxin/article/details/81236356

    是一个关键字,用于修饰成员变量,主要用于解决可见性和有序性问题,但是无法解决原子性问题。
    强制将线程本地内存改变后的值刷新到主内存
    public class MyThread extends Thread {
        //保证所有线程每次使用a时都会强制更新
        public volatile static  int a = 0;
        @Override
        public void run() {
            System.out.println("线程启动,休息2秒...");
            try {
                Thread.sleep(1000 * 2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("将a的值改为1");
            a = 1;
            System.out.println("线程结束...");
        }
    }
    
    public class TestDemo {
        public static void main(String[] args) {
            //1.启动线程
            MyThread t = new MyThread();
            t.start();
            //2.主线程继续
            while (true) {
                if (MyThread.a == 1) {
                    System.out.println("主线程读到了a = 1");
                }
            }
        }
    }
    输出结果:
    线程启动,休息2秒...
    将a的值改为1
    线程结束...
    主线程读到了a = 1
    主线程读到了a = 1
    ...    

    5、synchronized关键字

    解决多句代码的原子性问题

    a.synchronized是什么??
    一个可以用于让多行代码保证原子性的关键字


    b.synchronized的作用??
    让多行代码"同步",当某个线程进入这多行代码执行时,其他线程是无法进入的,直到多行代码都运行完毕了,其他线程才能进入

    c.

    public synchronized void 方法名(){
      需要同步的代码(需要保证原子性的代码)
    }

    //原理和同步代码块基本是一致的,区别在于同步代码块的锁对象是我们指定的,而同步方法也需要锁对象,但是不需要我们指定
    //由编译器自己指定,指定当前对象this

    /**
     * 卖票任务
     */
    public class MyRunnable implements Runnable {
        /**
         * 票数
         */
        public int count = 100;
        @Override
        public void run() {
            while (true){
                if (count > 0) {
                    try {
                        Thread.sleep(30);//模拟卖票掏钱消耗的时间
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"卖出第"+count+"张票");
                    count--;
                }
            }
        }
    }
    
    public class TestDemo {
        public static void main(String[] args) {
            //0.创建任务
            MyRunnable mr = new MyRunnable();
            //1.创建窗口123
            Thread t1 = new Thread(mr);
            Thread t2 = new Thread(mr);
            Thread t3 = new Thread(mr);
            t1.start();
            t2.start();
            t3.start();
            //出现多线程安全问题:
            //a.出现了重复数据
            //b.出现了0,-1非法数据
        }
    }
    
    重复数据出现的原因:
        当某个线程卖出某张后,还没来得及对票数减1,被其他线程抢走CPU,导致其他线程也卖出同张票!!
    非法数据出现的原因:
        当只剩下最后一张票时,由于多线程的随机切换可能多个线程都会通过大于0的判断, 最终导致卖出的票是0,-1,-2....这些张数!!! 

    抢票案例解决方案

    格式:
    synchronized(任意对象){ //也叫做锁对象
            需要同步的代码(需要保证原子性操作的那些代码)
    }
    
    /**
     * 卖票任务
     */
    public class MyRunnable implements Runnable {
        /**
         * 票数
         */
        public int count = 100;
        /**
         * 创建一个对象
         */
        public Object obj = new Object();
        @Override
        public void run() {
            while (true){
                //同步代码块
                synchronized (obj) {//任意对象都可以作为锁对象
                    if (count > 0) {
                        System.out.println(Thread.currentThread().getName() + "卖出第" + count + "张票");
                        count--;
                    }
                }
            }
        }
    }
    6.Lock锁
    Lock实际上是一个接口,我们要使用它的实现类,ReentrantLock锁对象
    其中有两个方法:
    public void lock(); //获取锁 public void unlock(); //释放锁 格式: Lock lock = new ReentrantLock(); lock.lock();//加锁 需要同步的代码,需要保证原子性的代码 lock.unlock();//解锁
  • 相关阅读:
    由保存当前用户引发的springboot的测试方式postman/restlet还是swagger2
    VS集成opencv编译C++项目遇到的问题
    利用StringUtils可以避免空指针问题
    springboot集成Guava缓存
    Oracle 课程四之索引
    Oracle 课程三之表设计
    Oracle 课程二之Oracle数据库逻辑结构
    Oracle 课程一之Oracle体系结构
    Oracle权限一览表
    Informatica元数据库解析
  • 原文地址:https://www.cnblogs.com/xiaozhang666/p/13169265.html
Copyright © 2011-2022 走看看