zoukankan      html  css  js  c++  java
  • Java显式锁学习总结之一:概论

      我们都知道在java中,当多个线程需要并发访问共享资源时需要使用同步,我们经常使用的同步方式就是synchronized关键字,事实上,在jdk1.5之前,只有synchronized一种同步方式。而在jdk1.5中提供了一种新的同步方式--显示锁(Lock)。显示锁是随java.util.concurrent包一起发布的,java.util.concurrent包是并发大神Doug Lea写的一个并发工具包,里面除了显示锁,还有许多其他的实用并发工具类。

    什么是显示锁

      什么是显示锁?用一段代码来说明:

    package com.gome;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class LockPractice {
        private static int a=0;
        private static Lock lock=new ReentrantLock();
        
        public static void increateBySynchronized(){
            a=0;
            for (int i = 0; i < 1000; i++) {
                Thread t=new Thread(new Runnable() {
                    public void run() {
                        for (int j = 0; j < 100; j++) {
                            synchronized (LockPractice.class) {
                                a++;
                            }
                        }
                    }
                });
                t.start();
            }
            while (Thread.activeCount()>1) {
                Thread.yield();
            }
            System.out.println(a);
        }
        
        public static void increateByLock(){
            a=0;
            for (int i = 0; i < 1000; i++) {
                Thread t=new Thread(new Runnable() {
                    public void run() {
                        for (int j = 0; j < 100; j++) {
                            lock.lock();
                            try {
                                a++;
                            } finally {
                                lock.unlock();
                            }
                        }
                    }
                });
                t.start();
            }
            while (Thread.activeCount()>1) {
                Thread.yield();
            }
            System.out.println(a);
        }
        
        public static void main(String[] args) {
            increateBySynchronized();
            increateByLock();
        }
    }

    执行结果:

    解释:

    类LockPractice 中有两个方法increateBySynchronized()和increateByLock(),这两个方法都成功地用多线程并发将a累加到100000而没有出现竞态条件(race condition)问题。

    其中increateBySynchronized()的同步是我们熟悉的synchronized关键字实现的:

    synchronized (LockPractice.class) {
      a++;
    }

    这句代码的含义是:当有一个线程A进入synchronized代码块后,阻塞其他要进入该代码块的线程直到A执行完代码块。synchronized关键字会关联一个锁对象,这里是LockPractice.class。synchronized关键字底层是由jvm来实现的,当一个线程进入synchronized块时,会在关联的锁对象的对象头(MarkWord)中记录下线程信息(可以简单的理解为线程id),这样这个锁对象就被当前线程独占了,其他试图获取这个锁对象的线程将被阻塞。

    因此一个线程进入、退出synchronized代码块的本质就是这个线程对锁对象的获取、释放。

    而increateByLock()的同步代码如下,其中 lock是全局变量 private static Lock lock=new ReentrantLock();

    lock.lock();
    try {
        a++;
    } finally {
        lock.unlock();
    }

    从代码上可以看出,显示锁Lock的使用和synchronized的本质很像,也是定义了一个锁对象(new ReentrantLock()),然后在进入同步代码前加锁,执行同步代码后释放锁。

    但是显示锁的底层却和synchronized完全不同,并没有使用到对象头(MarkWord)这样底层的东西,显示锁只是表现出了和synchronized一样的行为(第一个访问同步代码的线程获得锁,阻塞后来的线程)。

    显示锁的优点

    从上面的描述来看,显示锁实现了和synchronized一样的功能,但是写起来更复杂(需要手动加锁解锁,还需要写finally防止发生异常后锁不能释放),那为什么还要加入显示锁呢?

    我们可以从Lock接口提供的方法看出端倪:

    方法名称 描述
    void lock() 获取锁
    void lockInterruptibly() throws InterruptedException 可中断地获取锁,在线程获取锁的过程中可以响应中断
    boolean tryLock()   尝试非阻塞获取锁,调用方法后立即返回,成功返回true,失败返回false
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException 在超时时间内获取锁,到达超时时间将返回false,也可以响应中断
    void unlock(); 释放锁
    Condition newCondition(); 获取等待组件,等待组件实现类似于Object.wait()方法的功能

    从Lock提供的接口可以看出来,显示锁至少比synchronized多了以下功能:

    1. 可中断获取锁:使用synchronized关键字获取锁的时候,如果线程没有获取到被阻塞了,那么这个时候该线程是不响应中断(interrupt)的,而使用Lock.lockInterruptibly()获取锁时被中断,线程将抛出中断异常。
    2. 可非阻塞获取锁:使用sync关键字获取锁时,如果没有成功获取,只有被阻塞,而使用Lock.tryLock()获取锁时,如果没有获取成功也不会阻塞而是直接返回false。
    3. 可限定获取锁的超时时间:使用Lock.tryLock(long time, TimeUnit unit)。
    4. 其实显示锁还有其他的优势,比如同一锁对象上可以有多个等待队列(相当于Object.wait()),我们后面会讲。

    其实除了更多的功能,显示锁还有一个很大的优势:synchronized的同步是jvm底层实现的,对一般程序员来说程序遇到出乎意料的行为的时候,除了查官方文档几乎没有别的办法;而显示锁除了个别操作用了底层的Unsafe类之外,几乎都是用java语言实现的,我们可以通过学习显示锁的源码,来更加得心应手的使用显示锁。

    显示锁的缺点 

    当然显示锁也不是完美的,否则java就不会保留着synchronized关键字了,显示锁的缺点主要有两个:

    1. 使用比较复杂,这点之前提到了,需要手动加锁,解锁,而且还必须保证在异常状态下也要能够解锁。而synchronized的使用就简单多了。
    2. 效率较低,synchronized关键字毕竟是jvm底层实现的,因此用了很多优化措施来优化速度(偏向锁、轻量锁等),而显示锁的效率相对低一些。

    因此当需要进行同步时,优先考虑使用synchronized关键字,只有synchronized关键字不能满足需求时,才考虑使用显示锁。

    总结

    这篇文章介绍了显示锁是什么,显示锁的优点与缺点,在什么情况下会用到显示锁。

    后文将重点学习显示锁的底层实现:队列同步器(AbstractQueuedSynchronizer)的实现、重入锁(ReentrantLock)的实现、读写锁(ReadWriteLock)的实现、等待/通知(Condition)的实现。

  • 相关阅读:
    出现 could not open jvm.cfg 的解决办法
    powerdesigner相关概念理解
    UML建模类图
    LAMP环境折腾
    ThinkPHP学习笔记1
    ubuntu14在kDE界面下的关于eclipse提示框黑色背景的修改!
    LAMP环境安装与apache配置
    Unix网络编程---第四次作业
    Unix网络编程---第三次作业
    Unix网络编程---第二次作业
  • 原文地址:https://www.cnblogs.com/sheeva/p/6439335.html
Copyright © 2011-2022 走看看