天天看点

java线程原子锁

今天被问到这样一个问题,两个线程共用一个int类型的变量,怎么保证数据安全?(不可以用synchronized关键字)你知道原子锁吗?

1. 例子引入

public class RunThread extends Thread {

    private boolean isRunning = true;

    public boolean isRunning() {
        return isRunning;
    }

    public void setRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }

    @Override
    public void run() {
        System.out.println("进入到run方法中了");
        while (isRunning == true) {
        }
        System.out.println("线程执行完成了");
    }
}

public class Run {
    public static void main(String[] args) {
        try {
            RunThread thread = new RunThread();
            thread.start();
            Thread.sleep();
            thread.setRunning(false);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
           

现在有两个线程,一个是main线程,另一个是RunThread。它们都试图修改 第三行的 isRunning变量。按照JVM内存模型,main线程将isRunning读取到本地线程内存空间,修改后,再刷新回主内存。

而在JVM 设置成 -server模式运行程序时,线程会一直在私有堆栈中读取isRunning变量。因此,RunThread线程无法读到main线程改变的isRunning变量从而出现了死循环,导致RunThread无法终止。

解决方法,在第三行代码处用 volatile 关键字修饰即可。这里,它强制线程从主内存中取 volatile修饰的变量:

volatile private boolean isRunning = true;
           

2. volatile

所谓原子性,就是某系列的操作步骤要么全部执行,要么都不执行。

volatile就是非原子性的

比如,变量的自增操作 i++,分三个步骤:

①从内存中读取出变量 i 的值

②将 i 的值加1

③将 加1 后的值写回内存

这说明 i++ 并不是一个原子操作。因为,它分成了三步,有可能当某个线程执行到了第②时被中断了,那么就意味着只执行了其中的两个步骤,没有全部执行。

所以仅仅使用volatile关键字并不能保证的安全。

3. AtomicInteger

这个类的源码:

public final int getAndSet(int newValue) {
        for (;;) {
            int current = get();
            if (compareAndSet(current, newValue))
                return current;
        }
    }

    /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return true if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
           

这段代码写的很巧妙:

compareAndSet方法首先判断当前值是否等于current;

如果当前值 = current ,说明AtomicInteger的值没有被其他线程修改;

如果当前值 != current,说明AtomicInteger的值被其他线程修改了,这时会再次进入循环重新比较;

使用:

AtomicInteger atomicInteger = new AtomicInteger(1);

4、java提供的原子操作

  • 原子更新数组,Atomic包提供了以下几个类:

    AtomicIntegerArray

    AtomicLongArray

    AtomicReferenceArray

  • 原子更新引用类型,也就是更新实体类的值,比如

    AtomicReference:原子更新引用类型的值

    AtomicReferenceFieldUpdater:原子更新引用类型里的字段

    AtomicMarkableReference:原子更新带有标记位的引用类型

  • 原子更新字段值

    AtomicIntegerFieldUpdater:原子更新整形的字段的更新器

    AtomicLongFieldUpdater:原子更新长整形的字段的更新器

    AtomicStampedReference:原子更新带有版本号的引用类型的更新器

5、应用

Java原子操作类 AtomicInteger 在实际项目中的应用。HttpClientFacotryBean工厂会工作在多线程环境中,生成Httpclient,

就相当于建立HttpClient连接,通过工厂模式控制HttpClient连接,能够更好的管理HttpClient的生命周期。而我们使用java原子

操作类AtomicInteger来控制计数器,就是为了保证,在多线程的环境下,建立HttpClient连接不会出错,不会出现2个线程竞争一个

HttpClient连接的情况。

看到一篇说线程的文章,觉得不错,分享一下:

http://www.jianshu.com/p/40d4c7aebd66