深入理解java虚拟机(7)---线程安全 & 锁优化

关于线程安全的话题,足可以使用一本书来讲解这些东西。<Java Concurrency in Practice> 就是讲解这些的,在这里

主要还是分析JVM中关于线程安全这块的内容。

1.线程安全是什么?

线程安全,有经验的开发人员都听过这个名词,但是能否给到一个准确的定义,很难。

在 Java Concurrency in Practice里面定义是:

当多个线程访问一个对象时,如果不用考虑这些线程在运行时的环境下的调度和交替执行,

也不需要进行额外的同步,或者调用其他协作,这个情况下,线程就是安全的。

java中的线程安全可以定义5个级别:

1)不可改变。

也就是final修饰的词。

public class ThreadSafeType {


    class ThreadContext{
        public  int id = -1;
    }

    public void doSomeThingBackground(ThreadContext context)
    {
        new Thread(){
            @Override
            public void run(){
                context.id++;
            }
        }.start();
    }
}

上面标红的,会再android studio里面提示错误:

从内部类中访问本地变量context; 需要被声明为最终类型。

也就是说在

public void doSomeThingBackground(final ThreadContext context)

要改成上面的类型,这就是线程安全的考虑。

2)绝对安全

绝对安全其实很难描述,比如Vector是安全的。但是在多线程的情况下,它也是不安全的。

3)相对安全

相对安全其实就是我们一般意义上的线程安全。

它需要保证对这个对象的单独操作是安全的。但是对于特定的顺序,需要一些方法保证线程安全。

4)线程兼容

这就是我们常见的情况,需要使用synchronized等手段来保证线程安全。

5)线程对立

比较极端的情况,就是无论怎么加锁,代码无法并发运行。一种情况就是死锁。

2.如何实现线程安全

1)互斥同步

保持共享数据在同一时刻只被一个线程使用。

互斥是手段,同步是目的。

在java中最常见的就是synchronized方法。

synchronized标记的代码,会生成monitorenter & monitorexit  2段代码。

这是java编译器自动生成的,不会有遗漏。使用其他锁,lock & unlock成对出现,但是

开发者有时候会容易疏忽这个操作,尤其在catch代码里面忘记调用unlock,将是一个隐患。

java.util.concurrent 下面有不少同步的方法。ReentrantLock也是一个可以的方法,在1.5以前,性能

远由于synchronized。但是在1.6, java还是把synchronized做了很大的提升。原因就是synchronized使用的

代码已经远远大于ReentrantLock,并且引入ReentrantLock,可能会令需要开发者混淆。所以ReentrantLock可以认为是

一道开胃小菜而已。

2)非阻塞同步

互斥同步是一种阻塞同步,但是有些情况下,我们不需要互斥,只要能够同步就可以。

java.util.concurrent.atomic.AtomicInteger

就是这样一个自增的方法。

    /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }

源码里面没有 互斥的操作,就是一直在循环,知道+1满足就退出。

者就是非阻塞同步。

3)无同步

同步只是保证共享数据的手段,如果2个线程没有共享数据,也就不需要同步。

3.锁优化

1)自旋锁

自旋锁有时候会白白的耗用处理器的资源,但是没有任何实际效果。

2)锁消除

如果代码不可能存在共享数据需要同步,编译器就会把锁拿掉

3)锁粗化

原则上锁的互斥模块尽可能的小,但是如果对于同一对象,反复的lock & unlock 尤其是循环体中。

会带来很大的性能损失。

 

参考:

《深入理解Java虚拟机》周志明

《Java Concurrency in Pratice》

 

点赞