Java锁的几种应用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SonOfWind0311/article/details/79105674

Java锁有如下概念:

volatile
synchronized
ReentrantLock

在Java理论与实践系列中对此有过介绍分析:

正确使用 Volatile 变量
JDK 5.0 中更灵活、更具可伸缩性的锁定机制

在此对其中的概念再做一些深入的分析与解释:

1. volatile

提到volatile,很多地方都提及到的是volatile只能保证“可见性”无法保证“原子性”,首先的问题就是什么是可见性,为什么会存在可见性问题。其次通过几个例子说明一下可见性和原子性的区别。

可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。引起可见性问题的主要原因是每个线程拥有自己的一个高速缓存区——线程工作内存。深入的原理分析可以参考如下两篇文章:

深入分析Volatile的实现原理
volatile的使用及其原理

通过如下代码可以验证一下volatile不能保证原子性:

public class VolatileTest {

  public static void main(String[] args) {
    int count = 1000;
    Test test = new Test();

    for (int i = 0; i < count; i++) {
      new Thread(new Runnable() {
        @Override
        public void run() {
          try {
            Thread.sleep(10);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }

          test.increment();
        }
      }).start();
    }

    try {
      Thread.sleep(5000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    System.out.println("count is " + test.getCount());
  }

  private static class Test {
    private volatile int count = 0;

    void increment() {
      count++;
    }

    int getCount() {
      return count;
    }
  }
}

如果volatile能够保证原子性的话,输出结果应该是1000,但实际上输出结果不确定,可能是992,982。

2. synchronized

还是上面的例子,略作改动,去掉volatile, increment方法定义为synchronized,则可以保证操作原子性,每次输出结果都是1000。

  private static class Test {
    private int count = 0;

    synchronized void increment() {
      count++;
    }

    int getCount() {
      return count;
    }
  }

关键字有如下用法修饰的对象有如下几种:

修饰一个代码块

public void run() {
    synchronized(this) {
      // code block here
    }
}
private Object lock;
public void run() {
    synchronized(lock) {
      // code block here
    }
}


修饰一个方法

public synchronized void run() {
  // code block here
}


修饰一个静态方法

public synchronized static void run() {
  // code block here
}


修饰一个类

public class Test {
    public void run() {
        synchronized(Test.class) {
          // code block here
        }
    }
}

synchronized的本质是获取对象锁,因此关键有两点:

锁定的对象,锁定的范围

针对上述几种情况:

修饰一个代码块:锁定的对象是synchronized(xxx)所指定的xxx对象,锁定的范围是{}所包含的代码块
修饰一个方法:锁定的对象是调用该方法的类实例,锁定的范围是该方法
修饰一个静态方法:锁定的对象是该类,即xxx.class对象,锁定的范围是该方法
修饰一个类:实际上是修饰代码块的特例,对象是某个类,即xxx.class

3. ReentrantLock

synchronized 关键字的特点是编码简单,不需要考虑加锁/释放的问题,缺点是不能应对复杂的应用场景,效率不高。为此Java提供了ReentrantLock类,详细介绍分析可以参考:

JDK 5.0 中更灵活、更具可伸缩性的锁定机制

4. 其他

此外还有一些比较有用的包装可以供我们选择,不需要重新造轮子:

BlockingQueue:适用于典型的生产者/消费者模式

猜你喜欢

转载自blog.csdn.net/SonOfWind0311/article/details/79105674