前言
java中的锁一般指的是互斥锁,只是一般,高阶的本文暂不讨论。
什么是互斥锁?
当多个线程对共享资源访问的时候,只能有一个线程可以获得该共享资源的锁,当线程A尝试获取线程B的锁时,线程A必须等待或者阻塞,直到线程B释放该锁为止,否则线程A将一直等待下去。
特征
1.锁只能是引用类型
2.执行线程进入synchronized块之前会自动获得锁,并且获得锁的唯一途径就是进入这个内部锁保护的同步块或方法
3.无论是通过正常语句退出还是执行过程中抛出了异常,线程都会在放弃对synchronized块的控制时自动释放锁
4.在java中实现锁机制不仅仅限于使用synchronized关键字,JDK1.5之后提供的Lock
5.一个synchronized块包含两个部分:锁对象的引用,以及这个锁保护的代码块
6.synchronized不具有继承性(在父类中加上synchronized关键字,子类重写父类方法测试一下加上synchronized关键字和不加关键字的区别即可。)
类锁/对象锁
根据使用方式的不同一般我们会将锁分为对象锁和类锁,两个锁是有很大差别的。
类锁
0.所谓的类锁,不过是Class对象的锁而已。获取类的Class对象有好几种,最简单的就是[类名.class]的方式。
1.类锁是作用在静态方法或者Class对象上面的,每个类只有一个Class对象,所以类锁只有一个。
2.类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定的是实例方法还是静态方法区别的 。
3.类锁是用来控制静态方法(或静态变量互斥体)之间的同步
// 类锁:形式1
public class Test {
public static synchronized void Method1() {
System.out.println("我是类锁一号");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 类锁:形式2
public void Method2() {
synchronized (Test.class) {
System.out.println("我是类锁二号");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
对象锁
1.对象锁是作用在实例方法或者一个对象实例上面的,一个类可以有多个实例对象,因此一个类的对象锁可能会有多个。
2.对象锁是用来控制实例方法之间的同步
对象锁又分为:一般对象锁和方法锁。
// 对象锁:形式1(方法锁)
public class Test {
public synchronized void Method1() {
System.out.println("我是对象锁也是方法锁");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 对象锁:形式2(代码块形式)
public void Method2() {
synchronized (this) {
System.out.println("我是对象锁");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
死锁
介绍
- 当线程需要同时持有多个锁时,有可能产生死锁。考虑如下情形:
- 线程A当前持有互斥所锁lock1
- 线程B当前持有互斥锁lock2。
- 接下来,当线程A仍然持有lock1时,它试图获取lock2,因为线程B正持有lock2,因此线程A会阻塞等待线程B对lock2的释放。
- 如果此时线程B在持有lock2的时候,也在试图获取lock1,因为线程A正持有lock1,因此线程B会阻塞等待A对lock1的释放。
- 二者都在等待对方所持有锁的释放,而二者却又都没释放自己所持有的锁,这时二者便会一直阻塞下去。这种情形称为死锁。
代码
package com.zj.多线程.sychronized.死锁;
public class DeadLock {
//1:2个不同的锁
//2:任何一方都没法释放对方所需要的锁,因为双方要释放对方所需要的锁的条件就是对方的锁。
/*
* A和B
* A说,我想把我的锁给你,但是没有你的锁,我没法给你
* 再比如,2扇门,A的门把B关外面,B的门把A关外面
* */
public static void main(String[] args) throws InterruptedException {
final Lock lock1 = new Lock();
final Lock lock2 = new Lock();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (lock1) {
System.out.println("thread 111 out");
synchronized (lock2) {
System.out.println("thread 111 in");
}
}
// System.out.println("thread 111 in");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (lock2) {
System.out.println("thread 222 out");
synchronized (lock1) {
System.out.println("thread 222 in");
}
}
// System.out.println("thread 222 in");
}
}
}).start();
}
//锁对象
static class Lock {
Lock() {
}
}
}
规避
- 只在必要的最短时间内持有锁,考虑使用同步语句块代替整个同步方法;
- 尽量编写不在同一时刻需要持有多个锁的代码,如果不可避免,则确保线程持有第二个锁的时间尽量短暂;
- 创建和使用一个大锁来代替若干小锁,并把这个锁用于互斥,而不是用作单个对象的对象级别锁;