java多线程-锁

时间:2022-04-21 07:19:19

自 Java 5 开始,java.util.concurrent.locks 包中包含了一些锁的实现,因此你不用去实现自己的锁了。但是你仍然需要去了解怎样使用这些锁。

一个简单的锁

让我们从 java 中的一个同步块开始:

public class Counter{
private int count = 0; public int inc(){
synchronized(this){
return ++count;
}
}
}

可以看到在 inc()方法中有一个 synchronized(this)代码块。该代码块可以保证在同一时间只有一个线程可以执行 return ++count。虽然在 synchronized 的同步块中的代码可以更加复杂,但是++count 这种简单的操作已经足以表达出线程同步的意思。

以下的 Counter 类用 Lock 代替 synchronized 达到了同样的目的:

public class Counter{
private Lock lock = new Lock();
private int count = 0; public int inc(){
lock.lock();
int newCount = ++count;
lock.unlock();
return newCount;
}
}

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

这里有一个 Lock 类的简单实现:

public class Counter{
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();
}
}

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

当线程完成了临界区(位于 lock()和 unlock()之间)中的代码,就会调用 unlock()。执行 unlock()会重新将 isLocked 设置为 false,并且通知(唤醒)其中一个(若有的话)在 lock()方法中调用了 wait()函数而处于等待状态的线程。

锁的可重入性

Java 中的 synchronized 同步块是可重入的。这意味着如果一个 java 线程进入了代码中的 synchronized 同步块,并因此获得了该同步块使用的同步对象对应的管程上的锁,那么这个线程可以进入由同一个管程对象所同步的另一个 java 代码块。下面是一个例子:

public class Reentrant{
public synchronized outer(){
inner();
} public synchronized inner(){
//do something
}
}

注意 outer()和 inner()都被声明为 synchronized,这在 Java 中和 synchronized(this)块等效。如果一个线程调用了 outer(),在 outer()里调用 inner()就没有什么问题,因为这两个方法(代码块)都由同一个管程对象(”this”)所同步。如果一个线程已经拥有了一个管程对象上的锁,那么它就有权访问被这个管程对象同步的所有代码块。这就是可重入。线程可以进入任何一个它已经拥有的锁所同步着的代码块。

前面给出的锁实现不是可重入的。如果我们像下面这样重写 Reentrant 类,当线程调用 outer()时,会在 inner()方法的 lock.lock()处阻塞住。

public class Reentrant2{
Lock lock = new Lock(); public outer(){
lock.lock();
inner();
lock.unlock();
} public synchronized inner(){
lock.lock();
//do something
lock.unlock();
}
}

调用 outer()的线程首先会锁住 Lock 实例,然后继续调用 inner()。inner()方法中该线程将再一次尝试锁住 Lock 实例,结果该动作会失败(也就是说该线程会被阻塞),因为这个 Lock 实例已经在 outer()方法中被锁住了。

两次 lock()之间没有调用 unlock(),第二次调用 lock 就会阻塞,看过 lock()实现后,会发现原因很明显:

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

一个线程是否被允许退出 lock()方法是由 while 循环(自旋锁)中的条件决定的。当前的判断条件是只有当 isLocked 为 false 时 lock 操作才被允许,而没有考虑是哪个线程锁住了它。

为了让这个 Lock 类具有可重入性,我们需要对它做一点小的改动:

public class Lock{
boolean isLocked = false;
Thread lockedBy = null;
int lockedCount = 0; public synchronized void lock()
throws InterruptedException{
Thread callingThread =
Thread.currentThread();
while(isLocked && lockedBy != callingThread){
wait();
}
isLocked = true;
lockedCount++;
lockedBy = callingThread;
} public synchronized void unlock(){
if(Thread.curentThread() ==
this.lockedBy){
lockedCount--; if(lockedCount == 0){
isLocked = false;
notify();
}
}
} ...
}

注意到现在的 while 循环(自旋锁)也考虑到了已锁住该 Lock 实例的线程。如果当前的锁对象没有被加锁(isLocked = false),或者当前调用线程已经对该 Lock 实例加了锁,那么 while 循环就不会被执行,调用 lock()的线程就可以退出该方法(译者注:“被允许退出该方法”在当前语义下就是指不会调用 wait()而导致阻塞)。

除此之外,我们需要记录同一个线程重复对一个锁对象加锁的次数。否则,一次 unblock()调用就会解除整个锁,即使当前锁已经被加锁过多次。在 unlock()调用没有达到对应 lock()调用的次数之前,我们不希望锁被解除。

现在这个 Lock 类就是可重入的了。

锁的公平性

Java 的 synchronized 块并不保证尝试进入它们的线程的顺序。因此,如果多个线程不断竞争访问相同的 synchronized 同步块,就存在一种风险,其中一个或多个线程永远也得不到访问权 —— 也就是说访问权总是分配给了其它线程。这种情况被称作线程饥饿。为了避免这种问题,锁需要实现公平性。本文所展现的锁在内部是用 synchronized 同步块实现的,因此它们也不保证公平性。

在 finally 语句中调用 unlock()

如果用 Lock 来保护临界区,并且临界区有可能会抛出异常,那么在 finally 语句中调用 unlock()就显得非常重要了。这样可以保证这个锁对象可以被解锁以便其它线程能继续对其加锁。以下是一个示例:

lock.lock();
try{
//do critical section code,
//which may throw exception
} finally {
lock.unlock();
}

这个简单的结构可以保证当临界区抛出异常时 Lock 对象可以被解锁。如果不是在 finally 语句中调用的 unlock(),当临界区抛出异常时,Lock 对象将永远停留在被锁住的状态,这会导致其它所有在该 Lock 对象上调用 lock()的线程一直阻塞。

java多线程-锁的更多相关文章

  1. Java多线程--锁的优化

    Java多线程--锁的优化 提高锁的性能 减少锁的持有时间 一个线程如果持有锁太长时间,其他线程就必须等待相应的时间,如果有多个线程都在等待该资源,整体性能必然下降.所有有必要减少单个线程持有锁的时间 ...

  2. synchronized与static synchronized 的差别、synchronized在JVM底层的实现原理及Java多线程锁理解

    本Blog分为例如以下部分: 第一部分:synchronized与static synchronized 的差别 第二部分:JVM底层又是怎样实现synchronized的 第三部分:Java多线程锁 ...

  3. Java 多线程 锁 存款 取款

    http://jameswxx.iteye.com/blog/806968 最近想将java基础的一些东西都整理整理,写下来,这是对知识的总结,也是一种乐趣.已经拟好了提纲,大概分为这几个主题: ja ...

  4. Java多线程——锁概念与锁优化

    为了性能与使用的场景,Java实现锁的方式有非常多.而关于锁主要的实现包含synchronized关键字.AQS框架下的锁,其中的实现都离不开以下的策略. 悲观锁与乐观锁 乐观锁.乐观的想法,认为并发 ...

  5. Java多线程——锁

    Java多线系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多线 ...

  6. [java多线程] - 锁机制&同步代码块&信号量

    在美眉图片下载demo中,我们可以看到多个线程在公用一些变量,这个时候难免会发生冲突.冲突并不可怕,可怕的是当多线程的情况下,你没法控制冲突.按照我的理解在java中实现同步的方式分为三种,分别是:同 ...

  7. 详解Java多线程锁之synchronized

    synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法. synchronized的四种使用方式 修饰代码块:被修饰的代码块称为同步语句块,其作用的范围是大括号{}括 ...

  8. Java——多线程锁的那些事

    引入 Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率. 下面先带大家来总体预览一下锁的分类图 1.乐观锁 VS 悲观锁 乐观锁与悲观锁是一种广义上的概念,体现了 ...

  9. Java多线程-锁的区别与使用

    目录 锁类型 可中断锁 公平锁/非公平锁 可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分段锁 偏向锁/轻量级锁/重量级锁 自旋锁 Synchronized与Static Synchron ...

随机推荐

  1. October 23rd Week 44th Sunday 2016

    When ambition ends, happiness begins. 野心消亡之日,正是快乐破茧之时. No ambition, no annoyance. No ambition, no ac ...

  2. PB中用oracle的存储过程返回记录集做数据源来生成数据窗口,PB会找不到此存储过程及不能正常识别存储过程的参数问题(转)

    (转)在PB中用oracle的存储过程返回记录集做数据源来生成数据窗口 首先oracle的存储过程写法与MSSQL不一样,差别比较大. 如果是返回数据集的存储过程则需要利用oracle的包来定义游标. ...

  3. DB_MYSQL_mysql-5.7.10-winx64解压版安装笔记

    1.http://dev.mysql.com/downloads/mysql/ 里面下载Windows (x86, 64-bit), ZIP Archive mysql-5.7.10-winx64.z ...

  4. 约瑟夫(环)问题(Josephus problem)

    问题描述:皇帝决定找出全国中最幸运的一个人,于是从全国选拔出 n 个很幸运的人,让这 n 个人围着圆桌进餐,可是怎么选择出其中最幸运的一个人呢?皇帝决定:从其中一个人从 1 开始报数,按顺序数到第 k ...

  5. Java I/O 文件加锁,压缩

    文件加锁: 文件加锁机制允许我们同步访问某个作为共享资源的文件. public class Test { public static void main(String[] args) throws I ...

  6. Java快速入门

    Java 是什么? Java 的特点: 面向对象 平台无关(跨平台): 简单 安全 体系结构 - 中性 可移植 健壮 多线程 解释型 高性能 分布式 动态 Java环境设置: Java SE可免费提供 ...

  7. 220 DIV2 B. Inna and Nine

    220 DIV2 B. Inna and Nine input 369727 output 2 input 123456789987654321 output 1 题意:比如例子1:369727--& ...

  8. 表单,css

  9. DQL、DML、DDL、DCL区别

    DQL(data query language)数据查询语言 主要是由SELECT构成的查询语句 基本语法:select 字段名 from 表名 where 查询条件 DML(data manipul ...

  10. 缓存日志截取字段上传FTP

    #!/bin/bash awk '{print $3,$4,$5,$6,$9,$11,$12,$14,$15,$18}' /usr/local/tcacheserver/var/log/traffic ...