【Java并发编程实战】-----“J.U.C”:锁,lock

 

在java中有两种方法实现锁机制,一种是在前一篇博客中(【java7并发编程实战】-----线程同步机制:synchronized)介绍的synchronized,而另一种是比synchronized更加强大和领过的Lock。Lock确保当一个线程位于代码的临界区时,另一个线程不进入临界区,相对于synchronized,Lock接口及其实现类提供了更加强大、灵活的锁机制。

一个简单的锁

在使用synchronized时,我们是这样使用锁的:

public class ThreadTest {
public void test(){
synchronized(this){
//do something
}
}
}

synchronized可以确保在同一时间内只有一个线程在执行dosomething。下面是使用lock替代synchronized:

public class ThreadTest {
Lock lock = new Lock();
public void test(){
lock.lock();
//do something
lock.unlock();
}
}

lock()方法会对Lock实例对象进行加锁,因此所有对该对象调用lock()方法的线程都会被阻塞,直到该Lock对象的unlock()方法被调用。

【以下引自:Java中的锁

public class Lock{
private boolean isLocked = false; public synchronized void lock() throws InterruptedException{
while(isLocked){
wait();
}
isLocked = true;
} public synchronized void unlock(){
isLocked = false;
notify();
}
}

当isLocked为true时,调用lock()的线程在wait()调用上阻塞等待。为防止该线程没有收到notify()调用也从wait()中返回,这个线程会重新去检查isLocked条件以决定当前是否可以安全地继续执行还是需要重新保持等待,而不是认为线程被唤醒了就可以安全地继续执行了。如果isLocked为false,当前线程会退出while(isLocked)循环,并将isLocked设回true,让其它正在调用lock()方法的线程能够在Lock实例上加锁。

锁的公平性

公平性的对立面是饥饿。那么什么是“饥饿”呢?如果一个线程因为其他线程在一直抢占着CPU而得不到CPU运行时间,那么我们就称该线程被“饥饿致死”。而解决饥饿的方案则被称之为“公平性”——所有线程均可以公平地获得CPU运行机会。

导致线程饥饿主要有如下几个原因:

高优先级线程吞噬所有的低优先级线程的CPU时间。我们可以为每个线程单独设置其优先级,从1到10。优先级越高的线程获得CPU的时间越多。对大多数应用来说,我们最好是不要改变其优先级值。

线程被永久堵塞在一个等待进入同步块的状态。java的同步代码区是导致线程饥饿的重要因素。java的同步代码块并不会保证进入它的线程的先后顺序。这就意味着理论上存在一个或者多个线程在试图进入同步代码区时永远被堵塞着,因为其他线程总是不断优于他获得访问权,导致它一直得到不到CPU运行机会被“饥饿致死”。

线程在等待一个本身也处于永久等待完成的对象。如果多个线程处在wait()方法执行上,而对其调用notify()不会保证哪一个线程会获得唤醒,任何线程都有可能处于继续等待的状态。因此存在这样一个风险:一个等待线程从来得不到唤醒,因为其他等待线程总是能被获得唤醒。

为了解决线程“饥饿”的问题,我们可以使用锁实现公平性。

锁的可重入性

我们知道当线程请求一个由其它线程持有锁的对象时,该线程会阻塞,但是当线程请求由自己持有锁的对象时,是否可以成功呢?答案是可以成功的,成功的保障就是线程锁的“可重入性”。

“可重入”意味着自己可以再次获得自己的内部锁,而不需要阻塞。如下:

public class Father {
public synchronized void method(){
//do something
}
}
public class Child extends Father{
public synchronized void method(){
//do something
super.method();
}
}

如果所是不可重入的,上面的代码就会死锁,因为调用child的method(),首先会获取父类Father的内置锁然后获取Child的内置锁,当调用父类的方法时,需要再次后去父类的内置锁,如果不可重入,可能会陷入死锁。

java多线程的可重入性的实现是通过每个锁关联一个请求计算和一个占有它的线程,当计数为0时,认为该锁是没有被占有的,那么任何线程都可以获得该锁的占有权。当某一个线程请求成功后,JVM会记录该锁的持有线程 并且将计数设置为1,如果这时其他线程请求该锁时则必须等待。当该线程再次请求请求获得锁时,计数会+1;当占有线程退出同步代码块时,计数就会-1,直到为0时,释放该锁。这时其他线程才有机会获得该锁的占有权。

lock及其实现类

java.util.concurrent.locks提供了非常灵活锁机制,为锁定和等待条件提供一个框架的接口和类,它不同于内置同步和监视器,该框架允许更灵活地使用锁定和条件。它的类结构图如下:

ReentrantLock:一个可重入的互斥锁,为lock接口的主要实现。

ReentrantReadWriteLock:

ReadWriteLock:ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。

Semaphore:一个计数信号量。

Condition:锁的关联条件,目的是允许线程获取锁并且查看等待的某一个条件是否满足。

CyclicBarrier:一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点。

后面将会对这些类进行详细说明。

参考资料:

1、Java中的锁

2、【Java并发性和多线程】饥饿和公平

Java - “JUC”锁的更多相关文章

  1. Java多线程系列--“JUC锁”03之 公平锁(一)

    概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...

  2. Java多线程系列--“JUC锁”04之 公平锁(二)

    概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...

  3. Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例

    概要 本章介绍JUC包中的CyclicBarrier锁.内容包括:CyclicBarrier简介CyclicBarrier数据结构CyclicBarrier源码分析(基于JDK1.7.0_40)Cyc ...

  4. Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock

    本章对ReentrantLock包进行基本介绍,这一章主要对ReentrantLock进行概括性的介绍,内容包括:ReentrantLock介绍ReentrantLock函数列表ReentrantLo ...

  5. Java多线程系列--“JUC锁”01之 框架

    本章,我们介绍锁的架构:后面的章节将会对它们逐个进行分析介绍.目录如下:01. Java多线程系列--“JUC锁”01之 框架02. Java多线程系列--“JUC锁”02之 互斥锁Reentrant ...

  6. java多线程系类:JUC锁:01之框架

    本章,我们介绍锁的架构:后面的章节将会对它们逐个进行分析介绍.目录如下:01. Java多线程系列--"JUC锁"01之 框架02. Java多线程系列--"JUC锁&q ...

  7. Java多线程系列--“JUC锁”09之 CountDownLatch原理和示例

    概要 前面对"独占锁"和"共享锁"有了个大致的了解:本章,我们对CountDownLatch进行学习.和ReadWriteLock.ReadLock一样,Cou ...

  8. Java多线程系列--“JUC锁”06之 Condition条件

    概要 前面对JUC包中的锁的原理进行了介绍,本章会JUC中对与锁经常配合使用的Condition进行介绍,内容包括:Condition介绍Condition函数列表Condition示例转载请注明出处 ...

  9. Java多线程系列--“JUC锁”05之 非公平锁

    概要 前面两章分析了"公平锁的获取和释放机制",这一章开始对“非公平锁”的获取锁/释放锁的过程进行分析.内容包括:参考代码获取非公平锁(基于JDK1.7.0_40)释放非公平锁(基 ...

随机推荐

  1. “全栈2019”Java多线程第三十六章:如何设置线程的等待截止时间

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 下一章 "全栈2019"J ...

  2. Java几种单例模式的实现与利弊

    饿汉式 提前new出来实例了,并不是在第一次调用get方法时才实例化,没有进行延迟加载 public class Singleton1 { private static Singleton1 inst ...

  3. win下wamp虚拟主机配置

    第一步:在http://www.wampserver.com/网站上下载Wampserver,可以根据计算机的版本(32位或者64位)下载对应的版本 第二步,傻瓜式安装(直接点击下一步,下一步就好). ...

  4. python iter函数用法

    iter函数用法简述 Python 3中关于iter(object[, sentinel)]方法有两个参数. 使用iter(object)这种形式比较常见. iter(object, sentinel ...

  5. Python使用动态的变量名

    当我们在使用Python处理一些重复性很高的事情时,有时候需要很多的变量来存放一些暂行性的数据,由于这些变量的数量很大,所以这使我们就会想到能不能使用循环来像生成数据值一样生成变量名呢,当然是可以的 ...

  6. static、final、static final的区别

    final: final可以修饰属性,方法,类,局部变量(方法中的变量) final修饰的属性的初始化可以在编译期,也可以在运行期,初始化后不能被改变. final修饰的属性跟具体对象有关,在运行期初 ...

  7. python2 里边自定义线程池

    #!/usr/bin/env python # -*- coding:utf-8 -*- import Queue import threading class ThreadPool(object): ...

  8. SQL Server性能优化(7)理解数据库文件组织

    一.基本单位"页"     SQL Server是用8KB的页来存储数据.物理I/O操作也是在页级执行.页的种类有很多,具体参考(MSDN).我们关注更多的是数据页的结构,包括三部 ...

  9. Kafka消费异常处理

    org.apache.kafka.clients.consumer.CommitFailedException: Commit cannot be completed since the group ...

  10. 03-01:springboot 整合jsp

    1.修改pom文件,添加坐标 <!-- jstl -->        <dependency>            <groupId>javax.servlet ...