详解 锁
(有关线程的基本知识,请观看本人博文 —— 《详解 线程》)
在上篇博文中,本人通过一个例子,展示了 线程安全问题的现象 以及 出现的原因。
那么,在本篇博文中,本人就来讲解下线程安全的处理手段之一的 锁:
@
锁
说到锁,本人就不得不说说 同步代码块:
同步代码块:
为什么本人说 锁 和 同步代码块有关系呢?
本人仅拿同步代码块的格式来展示下这两者之间的关系:
格式:
synchronized(对象){ //不能在括号了直接new 对象,new 了 就没效果
要被同步的代码 ;
}
那么,对于上述的格式,本人要做以下说明:
说明:
这个同步代码块保证数据的安全性的一个主要因素就是这个对象
注意这个对象 要定义为静态成员变量 才能被所有线程共享
需要这个对象被所有的线程对象所共享
这个对象其实就是一把锁
这个对象习惯叫做 监视器 或 临界资源
本人现在来讲解下 同步的优缺点:
优缺点:
- 同步的好处:
同步的出现解决了多线程的安全问题。- 同步的弊端:
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
那么,现在,本人来介绍下 锁的类别:
锁的类别:
- 内置锁:
- 内置锁:
每个java对象都可以用做一个实现同步的锁,这些锁被称为内置锁。
线程进入同步代码块或方法的时候会自动获得该锁,
在退出同步代码块或方法时会释放该锁。
那么,对于内置锁,本人有两点说明:
说明:
- 获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
- java内置锁是一个互斥锁,这就意味着最多只有一个线程能够获得该锁。
当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,
直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
- 对象锁 和 类锁
- 对象锁和类锁:
java的对象锁和类锁在锁的概念上基本上和内置锁是一致的。
那么,现在,本人来展示下这两种锁之间的区别:
区别:
- 对象锁是用于对象实例方法,或者一个对象实例上的,
- 类锁是用于类的静态方法或者一个类的class对象上的。
可能有的同学看了上述的区别,会有如下疑惑:
类的实例,不就是对象吗?那这两种锁有差别吗?
那么,本人再来细讲下这两种锁的区别:
我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,
所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
但是有一点必须注意的是:
其实类锁只是一个概念上的东西,并不是真实存在的,
它只是用来帮助我们理解锁定实例方法和静态方法的区别的。
现在,本人来根据应用场景,来介绍下 不同应用场景锁的对象:
不同应用场景锁的对象:
- 同步代码块的锁对象:
任意一个对象- 同步方法的锁对象:
this- 静态同步方法的锁对象:
当前类对应的字节码文件对象(.class文件对象)
本人还要再提的一点就是 synchronized锁是一个 “悲观锁”
(至于 悲观锁 的知识点,将在本人 多线程专题下的后续博文《详解 volatile关键字 与 CAS算法》中进行讲解)
那么,说了这么多,本人现在来 通过上述知识点,来展示下对于 上篇博文的 卖票问题的解决:
首先,本人来给出一个售票线程类:
package edu.youzg.about_synchronized.core;
public class MyRunnable implements Runnable{
static int piao = 100;
static boolean goon = true;
static Object obj = new Object();
int i=1;
@Override
public void run() {
while (goon) {
goon = cellTickets();
}
}
//方法上加有一个synchronized关键字我们叫做同步方法
//同步方法使用的所对象不是任意对象,他用的锁是this
private synchronized boolean cellTickets() {
if (piao > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售" + (piao--) + "张票");
return true;
}
return false;
}
}
那么,现在,本人来给出测试类:
package edu.youzg.about_synchronized.core;
public class Test {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread th1 = new Thread(myRunnable);
Thread th2 = new Thread(myRunnable);
Thread th3 = new Thread(myRunnable);
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
那么,现在,本人来展示下运行结果:
由于输出结果太长,本人仅展示部分结果。
这次的运行结果没有出现线程安全问题!
本人在之前的博文中曾说过 StringBuffer 和 Vector 这个两个类都是线程安全的,那么,是为什么呢?
本人现在来展示下这两个类的部分原码:
首先是 StringBuffer类:
然后是 Vector类:
可以看到,这两个类的好多方法,都用了synchronized锁,这就保证了线程的安全性!
其实,在JDK5之后,专门有一个类来处理锁的基本操作 —— Lock类:
Lock 接口:
首先,本人来介绍下这个类:
概述:
虽然我们可以理解同步代码块和同步方法的锁对象问题,
但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象 —— Lock
那么,现在,本人来介绍下这个接口提供的API:
接口API:
- void lock()
获取锁- void lockInterruptibly()
获取该锁除非当前线程 interrupted- Condition newCondition()
返回一个新的 Condition实例绑定到该 Lock实例- boolean tryLock()
只有在调用时释放该锁,才能获取锁- boolean tryLock(long time, TimeUnit unit)
获取锁,如果它是免费的在给定的等待时间和当前线程没有被 interrupted- void unlock()
释放锁
在这个接口的子实现类中,我们主要应用 ReentrantLock类:
ReentrantLock类:
首先,本人先来介绍下这个类:
概述:
一个可重入的互斥 Lock具有相同的基本行为和语义为隐式监控锁使用 synchronized方法和报表访问,但扩展功能。
一个ReentrantLock是由线程最后成功锁定,但尚未解锁它。一个线程调用lock将返回,成功获取锁,当锁不是由另一个线程拥有。如果当前线程已经拥有锁,该方法将立即返回。这可以使用方法isHeldByCurrentThread()检查,并getHoldCount()。
此类的构造函数接受一个可选的公平性参数。当设置true,争,锁青睐授予访问最长等待线程。否则,此锁不保证任何特定的访问顺序。使用许多线程访问的公平锁的程序可能会显示较低的整体吞吐量(即,比那些使用默认设置慢,往往要慢得多),但有较小的差异,在时间获得锁和保证缺乏饥饿。请注意,锁的公平性并不能保证线程调度的公平性。因此,使用一个公平锁的许多线程之一可能会获得它的连续多次,而其他活动线程不进展,而不是目前持有的锁。还要注意,不定时的tryLock()方法不尊重公平设置。如果锁是可用的,即使其他线程正在等待,它也会成功的。
那么,本人再来展示下这个类的 构造方法:
构造方法:
- ReentrantLock()
创建 ReentrantLock实例- ReentrantLock(boolean fair)
创建具有给定的公平政策 ReentrantLock实例
现在,本人来介绍下这个类的 常用API:
常用API:
- int getHoldCount()
查询当前线程在这个锁上的数目- protected Thread getOwner()
返回该线程当前拥有该锁,
如果不拥有,则返回null- protected Collection< Thread > getQueuedThreads()
返回一个包含可能等待获取此锁的线程的集合- int getQueueLength()
返回等待获取此锁的线程数的估计值- protected Collection< Thread > getWaitingThreads(Condition condition)
返回一个集合,包含可能在与此锁关联的给定条件下等待的线程集合- int getWaitQueueLength(Condition condition)
返回在与此锁关联的给定条件下等待的线程数的估计值- boolean hasQueuedThread(Thread thread)
查询给定线程是否正在等待获取此锁- boolean hasQueuedThreads()
查询是否有任何线程等待获取此锁- boolean hasWaiters(Condition condition)
查询是否有任何线程在与此锁关联的给定条件下等待- boolean isFair()
如果锁已经公平,则 返回 true- boolean isHeldByCurrentThread()
查询如果这个锁是否由当前线程持有的- boolean isLocked()
查询此锁是否由任何线程所持有- void lock()
获取锁- void lockInterruptibly()
获取该锁除非当前线程 interrupted- Condition newCondition()
返回一个用于这 Lock实例 Condition实例- String toString()
返回一个确定此锁的字符串,以及它的锁状态- boolean tryLock()
只有在调用时,它不是由另一个线程持有的锁- boolean tryLock(long timeout, TimeUnit unit)
如果这个锁 不是由另一个线程在等待时间和当前线程没有被 interrupted,则返回true- void unlock()
试图释放这个锁
那么,现在,本人来展示下这个类的部分API 的使用:
首先是 售票线程类:
package edu.youzg.about_synchronized.core;
import java.util.concurrent.locks.ReentrantLock;
public class MyRunnable implements Runnable{
static int piao = 100;
static Object obj = new Object();
static ReentrantLock lock = new ReentrantLock();
static boolean goon = true;
@Override
public void run() {
while (goon) {
//加锁
try {
lock.lock();
if (piao > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售" + (piao--) + "张票");
} else {
goon = false;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//确保锁要释放掉
//释放锁
lock.unlock();
}
}
}
}
那么,本人再来给出一个 测试类:
package edu.youzg.about_synchronized.core;
public class Test {
public static void main(String[] args) {
MyRunnable myRunnable1 = new MyRunnable();
Thread th1 = new Thread(myRunnable1);
Thread th2 = new Thread(myRunnable1);
Thread th3 = new Thread(myRunnable1);
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
那么,本人来展示下运行结果:
在我们使用锁的过程中,可能会遇到一个很致命的问题 —— 死锁:
死锁:
首先,本人来介绍下 死锁 是什么意思:
概述:
死锁:
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
那么,看了上述的解释,可能还有同学不太明白发生的原因。
那么,本人来举个例来展示下:
举例:
中国人和美国人一起吃饭
中国人使用的筷子
美国人使用的刀和叉
中国人获取到了美国人的刀
美国人获取到了中国人的一根筷子
这样的话,无论是中国人还是美国人,都无法运行到吃饭,就会一直等待对方将餐具归还
那么,本人现在拿代码来展示下“死锁”状态的现象:
首先,本人来创建一个提供两把锁的接口:
package edu.youzg.about_synchronized.core;
public interface ObjectUtils {
//创建两把锁对象
public static final Object objA= new Object();
Object objB=new Object();
}
那么,本人再来创建一个会产生死锁现象的线程类:
package edu.youzg.about_synchronized.core;
public class MyThread extends Thread{
boolean flag;
public MyThread(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if(flag) {
synchronized (ObjectUtils.objA){
System.out.println("true====ObjA 进来了");
synchronized (ObjectUtils.objB){
System.out.println("true====ObjB进来了");
}
} //objA 不释放
} else {
synchronized (ObjectUtils.objB) {
System.out.println("false====ObjB 进来了");
synchronized (ObjectUtils.objA) {
System.out.println("false====ObjA 进来了");
}
} //objB不释放
}
System.out.println("后续代码... ...");
}
}
现在,本人来给出一个测试类:
package edu.youzg.about_synchronized.core;
public class Test {
public static void main(String[] args) {
MyThread th1= new MyThread(true);
MyThread th2 = new MyThread(false);
th2.start();
th1.start();
}
}
那么,本人现在来展示下运行结果:
可以看到,线程仿佛 “卡死”在了那几行代码,
这就是因为这两把锁都争夺走了对方所需的资源,造成了死锁现象。
现在,本人再来讲解一个在面试中,经常会问到的一个有关 多线程和锁 的问题:
生产者与消费者问题:
那么,本人直接来上代码:
首先是 一个用于存储信息的Model类:
package edu.youzg.about_synchronized.core;
public class Model {
String name;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
现在,本人来创建一个能够创建线程的 生产者类:
package edu.youzg.about_synchronized.core;
public class ProducerThread extends Thread{
Model model;
int i = 0;
public ProducerThread(Model model) {
this.model = model;
}
@Override
public void run() {
while (true){
synchronized (model){
if (i % 2 == 0) {
model.name = "生产者2号";
model.age = 23;
//等待...
} else {
model.name = "生产者1号";
model.age = 24;
}
i++;
}
}
}
}
现在,本人来给出一个消费者线程类:
package edu.youzg.about_synchronized.core;
public class ConsumerThread extends Thread {
Model model;
public ConsumerThread(Model model) {
this.model = model;
}
@Override
public void run() {
while (true) {
System.out.println(model.name + " === " + model.age);
}
}
}
现在,本人再给出一个测试类:
package edu.youzg.about_synchronized.core;
public class Test extends Thread{
public static void main (String[] args) {
Model model = new Model();
ProducerThread producer = new ProducerThread(model);
ConsumerThread consumer = new ConsumerThread(model);
producer.start();
consumer.start();
}
}
那么,现在,本人来展示下运行结果:
上图中我们能发现:即使我们用了锁,还是出现了线程安全问题!
那么,为什么我们明明都给代码块上了锁,还是出现了线程安全问题呢?
这是因为,我们生产者每次进行的操作都是分两步的,而生产者自己能进自己的锁内,最后导致生产者所生产的信息是错误的。
那么,我们该如何解决呢?
本人现在来介绍两个方法:
Object 类中:
- void wait ():
在其他线程调用此对象的 notify () 方法或 notifyAll () 方法前,导致当前线程等待- void wait (long timeout):
在其他线程调用此对象的 notify () 方法或 notifyAll () 方法,
或者超过指定的时间量前,导致当前线程等待- void notify ():
唤醒在此对象监视器上等待的单个线程- void notifyAll ():
唤醒在此对象监视器上等待的所有线程
那么,本人现在来对上述的四个类做下改变,来解决上述的问题:
首先是用于存储数据的Model类:
package edu.youzg.about_synchronized.core;
public class Model {
String name;
int age;
boolean flag;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
然后是生产者线程类:
package edu.youzg.about_synchronized.core;
public class ProducerThread extends Thread{
Model model;
int i = 0;
public ProducerThread(Model model) {
this.model = model;
}
@Override
public void run() {
while (true){
synchronized (model){
//作为生产者来说:有了资源,等着,通知消费线程来消费
if(model.flag){
try {
model.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (i % 2 == 0) {
model.name = "生产者2号";
model.age = 23;
//等待...
} else {
model.name = "生产者1号";
model.age = 24;
}
//通知消费线程,去消费
model.flag = true; //修改标记
model.notify();//唤醒等待的线程,唤醒之后,多个线程还要再次争抢时间片
i++;
}
}
}
}
接下来是消费者线程类:
package edu.youzg.about_synchronized.core;
public class ConsumerThread extends Thread {
Model model;
public ConsumerThread(Model model) {
this.model = model;
}
@Override
public void run() {
while (true) {
synchronized (model) {
if (!model.flag) {
try {
model.wait();//没有资源等待,wait()方法一旦等待,就必须释放锁,从哪里等待,被唤醒后,就从这里醒来
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费了资源,通知生产线程去生产
System.out.println(model.name + " === " + model.age);
model.flag = false;
model.notify(); //唤醒等待的 线程
}
}
}
}
现在,本人再来展示下测试类:
package edu.youzg.about_synchronized.core;
public class Test extends Thread{
public static void main (String[] args) {
Model model = new Model();
ProducerThread producer = new ProducerThread(model);
ConsumerThread consumer = new ConsumerThread(model);
producer.start();
consumer.start();
}
}
那么,本人来展示下运行中的某个结果片段:
(本人 《详解 线程》 博文 链接:https:////www.cnblogs.com/codderYouzg/p/12418954.html)
(本人 《详解 多线程》 博文 链接:https:////www.cnblogs.com/codderYouzg/p/12418935.html)