JUC学习笔记(一)https://www.cnblogs.com/lm66/p/15118407.html

1、Lock接口

1.1、Synchronized

1.1.1、Synchronized关键字回顾

synchronized 是 Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:

  • 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{} 括起来的代码,作用的对象是调用这个代码块的对象;
  • 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
    • 虽然可以使用 synchronized 来定义方法,但 synchronized 并不属于方法定 义的一部分,因此,synchronized 关键字不能被继承。如果在父类中的某个方 法使用了 synchronized 关键字,而在子类中覆盖了这个方法,在子类中的这 个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上 synchronized 关键字才可以。当然,还可以在子类方法中调用父类中相应的方 法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  • 修改一个类,其作用的范围是 synchronized后面括号括起来的部分,作用主
  • 的对象是这个类的所有对象。

1.1.2、售票案例

class Ticket {

    /**
* 票数
*/
private int number = 30; public synchronized void sale() {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + ":卖出:" + number-- + " 剩下:" + number);
}
} }

  如果一个代码块被 synchronized 修饰了,当一个线程获取了对应的锁,并执 行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里 获取锁的线程释放锁只会有两种情况:

1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;

2)线程执行发生异常,此时JVM 会让线程自动释放锁。

  那么如果这个获取锁的线程由于要等待 IO 或者其他原因(比如调用 sleep 方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一 下,这多么影响程序执行效率。

因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过 Lock 就可以办到。

1.2、什么是Lock

  Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允 许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock 提供了比 synchronized 更多的功能。

Lock 与的 Synchronized 区别:

  • Lock 不是 Java 语言内置的,synchronized是 Java 语言的关键字,因此是内 置特性。Lock 是一个类,通过这个类可以实现同步访问;
  • Lock 和 synchronized 有一点非常大的不同,采用 synchronized不需要用户 去手动释放锁,当 synchronized 方法或者synchronized 代码块执行完之后, 系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

1.2.1、Lock接口

public interface Lock {

    void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}

下面来逐个讲述 Lock 接口中每个方法的使用

1.2.2、lock

  lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他 线程获取,则进行等待。

  采用 Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一 般来说,使用 Lock 必须在 try{}catch{}块中进行,并且将释放锁的操作放在 finally 块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用 Lock来进行同步的话,是以下面这种形式去使用的:

        Lock lock = ...;
lock.lock();
try {
// 处理任务
} catch (Exception e) {
} finally {
// 释放锁
lock.unlock();
}

1.2.3、newCondition

  关键字 synchronized与wait()/notify()这两个方法一起使用可以实现等待/通知模式, Lock 锁的 newContition()方法返回 Condition 对象,Condition 类 也可以实现等待/通知模式。

用 notify()通知时,JVM会随机唤醒某个等待的线程, 使用 Condition 类可以 进行选择性通知, Condition 比较常用的两个方法:

  • await()会使当前线程等待,同时会释放锁,当其他线程调用 signal()时,线程会重 新获得锁并继续执行。
  • signal()用于唤醒一个等待的线程。

注意:在调用 Condition 的await()/signal()方法前,也需要线程持有相关 的 Lock 锁,调用await()后线程会释放这个锁,在 singal()调用后会从当前 Condition 对象的等待队列中,唤醒 一个线程,唤醒的线程尝试获得锁, 一旦

获得锁成功就继续执行。

1.3、ReentrantLock

  ReentrantLock,意思是“可重入锁”,关于可重入锁的概念将在后面讲述。 ReentrantLock 是唯一实现了 Lock 接口的类,并且 ReentrantLock 提供了更

多的方法。下面通过一些实例看具体看一下如何使用。

public class Test {
private ArrayList<Integer> arrayList = new ArrayList<Integer>(); public static void main(String[] args) {
final Test test = new Test();
new Thread() {
@Override
public void run() {
test.insert(Thread.currentThread());
}
}.start(); new Thread() {
@Override
public void run() {
test.insert(Thread.currentThread());
}
}.start();
} public void insert(Thread thread) {
Lock lock = new ReentrantLock(); //注意这个地方
lock.lock();
try {
System.out.println(thread.getName() + "得到了锁");
for (int i = 0; i < 5; i++) {
arrayList.add(i);
}
} catch (Exception e) {
} finally {
System.out.println(thread.getName() + "释放了锁");
lock.unlock();
} }
}

1.4、ReadWriteLock

  ReadWriteLock 也是一个接口,在它里面只定义了两个方法:

  • Lock readLock();
  • Lock writeLock();

  一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分 成 2 个锁来分配给线程,从而使得多个线程可以同时进行读操作。下面的 ReentrantReadWriteLock 实现了 ReadWriteLock 接口。

ReentrantReadWriteLock 里面提供了很多丰富的方法,不过最主要的有两个 方法:readLock()和 writeLock()用来获取读锁和写锁。

下面通过几个例子来看一下 ReentrantReadWriteLock 具体用法。

public class ReadWriteLockDemo {
public static void main(String[] args) throws InterruptedException {
MyCache myCache = new MyCache();
for (int i = 1; i <= 5; i++) {
final int num = i;
new Thread(() -> {
myCache.put(num + "", num);
}, String.valueOf(i)).start();
} for (int i = 1; i <= 5; i++) {
final int num = i;
new Thread(() -> {
Object result = myCache.get(num + "");
}, String.valueOf(i)).start();
}
}
} class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 放数据
public void put(String key, Object value) { Lock lock = readWriteLock.writeLock();
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "正在进行写操作:" + key);
TimeUnit.MICROSECONDS.sleep(300);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "完成写操作:" + key);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} // 取数据
public Object get(String key) {
Lock lock = readWriteLock.readLock();
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "正在进行读操作:" + key);
TimeUnit.MICROSECONDS.sleep(300);
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "完成读操作:" + key);
return result;
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return null;
}
}

注意:

  • 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写 锁的线程会一直等待释放读锁。
  • 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。

1.5、小结

Lock 和 synchronized 有以下几点不同:

  • Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现;
  • synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现 象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很 可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
  • Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用synchronized 时,等待的线程会一直等待下去,不能够响应中断;
  • 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
  • Lock 可以提高多个线程进行读操作的效率。

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源 非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于synchronized。

JUC学习笔记(二)的更多相关文章

  1. JUC学习笔记(六)

    JUC学习笔记(一)https://www.cnblogs.com/lm66/p/15118407.html JUC学习笔记(二)https://www.cnblogs.com/lm66/p/1511 ...

  2. JUC学习笔记(五)

    JUC学习笔记(一)https://www.cnblogs.com/lm66/p/15118407.html JUC学习笔记(二)https://www.cnblogs.com/lm66/p/1511 ...

  3. JUC学习笔记(四)

    JUC学习笔记(一)https://www.cnblogs.com/lm66/p/15118407.html JUC学习笔记(二)https://www.cnblogs.com/lm66/p/1511 ...

  4. JUC学习笔记(三)

    JUC学习笔记(一)https://www.cnblogs.com/lm66/p/15118407.html JUC学习笔记(二)https://www.cnblogs.com/lm66/p/1511 ...

  5. WPF的Binding学习笔记(二)

    原文: http://www.cnblogs.com/pasoraku/archive/2012/10/25/2738428.htmlWPF的Binding学习笔记(二) 上次学了点点Binding的 ...

  6. AJax 学习笔记二(onreadystatechange的作用)

    AJax 学习笔记二(onreadystatechange的作用) 当发送一个请求后,客户端无法确定什么时候会完成这个请求,所以需要用事件机制来捕获请求的状态XMLHttpRequest对象提供了on ...

  7. [Firefly引擎][学习笔记二][已完结]卡牌游戏开发模型的设计

    源地址:http://bbs.9miao.com/thread-44603-1-1.html 在此补充一下Socket的验证机制:socket登陆验证.会采用session会话超时的机制做心跳接口验证 ...

  8. JMX学习笔记(二)-Notification

    Notification通知,也可理解为消息,有通知,必然有发送通知的广播,JMX这里采用了一种订阅的方式,类似于观察者模式,注册一个观察者到广播里,当有通知时,广播通过调用观察者,逐一通知. 这里写 ...

  9. java之jvm学习笔记二(类装载器的体系结构)

    java的class只在需要的时候才内转载入内存,并由java虚拟机的执行引擎来执行,而执行引擎从总的来说主要的执行方式分为四种, 第一种,一次性解释代码,也就是当字节码转载到内存后,每次需要都会重新 ...

随机推荐

  1. RPM安装MySQL5.7并更改数据目录

    RPM安装MySQL5.7并更改数据目录 文末附MySQL完整配置文件 官网地址:https://dev.mysql.com/downloads/mysql/5.7.html#downloads 注意 ...

  2. Arduino库和STM32的寄存器、标准库、HAL库、LL库开发比较之GPIO

    标题: Arduino库和STM32的寄存器.标准库.HAL库.LL库开发比较之GPIO 作者: 梦幻之心星 sky-seeker@qq.com 标签: [#Arduino,#STM32,#库,#开发 ...

  3. js笔记15

    DOM2动态创建节点 1.生成节点的方法 document.createElement("div") 2.插入节点的方法 父元素.appendChild(新节点) 在父节点的子节点 ...

  4. 深入理解 Android ANR 触发原理以及信息收集过程

    一.概述 作为 Android 开发者,相信大家都遇到过 ANR.那么为什么会出现 ANR 呢,ANR 之后系统都做了啥.文章将对这个问题详细解说. ANR(Application Not respo ...

  5. 关于开箱即用的文档静态网站生成器VuePress

    关于VuePress 一个由Vue驱动的静态文档网站生成框架,具有开箱即用的优点. 给项目添加.gitignore .gitignore是git用来排除目录的清单,我们把以下目录加入其中,以便每次操作 ...

  6. 41、shell编程基础

    bash的变量默认都是全局变量,脚本内都可以调用,无论在什么位置(函数体中也一样),即函数体外可以调用函数体内的变量: local一般用于局部变量声明,多在函数体内使用: 如果要变为局部变量,则要使用 ...

  7. 3、mysql的多实例配置(2)

    4.设置mysql多实例启动脚本: (1)3306: [root@backup application]# cat /data/3306/mysql #!/bin/sh . /etc/init.d/f ...

  8. C++ nullptr 和 NULL 的使用区别

    1. 为什么会有nullptr的出现 目的:nullptr的出现主要是为了替代NULL. 那么,为什么要替代NULL呢? 在NULL的定义中存在会有2种方式,有的编译器会将NULL定义成0,有的编译器 ...

  9. Springboot集成RabbitMQ之MessageConvert源码解析

    问题 最近在使用RabbitMq时遇到了一个问题,明明是转换成json发送到mq中的数据,消费者接收到的却是一串数字也就是byte数组,但是使用mq可视化页面查看数据却是正常的,之前在使用过程中从未遇 ...

  10. mybatis中使用selectKey,返回结果一直是1

    转:https://www.cnblogs.com/caizhen/p/9186608.html mybatis中使用selectKey,返回结果一直是1,结合这个问题,笔记一下selectKey标签 ...