美文网首页
Java并发编程艺术-笔记之volatile与synchroni

Java并发编程艺术-笔记之volatile与synchroni

作者: 霍霍霍霍霍霍霍霍霍霍霍 | 来源:发表于2019-08-27 17:10 被阅读0次

    volatile的应用

    在多线程并发编程中synchronizedvolatile都扮演着重要的角色,volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程 修改一个共享变量时,另外一个线程能读到这个修改的值。如果volatile变量修饰符使用恰当 的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。

    volatile的定义与实现原理

    Java语言规范第3版中对volatile的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁要更加方便。如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。

    synchronized的实现原理与应用

    synchronized很多人都会称呼它为重量级锁。但是,随着Java SE 1.6对synchronized进行了各种优化之后,有些情况下它就并不那么重了。

    利用synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现 为以下3种形式。

    • 对于普通同步方法,锁是当前实例对象。
    • 对于静态同步方法,锁是当前类的Class对象。
    • 对于同步方法块,锁是synchonized括号里配置的对象。

    锁的升级与对比

    Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”“轻量级锁”在 Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状 态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高 获得锁和释放锁的效率

    锁的优缺点对比

    锁的优缺点对比.png

    Java如何实现原子操作

    在Java中可以通过锁和循环CAS的方式来实现原子操作。

    1. 使用循环CAS实现原子操作
    以下代码实现了一个基于CAS线程安全的计数器 方法safeCount和一个非线程安全的计数器count。

     private AtomicInteger atomicI = new AtomicInteger(0);
        private int i = 0;
    
        public static void main(String[] args) {
            final Counter cas = new Counter();
            List<Thread> ts = new ArrayList<Thread>(600);
            long start = System.currentTimeMillis();
            for (int j = 0; j < 100; j++) {
                Thread t = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 10000; i++) {
                            cas.count();
                            cas.safeCount();
                        }
                    }
                });
                ts.add(t);
            }
            for (Thread t : ts) {
                t.start();
            } // 等待所有线程执行完成 
            for (Thread t : ts) {
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(cas.i);
            System.out.println(cas.atomicI.get());
            System.out.println(System.currentTimeMillis() - start);
        }
    
        /**
         * 使用CAS实现线程安全计数器
         */
        private void safeCount() {
            for (; ; ) {
                int i = atomicI.get();
                boolean suc = atomicI.compareAndSet(i, ++i);
                if (suc) {
                    break;
                }
            }
        }
    
        /*** 非线程安全计数器
         */
        private void count() {
            i++;
        }
    

    从Java 1.5开始,JDK的并发包里提供了一些类来支持原子操作,如AtomicBoolean(用原子 方式更新的boolean值)AtomicInteger(用原子方式更新的int值)和AtomicLong(用原子方式更 新的long值)。这些原子包装类还提供了有用的工具方法,比如以原子的方式将当前值自增1 和 自减1。

    CAS实现原子操作的三大问题
    1)ABA问题。因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化 则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它 的值没有发生变化,但是实际上却变化了。

    2)循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。

    3)只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循 环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子 性,这个时候就可以用锁。

    2.使用锁机制实现原子操作
    锁机制保证了只有获得锁的线程才能够操作锁定的内存区域。JVM内部实现了很多种锁机制,有偏向锁、轻量级锁和互斥锁。除了偏向锁,JVM实现锁的方式都用了循环 CAS,即当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时候使用循环CAS释放锁。

    相关文章

      网友评论

          本文标题:Java并发编程艺术-笔记之volatile与synchroni

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