Java多线程(二)

一、常见锁

1.1 乐观锁vs悲观锁

乐观锁: 每次获取数据都不担心数据会被修改,每次在获取数据的时候都不加锁,在更新数据的时候需要判断数据是否被修改过,如果数据被其他线程修改过不对数据进行更新,如果没有,进行数据更新,期间可被其他线程读写。

悲观锁: 每次获取数据都担心数据会被修改,每次获取数据时都会加锁,确保自己使用过程中不会被别人修改。

使用场景:乐观锁适合读较多的操作,悲观锁适合写比较多的操作。

1.2 读写锁

读写锁(readers-writer lock):在执行加锁操作时需要额外表明读写意图,复数读者之间并不互斥,而写者则要求与任何人互斥。

1.3 自旋锁

没抢到锁就死等。

自旋锁的缺点:就是如果锁没有很快被释放,则线程其实是光在消耗 CPU 资源,长期在做无用功的。

1.4 可重入锁

可重入锁的字面意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁。比如一个递归函数里有加锁操作,递归过程中这个锁会阻塞自己吗?如果不会,那么这个锁就是可重入锁(因为这个原因可重入锁也叫做递归锁)。

Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括synchronized
关键字锁都是可重入的。

二、CAS

2.1 什么是CAS?

CAS: 全称Compare and swap,字面意思:”比较并交换“,一个 CAS 涉及到以下操作:

我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。

  1. 比较 A 与 V 是否相等。(比较)
  2. 如果比较相等,将 B 写入 V。(交换)
  3. 返回操作是否成功。

当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。可见 CAS 其实是一个乐观锁。

2.2 如何实现CAS?

针对不同的操作系统,JVM 用到了不同的 CAS 实现原理,简单来讲:

  • java 的 CAS 利用的的是 unsafe 这个类提供的 CAS 操作;
  • unsafe 的 CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg;
  • Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 cpu 硬件提供的 lock 机制保证其原子性。

2.3 CAS的应用

实现自旋锁:

public class SpinLock {
	private AtomicReference<Thread> sign = new AtomicReference<>();
	public void lock(){
		Thread current = Thread.currentThread();
		// 不放弃 CPU,一直在这里旋转判断
		while(!sign .compareAndSet(null, current)){
		}
	}
	public void unlock (){
		Thread current = Thread.currentThread();
		sign.compareAndSet(current, null);
	}
}

2.4 CAS存在的问题

2.4.1 自旋

  • 如果CAS一直不成功,那么就会出现自旋的情况,浪费资源

2.4.2 ABA问题

如果一个线程(1)要把值A变为B,此时另一个线程(2)抢先把A变成了B然后又变回A,此时线程(1)再去判断就发现值A没有变过就会去修改值A,这个操作看起来似乎没有什么问题,但是我们使用下面这个例子来看:

现在有一个由单链表实现的栈,栈中有元素A,B
在这里插入图片描述
栈顶元素是A,并且A.next = B,现在有一个线程(1)想将栈顶元素变为B,另一个线程(2)抢先操作先将A、B出栈,然后再将C、A入栈

在这里插入图片描述
此时栈顶元素为A,然后现在线程(1)再去操作,发现栈顶元素是A于是将栈顶元素修改为B,因为B.next = null ,所以我们的元素C就不见了。

三、synchronized背后的原理

JVM 将 synchronized 锁分为 无锁、偏向锁、轻量级锁、重量级锁 状态。会根据情况,进行依次升级。

无锁:没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功,其他修改失败的线程会不断重试直到修改成功。

偏向锁:对象的代码一直被同一线程执行,不存在多个线程竞争,该线程在后续的执行中自动获取锁,降低获取锁带来的性能开销。偏向锁,指的就是偏向第一个加锁线程,该线程是不会主动释放偏向锁的,只有当其他线程尝试竞争偏向锁才会被释放。

偏向锁的撤销,需要在某个时间点上没有字节码正在执行时,先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态。

  • 如果线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁;
  • 如果线程处于活动状态,升级为轻量级锁的状态。

轻量级锁:轻量级锁是指当锁是偏向锁的时候,被第二个线程 B 所访问,此时偏向锁就会升级为轻量级锁,线程 B会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。

当前只有一个等待线程,则该线程将通过自旋进行等待。但是当自旋超过一定的次数时,轻量级锁便会升级为重量级锁;当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁。

重量级锁:指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。

重量级锁通过对象内部的监视器(monitor)实现,而其中 monitor 的本质是依赖于底层操作系统的 Mutex Lock实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。

区别:

  • synchronized是java中的一个关键字,是Java语言内置的特性
  • synchronized在执行完代码块后会自动释放锁
  • synchronized如果持有锁的线程不释放锁,就会一直阻塞
  • synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生
  • lock是一个类可以根据这个类实现同步
  • lock必须使用unlock释放锁
  • lock通过tryLock()方法不让等待的线程一直无期限地等待下去,在time时间内如果没有获取到锁就先去做其他事情。tryLock()、tryLock(long time, TimeUnit unit)是有返回值的,表示尝试获取锁,获取到就返回true。
  • 而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
  • 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到
  • Lock可以提高多个线程进行读操作的效率。(用synchronize一个线程读完,另一个线程才能读)
  • ReentrantReadWriteLock的readLock()和writeLock()用来获取读锁和写锁

如果竞争不激烈两者性能差不多,如果竞争激烈,lock的性能较好。

四、Callable 的使用

五、java.util.concurrent 包下的常见类

线程同步?

同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。

线程异步?

异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。

线程同步的方式有哪些?

  • 互斥量(mutex):引入互斥锁,得到锁的线程执行(读,修改,写)的操作,没有获得锁的线程只能等待,不能共享数据。
  • 条件变量:利用线程间共享的全局变量进行同步的一种机制。
  • 信号量
  • 读写锁

5.1 Semaphore

一个计数信号量,主要用于控制多线程对共同资源库访问的限制。

5.2 ReentrantLock

可重入互斥锁。

5.3 CountDownLatch

好像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩。

六、死锁

死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

死锁产生的四个必要条件

  • 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
  • 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
  • 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
  • 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。

① 可剥夺资源:在线程满足条件时,释放掉已占有的资源
② 资源一次性分配(破坏请求与保持条件)
③ 资源有序分配:系统为每类资源赋予一个编号,每个线程按照编号递 请求资源,释放则相反

七、银行家算法-操作系统资源分配

一个银行家如何将一定数目的资金安全地借给若干个客户,使这些客户既能借到钱完成要干的事,同时银行家又能收回全部资金而不至于破产。

银行家算法需要确保以下四点:
1、当一个顾客对资金的最大需求量不超过银行家现有的资金时就可接纳该顾客;
2、顾客可以分期贷款, 但贷款的总数不能超过最大需求量;
3、当银行家现有的资金不能满足顾客尚需的贷款数额时,对顾客的贷款可推迟支付,但总能使顾客在有限的时间里得到贷款;
4、当顾客得到所需的全部资金后,一定能在有限的时间里归还所有的资金。

八、线程池

8.1 线程池优点

重用线程池中的线程,减少创建和销毁线程带来的开销。

8.2 线程池参数

在这里插入图片描述

8.3 线程池工作一次流程

在这里插入图片描述

8.4 功能线程池

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值