1. 锁介绍
在Java中每个对象或类都可以当做锁使用,这些锁称为内置锁。
Java中内置锁都是互斥锁。也就是说一个线程获取到锁,其他线程必须等待或阻塞。 如果占用锁的线程不释放锁,其他线程将一直等待下去。锁在同一时刻,只能被一个线程持有。
如果锁是作用于对象,称对象锁。如果锁作用整个类称为类锁。
2. synchronized介绍
- synchronized关键字是锁的一种实现。
- synchronized的加锁和解锁过程不需要程序员手动控制 ,只要执行到synchronized作用范围会自动加锁(获取锁/持有锁),执行完成后会自动解锁(释放锁)。加锁的范围中代码出现异常,自动解锁。
- synchronized具有可见性、有序性和原子性。
- synchronized分多种情况:
- 修饰实例方法(对象锁)
- 修饰静态方法(类锁)
- 修饰代码块(对象锁、类锁)
3. 修饰实例方法
先创建几个线程
Test01 test01 = new Test01();//创建一次对象
for (int i = 0; i < 3; i++) {
/* 每一个线程都会创建一个新对象,不同对象调用加锁的方法,锁失效 */
//Test01 test01 = new Test01(); //创建三次对象
new Thread(new Runnable() {
@Override
public void run() {
test01.test3();
}
}).start();
Thread.sleep(3000);
System.out.println(a);
}
锁类型:使用synchronized修饰实例方法时为对象锁。
锁范围:锁的范围是加锁的方法。
锁生效:必须为同一个对象调用该方法该锁才有作用。
/*
加锁:执行到加锁的方法,自动加锁
释放锁:执行完加锁的方法,自动解锁,出现异常自动解锁
锁范围:加锁的方法
锁类型:对象锁(同一个对象生效)多个线程争夺同一个对象锁
锁失效:多个对象失效
*/
public synchronized void test1(){//实例方法
for (int i = 0; i < 10000; i++) {
a++;
}
}
4. 修饰静态方法
锁类型:使用synchronized修饰静态方法时为类锁。
锁范围:锁的范围是加锁的方法。
锁生效:该类所有的对象调用加锁方法,锁都生效 。必须为同一个类调用该方法锁才有作用。
/*
加锁:执行到加锁的方法,自动加锁
释放锁:执行完加锁的方法,自动解锁,出现异常自动解锁
锁范围:加锁的方法
锁类型:类锁(同一个类生效,和对象无关)多个线程争夺同一个类锁
*/
public static synchronized void teat2(){
for (int i = 0; i < 10000; i++) {
a++;
}
}
5. 修饰代码块
锁代码块是非常重要的地方。添加锁的类型是Object类型。
使用synchronized修饰的代码块语法格式为:
synchronized(锁){
// 内容
}
5.1 锁为固定值
由于固定值属于类且值固定不变,所有的对象调用加锁的代码块,都会争夺锁资源,属于类锁。
/*
加锁:执行到加锁的方法,自动加锁
释放锁:执行完加锁的方法,自动解锁,出现异常自动解锁
锁范围:加锁的代码块
锁类型:类锁(同一个固定值)
锁失效:多个值
*/
final String LOCK = "锁";//固定值
public void test3(){
synchronized (LOCK){
for (int i = 0; i < 10000; i++) {
a++;
}
}
}
5.2 锁为不同内容
public void test4(String s){//s为每个线程的名字,线程名字不同
synchronized (s){//锁不相同,相当于没有加锁,锁资源不需要竞争
for (int i = 0; i < 10000; i++) {
a++;
}
}
}
5.3 锁为this
5.3.1 同一个对象调用加锁方法时:
如果是同一个对象调用synchronized所在方法时,this代表的都是一个对象。this就相当于固定值。所以可以保证结果正确性,属于对象锁。
5.3.2 不同对象调用加锁方法时:
如果不是同一个对象调用synchronized所在方法时,this所代表的对象就不同。相当于锁为不同内容时,锁失效。
/*
加锁:执行到加锁的方法,自动加锁
释放锁:执行完加锁的方法,自动解锁,出现异常自动解锁
锁范围:加锁的代码块
锁类型:对象锁(同一个对象)
锁失效:多个对象
*/
public void test5(){
synchronized (this){//this表示当前对象
for (int i = 0; i < 10000; i++) {
a++;
}
}
}
5.4 锁为class
锁为Class时,是一个标准的类锁,所有的对象调用加锁的代码块都生效。
/*
加锁:执行到加锁的方法,自动加锁
释放锁:执行完加锁的方法,自动解锁,出现异常自动解锁
锁范围:加锁的代码块
锁类型:类锁(同一个类对象生效)
*/
public void test6(){
synchronized (Test01.class){//Test01.class类对象
for (int i = 0; i < 10000; i++) {
a++;
}
}
}
6. 对象锁和类锁
当synchronized修饰静态方法或代码块参数为Class时或代码块参数为固定值,锁为类锁,作用整个类。同一个类使用,锁生效。
当synchronized修饰实例方法或代码块参数为this时,为对象锁,只对当前对象有效。
多个对象使用时,锁生效,使用类锁。
同一对象使用时,锁生效,使用对象锁。
7. 可重入锁
某个线程已经获得了某个锁,允许再次获得锁,就是可重入锁。如果不允许再次获得锁就称为不可重入锁。
synchronized为可重入锁。但可重入锁不仅仅只有synchronized。ReentrantLock也是可重入锁。
Test02 test02 = new Test02();
new Thread(new Runnable() {
@Override
public void run() {
test02.test1();
}
}).start();
Thread.sleep(1000);
public synchronized void test1(){//可重入锁
System.out.println("t1");
//此时还没释放锁
test2();//再次申请锁资源
}
public synchronized void test2(){
System.out.println("t2");
}
7.2 可重入锁底层原理
可重入锁底层原理就是计数器。
当一个线程第一次持有某个锁时会由monitor(监控器)对持有锁的数量加1,当这个线程再次需要碰到这个锁时,如果是可重入锁就对持有锁数量再次加1(如果是不可重入锁,发现持有锁为1了,就不允许多次持有这个锁了,阻塞),当释放锁时对持有锁数量减1,直到减为0,表示完全释放了这个锁。