多线程编程基础--Java线程同步机制

1. 前言

从广义来讲,Java平台提供的线程同步机制包括锁、volatile关键字、final关键字、static关键字以及一些相关的API。本文主要介绍Java平台中用于协调线程间共享数据访问的相关关键字和API,固然也是经常使用的线程同步方法。java

2. 锁概述

学习线程都知道,在多个线程并发访问共享变量、共享资源时,会形成线程安全问题,那怎么解决呢?编程

咱们很容易想到一种保障线程安全的方法--将多个线程对于并发访问转换为串行访问,即一个共享数据一次只能被一个线程访问,该线程访问结束后其余线程才能对其进行访问。锁(Lock)就是利用这种思路以保障线程安全的线程同步机制。api

一些名词概念:缓存

  • 临界区: 锁的持有线程在其得到锁以后和释放锁以前这段时间内所执行的代码称做临界区(Critical Section)。
  • 可重入性(Reentrancy): 一个线程在其持有一个锁的时候可否再次或屡次申请该锁。
  • 锁的争用与调度: 资源的争用、调度的概念对锁也是一样适用的。
  • 锁的粒度: 一个锁实例所保护的共享数据的数量大小就被称为该锁的粒度(Granularity)。数量大,称锁的粒度粗,不然,称粒度细。

这些复杂概念就不深研究了,只简单了解,想深刻研究的小伙伴自行查阅相关书籍。安全

3. 内部锁:synchronized 关键字

Java平台中的任何一个对象都有惟一一个与之关联的锁,这种锁称为监视器(Monitor)或者内部锁(Intrinsic Lock)。内部锁可以保障原子性、可见性和有序性。内部锁时经过synchronized 关键字实现的。synchronized提供了一种独占的加锁方式,是比较经常使用的线程同步的关键字,通常在“线程安全的单例”中广泛使用。该关键字可以保证代码块的同步性和方法层面的同步。多线程

代码块同步

synchronized 关键字修饰的代码块就被称为同步块并发

//使用synchronized关键字实现线程安全的单例模式
    private static Singleton instance;
    public static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class)
            {
                if(instance == null){
                    instance = new Singleton();
                }              
            }
        }
        return instance;
}
privateSingleton(){ }
复制代码

方法同步

synchronized 关键字修饰的方法就被称为同步方法。 同步方法的这个方法体就是一个临界区。负载均衡

public static synchronized Singleton getInstance2(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
private Singleton(){ }
复制代码

4. 显式锁:Lock接口

以读写锁为例,Lock的使用方式很简单,只须要在须要加锁的地方先获取锁,操做完成以后释放锁,须要注意的是学习

要在finally中释放锁,这样作的目的是保证在获取到所以后,最终都是可以被释放; 不要讲获取锁的过程写在try代码块中,防止在获取锁发生异常时致使的锁无端释放。spa

Lock lock  = new ReentrantLock();
lock.lock();
try{
//可能会出现线程安全的操做
}finally{
//必定在finally中释放锁
//也不能把获取锁在try中进行,由于有可能在获取锁的时候抛出异常
  lock.ublock();
}
复制代码

Lock api Lock是一个接口,定义了锁的获取和释放等基本操做。

  • void lock() 线程调用该方法获取锁,获取锁后返回;

  • void lockInterruptibly() throws InterruptedException 可中断地获取锁,和lock()方法的区别在于该方法能够响应中断;

  • boolean tryLock() 尝试非阻塞获取锁,线程调用该方法后马上返回,成功获取到锁返回true,不然返回false;

  • boolean tryLock(long time, TimeUnit unit) throws InterruptedException 超时获取锁,该方法在如下3中状况会返回: 在超时时间内得到锁; 在超时时间被中断; 超时时间结束仍未得到,返回false。

  • void unlock() 释放锁;

  • Condition newCondition() 获取等待通知Condition组件,该组件和当前锁绑定,只有线程获取到了锁才能调用await()方法,调用后,当前线程释放锁。

Lock与synchronized之间的区别和联系

在总结二者的区别和联系以前先引入两个概念:隐式锁和显式锁。 上面都有涉及,下面仔细讲解一下。

隐式锁: 隐式获取锁,synchronized是它的表明,使用者不须要关心其内部锁的获取和释放,全部的锁的相关操做都由具体的关键字完成; 显式锁: 显示地获取锁,Lock是它的表明,须要使用者在使用的时候显示地获取和释放锁。

显式锁和隐式锁都实现了对临界区访问的控制,可是显式锁提供了更灵活、更强大的接口:

  • 隐式锁将锁的获取和释放固化了,只能先获取再释放;显式锁显然无此约束,能够按照本身的须要来作锁的释放;
  • 显式锁提供了可中断获取锁以及超时获取锁等多种隐式锁不具有的同步特性;
  • 提供维度更小的等待与唤醒(Condition)。

就Lock接口提供的synchronized关键字不具有的特性作一个分析描述:

特性 描述
尝试非阻塞获取锁 当前线程尝试获取锁,若是锁未被其余线程获取,当前线程成功获取并持有锁
可中断获取锁 获取锁的线程可以响应中断,当获取到锁的线程被中断时,抛出中断异常,锁被释放
超时获取锁 在指定的时间内获取锁,若是在指定的时间内未获取到,获取锁失败,返回

5. 轻量级同步机制:volatile 关键字

volatile “不稳定”的意思。volatile关键字用于修饰共享可变变量,既没有使用 final 关键字修饰的实例变量或静态变量,相应的变量就被称做 volatile变量。 private volatile int logLevel;

volatile 关键字表示被修饰的变量的值容易发生变化(即被其余线程更改),于是不稳定。volatile变量的不稳定性意味着对这种变量的读和写都必须从高速缓存或者主内存中读取,以读取变量的相对新值。

volatile 关键字常被称为轻量级锁,其做用与锁的做用有相同的地方:保证可见性和有序性。所不一样的是,在原子性方面它仅能保障写volatile 变量操做的原子性,但没有锁 的排他性;其次,volatile 关键字的使用不会引发上下文切换(这是volatile 被称为轻量级的缘由)。

6. 小结&参考资料

小结

对于多线程编程来讲,此文介绍的仅是九牛一毛,在解决负载均衡问题,充分利用CPU资源方面,多线程编程起着相当重要的做用。 我才刚刚入门,加油,菜鸡!!

参考资料

相关文章
相关标签/搜索