线程进阶(以解决线程安全问题为主)、volatile的底层实现

线程:以解决线程安全问题为主

进程:运行时程序,操作系统分配内存资源的最小单位。

线程 :进程内部最小执行单元。

多线程的优点:提高程序响应速度,可以多个线程各自完成自己的工作,提高设备利用率。

缺点:在多个线程同时访问共享数据,可能会出现资源共享问题。

并发执行:在一个时间段内对多个线程依次执行

并行执行:是真正意义上同时执行,两个线程在同一时间节点上一起执行

并发编程的核心问题:

1,不可见性:一个线程对共享变量修改,另一个线程不能立刻看到,称不可见性。(缓存不能及时刷新导致)
public class Demo {
    public static void main(String[] args) {
        RunTask runTask=new RunTask();//创建任务对象
        Thread thread=new Thread(runTask);//创建线程
        thread.start();//启动线程

        while(true){
            if(!runTask.isFlag()) {
                System.out.println("main:"+runTask.isFlag());
                break;
            }
        }
    }
}
public class RunTask implements Runnable{
   private boolean flag=false;
    @Override
    public void run() {
        flag=true;//设置此时flag为true
        System.out.println(flag);
    }

    public  boolean isFlag() {
        return flag;
    }

    public  void setFlag(boolean flag) {
        this.flag = flag;
    }
}

 线程进阶(以解决线程安全问题为主)、volatile的底层实现_第1张图片

先启动了一个线程任务,设置的flag为true。但是在main线程取出flag为false。发现读取的数据不一致,说明了线程的不可见性。我们该如何避免这种,在一个线程修改后其他线程可以立刻看见修改后的数据呢?----------添加关键字volatile

线程进阶(以解决线程安全问题为主)、volatile的底层实现_第2张图片


public class RunTask implements Runnable{
   private volatile boolean flag=false;
    @Override
    public void run() {
        flag=true;//设置此时flag为true
        System.out.println(flag);
    }

    public  boolean isFlag() {
        return flag;
    }

    public  void setFlag(boolean flag) {
        this.flag = flag;
    }
}

为要修改的变量添加修饰volatile发现可以使改变的变量立即被看见。

 volatile修饰的变量在一个线程中被修改后,对其他线程立即可见。并且禁止指令重排。

volatile的底层实现

 该关键字可以确保对一个变量的更新对其他线程马上可见。当一个变量被声明为 volatile 时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。当其他线程读取该共享变量时,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。volatile 的内存语义和synchronized有相似之处,具体来说就是,当线程写入了 volatile变量值时就等价于线程退出synchronized同步块(把写入工作内存的变量值同步到主内存),读取 volatile 变量值时就相当于进入同步块(先清空本地内存变量值,再从主内存获取最新值)。

 volatile 虽然提供了可见性保证,但并不保证操作的原子性。


那么一般在什么时候才使用 volatile关键字呢?
①写入变量值不依赖变量的当前值时。因为如果依赖当前值,将是获取一计算一写入三步操作,这三步操作不是原子性的,而 volatile 不保证原子性。
②读写变量值时没有加锁。因为加锁本身已经保证了内存可见性,这时候不需要把变量声明为volatile的。

2,乱序性:指令在执行过程中改变顺序,可能会影响程序运行结果

为了优化性能,有时候会改变程序中语句的先后顺序。

public class Test {
    static int a = 0, b = 0, x = 0, y = 0;

    public static void main(String[] args) {
        while (true) {
            x=0;
            y=0;
            a=0;
            b=0;
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    a = 1;
                    x = b;
                }
            });
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    b = 1;
                    y = a;
                }
            });
            t1.start();
            t2.start();
            try {
                t1.join();
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (x == 0 && y == 0) {
                System.err.println(x + "---" + y);
                break;
            }else{
                System.out.println(x+" "+y);
            }
        }
    }
}

每次执行每个线程的count值都不同。由于三个线程是并发执行,所以不确定他们的执行顺序,这就是并发的乱序问题。

解决办法:

1,上锁

    public void increment() {
        synchronized (obj) {
            count++; // 这是一个原子操作,不可中断
            System.out.println("Thread " + Thread.currentThread().getId() + " incremented count to " + count);
        }
    }

2, volatile

3,非原子性:线程切换带来的非原子性问题

A线程执行时,被切换到B线程。

使用Javap-c命令查看汇编代码,如下所示。
        public void inc();
        Code:
                0:aload0
                1:dup
                2:getfield
                5:Iconst 1
                6:ladd
                7:putfield10:return
由此可见,简单的++value 由2、5、6、7四步组成,其中第2步是获取当前 value的值并放入栈顶,第5步把常量1放入栈顶,第6步把当前栈顶中两个值相加并把结果放入栈顶,第7步则把栈顶的结果赋给 value变量。因此,Java中简单的一句+value 被转换为汇编后就不具有原子性了。 

解决方案:

1,使用synchronized关键字

2,CAS机制:不加锁的机制

public class Demo {
    private volatile  int count = 0;
    public static void main(String[] args) {
        Demo demo = new Demo();
        // 创建并启动三个线程,它们都会访问demo对象的increment()方法
        Thread t1 = new Thread(() -> {
            demo.increment();
        });
        Thread t2 = new Thread(() -> {
            demo.increment();
        });
        Thread t3 = new Thread(() -> {
            demo.increment();
        });

        t1.start();
        t2.start();
        t3.start();

        try {
            t1.join();
            t2.join();
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 输出最终的count值,由于并发中的乱序问题,这个值可能会与预期不符
        System.out.println("Final count: " + demo.count);
    }

    public void increment() {
            System.out.println("Thread " + Thread.currentThread().getId() + " incremented count to " + ++count);
    }
}

线程进阶(以解决线程安全问题为主)、volatile的底层实现_第3张图片 

你可能感兴趣的:(JAVA进阶,java,开发语言,运维)