浅谈一下Java锁机制

        在学习锁之前,我们先一起来了解几个关于锁的概念。了解之后,再进一步去学习锁的知识。

公平锁和非公平锁

        公平锁:意思就是各个线程之间是公平的,不存在相互竞争的情况,多个线程之间按照申请锁的顺序来获取锁。

        非公平锁:是指多个线程之间获取锁的顺序并不是按照申请的所的顺序,而是通过相互之间的竞争来获取锁,谁抢到了谁就拥有这把锁。

可重入锁

       允许同一个线程重复获取同一个锁,这种能被同一个线程反复获取的锁,就叫做可重入锁。

乐观锁和悲观锁

        乐观锁:顾名思义,代表他这把锁十分乐观。他总是觉得线程中的资源和数据不会被修改。所以他在读取的时候是不会加锁的,但是在乐观锁再进行写入操作的时候会判断数据是否被修改过。

        悲观锁:与乐观锁截然不同,他具有严重的受害心理,他具有强烈的独占和排他性质。他总是认为资源和数据会被别人修改,所以他在持有数据的时候会把资源和数据都锁上,这样其他线程想要请求这个资源就会阻塞,直到悲观锁释放资源为止。

了解完这几个概念之后,我们一起来看看Java中的锁:

Synchronized

        Synchronized,就是一个:非公平,悲观,可重入的锁。

Synchronized关键字的用法

        1.修饰实例方法:默认为this当前方法调用对象;

        2.修饰静态方法:默认为Class对象;

        3.修饰代码块:是某个指定的Java对象;

Synchronized修饰实例方法

        使用this当前对象作锁,锁定该方法,只有获取到this锁,才可以访问当前方法。

        在并发过程中,可以有多个线程同时请求该方法,但是只有一个方法可以获取到锁,执行该方法。

        对于不同的线程,他们所持有的对象,必须是相同的。

有以下两种方法使用Synchronized修饰实例方法,其作用和意义都是相同的。

public class DoSth {
    // 实例方法
    public synchronized void doSth1() {
        // 获取this锁,执行该方法
    }
    
    // 实例方法
    public void doSth2() {
        synchronized(this) {
           // 获取this锁,执行该代码块
        }
    }
}
public static void main(String[] args) {
    // 实例化一个对象
    DoSth do = new DoSth();
	
    // 创建不同的线程1
    Thread t1 = new Thread() {
        public void run() {
            // 使用相同的对象访问synchronized方法
            do.doSth1();
        }
    };
	
    // 创建不同的线程2
    Thread t2 = new Thread() {
        public void run() {
            // 使用相同的对象访问synchronized方法
            do.doSth1();
        }
    };
    
    // 启动线程
    t1.start();
    t2.start();
}

 Synchronized修饰静态方法

        使用当前对象的class对象作为锁,锁定该方法,只有获取到了class锁,才能执行该方法;

        多个线程所持有的对象可以不同,但其class类型必须相同;

public class DoSth {
    // 静态方法
    public synchronized static void doSth1() {
        // 获取当前对象的Class对象锁,执行该方法
    }
    
    // 实例方法
    public static void doSth2() {
        synchronized(this.getClass()) {
        	// 获取当前对象的Class对象锁,执行该代码块
        }
    }
}
public static void main(String[] args) {
    // 创建不同的对象(相同类型)
    DoSth do1 = new DoSth();
    DoSth do2 = new DoSth();

    // 创建不同线程1
    Thread thread01 = new Thread() {
        public void run() {
            // 使用不同的对象访问synchronized方法
            do1.doSth2();
        }
    };

    // 创建不同线程2
    Thread thread02 = new Thread() {
        public void run() {
            // 使用不同的对象访问synchronized方法
            do2.doSth2();
        }
    };

    // 启动线程
    thread01.start();
    thread02.start();
}

 Synchronized修饰代码块

        使用自定义对象作为锁;

synchronized(自定义对象) {
   
}

 Synchronized的底层实现

        Synchronized的底层是由Monitor监视器所实现的,Synchronized代码块是通过一对 monitorenter / monitorexit 指令实现的。线程通过monitorenter指令来尝试获取monitor的所有权,当monitor被占用时就会处于锁定状态。

线程获取monitor的过程

        1.如果monitor的进入数为0,则当前线程可以进入monitor,然后设置进入数为1,代表当前线程为monitor的持有者,即持有锁;

        2.如果当前线程已经持有了monitor,只是重新进入,则monitor的进入数会+1;

        3.如果在当前线程之前,已经有其他线程持有了monitor,则当前线程就会进入阻塞状态;直到monitor的进入数为0的时候,会再次尝试获取monitor;

Synchronized的锁升级机制

        锁升级的针对Synchronized的一种机制,是对于Synchronized的一种优化,JVM会检测不同的情况,自动切换到适应的锁实现,即为锁的升级与降级。

        在JVM中实现了三种类型的锁:偏向锁、轻量级锁、重量级锁

偏向锁

        偏向锁适用于单线程的情况,如果发现有多个线程执行就会自动升级为轻量级锁;

轻量级锁

        轻量级锁适用于多线程串行执行的情况,即多个线程按顺序执行,如果发现多个线程并行执行了就会自动升级为重量级锁;

重量级锁

        重量级锁适用于多线程并行执行。

下图为Synchronized锁升级的原理:

 

ReentartLock

        ReentartLock,就是默认非公平但可实现公平的,悲观,可重入的锁。

        ReentartLock与Synchronized不同的是:在Synchronized中,线程只要获取不到锁,就会进入阻塞状态,一直等待,没有额外的尝试机制;而ReentartLock可以在获取失败后,可以尝试获取锁。

ReentartLock内部结构

        ReentartLock内部共有三个类:Sync、NotfairSync、FairSync

        NotfairSync类继承了Sync类:表示不公平策略获取锁,在获取锁的时候,不会按照公平的原则进行,不会让等待时间最久的线程获得锁,按照抢占机制来争夺锁;

        FairSync类继承Sync类:表示公平策略获取锁,在获取锁的时候,按照公平的原则进行,当资源处于空闲状态时,他总会判断是否有等待时间更长的线程,如果有则将当前线程加入到等待队列的尾部,实现了公平获取原则。

        ReentartLock构造方法:默认采用非公平锁。

public ReentrantLock() {
    // 默认非公平策略
    sync = new NonfairSync();
}

        也可以通过传递参数确定采用公平策略或者是非公平策略,参数为 true表示公平策略,否则,采用非公平策略。

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

 Synchronized和ReentartLock的对比

SynchronizedReentartLock
是一个关键字,依赖于JVM的底层实现是一个类,依赖于JDK API
实现机制Monitor监视器AQS(AbstractQueueSynchronizer)
锁的获取抢占机制可以通过tryLock()尝试获取锁,更加灵活
锁的释放自动释放必须手动释放锁
锁的类型非公平锁公平锁和非公平锁
可重入性可重入可重入

补充:死锁

        死锁是指多个线程之间,都需要获取对方所持有的锁或者资源,所导致的无线等待状态。

例如:

package com.zwd.demo;

public class DeadLock {
	private static Object lockA = new Object();
	private static Object lockB = new Object();
	
	public void add() throws InterruptedException {
		synchronized (lockA) { // 获得lockA的锁
			Thread.sleep(100); // 线程休眠
			synchronized (lockB) { // 获得lockB的锁
				System.out.println("执行add()");
			} // 释放lockB的锁
		} // 释放lockA的锁
	}

	public void dec() throws InterruptedException {
		synchronized (lockB) { // 获得lockB的锁
			Thread.sleep(100); // 线程休眠
			synchronized (lockA) { // 获得lockA的锁
				System.out.println("执行dec()");
			} // 释放lockA的锁
		} // 释放lockB的锁
	}
}

对于上述代码而言:

        当线程1和线程2分别执行add()和dec()时: 

        线程1:进入add(),获得lockA;        线程2:进入dec(),获得lockB;

        线程1:休眠100毫秒,准备获取lockB,失败,等待中...

        线程2:休眠100毫秒,准备获取lockA,失败,等待中...

        现在,两个线程各持有一把锁,而双方都想要获取对方手里的锁,造成了无限等待状态,即为死锁。

        当死锁放生之后,没有任何办法可以解决死锁,只能强制结束JVM进程。

产生死锁的四个条件:

        1.资源互斥:在同一时刻,一把锁只能被一个线程所持有;

        2.不可剥夺:当一个线程以获得了资源,在没有使用完之前,不能被剥夺,只能等待所持有资源的线程自行释放锁;

        3.请求等待:当一个线程想要请求其他资源发生阻塞时,对于自己所持有的资源也不释放;

        4.循环等待:多个线程之间的循环等待;

如何让避免死锁?

        1.每次只占用不超过1个锁;

        2.按照相同的顺序申请锁;

        3.使用信号量;

使用信号量解决死锁:

public class DeadLock {
	private Semaphore semaphoreA = new Semaphore(1);
	private Semaphore semaphoreB = new Semaphore(1);

	public void add() throws InterruptedException {
		semaphoreA.acquire();
		Thread.sleep(1000);
		semaphoreB.acquire();
		try {
			System.out.println("执行add()");
		} finally {
			semaphoreB.release();
			semaphoreA.release();
		}

	}

	public void dec() throws InterruptedException {
		while (semaphoreB.tryAcquire()) {
			Thread.sleep(100);
			if (semaphoreA.tryAcquire()) {
				try {
					System.out.println("执行dec()");
					return;
				} finally {
					semaphoreA.release();
					semaphoreB.release();
				}
			} else {
				semaphoreA.release();
				semaphoreB.release();
			}
		}
	}
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值