多线程学习


前言

本篇博客主要介绍多线程的相关知识点。


提示:以下是本篇文章正文内容,下面案例可供参考

一、并发编程的三个重要概念

原子性:一个操作或者多个操作,要么都成功,要么都失败,中间不能由于任何的元素中断。
可见性:在java中用volatile关键字保证
有序性:在java中用happens-before relationship

happens-before规则:
代码的执行顺序,编写在前面的发生在编写在后面的;
unlock必须发生在lock之后;
volatile修饰的变量,对一个变量的写操作先于对该变量的读操作;
传递规则,操作A先于B,B先于C,那么A肯定先于C;
线程启动规则,start方法肯定先于线程run;
线程中断规则,interrupt这个动作,必须发生在捕获该动作之前;
对象销毁规则,初始化必须发生在finilize之前;
线程终结规则,所有的操作都发生在线程死亡之前

二、Thread方法

1. 构造方法

Thread()
Thread(Runnable target)	
Thread(Runnable target, String name)
Thread(String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
Thread(ThreadGroup group, String name)

2. Thread与ThreadGroup的关系

ThreadGroup 可以把thread的名字统一起来。一起处理catch。
ThreadGroup是Java提供的一种对线程进行分组管理的手段,可以对所有线程以组为单位进行操作,如设置优先级、守护线程等。

三、线程的生命周期

在这里插入图片描述

四、终止线程

Java中有三种方法可以终止正在运行的线程
A. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
B. 使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用它们可能产生不可预料的结果。
C. 使用interrupt方法中断线程。

1. 判断线程是否是停止状态(interrupt)

interrupted()			测试当前线程是否已经是中断状态,执行后具有将状态标志置清除为false的功能
isInterrupted()			测试线程是否已经是中断状态,但不清除状态标志。

interrupted()方法的解释:	测试当前线程是否已经中断,当前线程是指运行this.interrupted()方法的线程。
官网对interrupted方法的解释:
测试当前线程是否已经中断。线程的中断状态由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外。) 
如果在sleep状态下停止某一线程,会进入catch语句,并且清除停止状态值,使之变成false。建议使用“抛异常”的方法来实现线程的停止,因为在catch块中还可以将异常向上抛,使线程停止的事件得以传播。

interrupt()方法:
(1) 如果在调用Object类的wait()wait(long)wait(long, int)join()join(long)join(long, int)sleep(long)sleep(long, int)方法时阻塞了此进程,interrupt()方法打断线程,并使其中断状态被清除!并将收到InterruptedException。
(2) 如果此线程在InterruptibleChannel上的IO操作中被阻止,则此通道将被关闭,该线程的中断状态将被设置,并且该线程将收到java.nio.channels.ClosedByInterruptException。
(3) 如果此线程在java.nio.channels.Selector中被阻塞,则将设置该线程的中断状态,并且它将立即从选择操作中返回(可能具有非零值),就像调用选择器的唤醒方法一样。
(4) 如果以上条件均不成立,则将设置该线程的中断状态。
(5) 中断未激活的线程不会产生任何效果。

2. sleep()方法

sleep()方法:
如果任意线程已经中断了当前线程,这个interrupt状态会被清除,会抛出一个异常。
那状态被清除了该怎么办呢?
(1) 捕捉异常后,重新调用Thread.currentThread().interrupt()来修改中断状态,以便线程的interrupt状态能被捕获,退出循环
(2) 抛出异常。

五、synchronized锁升级过程

1. 重量级锁

Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。JDK中对Synchronized做的种种优化,其核心都是为了减少这种重量级锁的使用。JDK1.6以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了“轻量级锁”和“偏向锁”。

2. 轻量级锁

锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。JDK 1.6中默认是开启偏向锁和轻量级锁的,我们也可以通过-XX:-UseBiasedLocking来禁用偏向锁。锁的状态保存在对象的头文件中,以32位的JDK为例:

“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

轻量级锁的加锁过程:
(1) 在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。这时候线程堆栈与对象头的状态如图2.1所示。
(2) 拷贝对象头中的Mark Word复制到锁记录中。
(3) 拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤(4),否则执行步骤(5)。
(4) 如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图2.2所示。
(5) 如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。 而当前线程便尝试使用自旋来获取锁,自旋就是为了不让线程阻塞,而采用循环去获取锁的过程。

轻量级锁的解锁过程:
(1) 通过CAS操作尝试把线程中复制的Displaced Mark Word对象替换当前的Mark Word。
(2) 如果替换成功,整个同步过程就完成了。
(3) 如果替换失败,说明有其他线程尝试过获取该锁(此时锁已膨胀),那就要在释放锁的同时,唤醒被挂起的线程。

3. 偏向锁

引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。上面说过,轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。

偏向锁获取过程:
(1) 访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01 —— 确认为可偏向状态。
(2) 如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤(5),否则进入步骤(3)。
(3) 如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行(5);如果竞争失败,执行(4)。
(4) 如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。
(5) 执行同步代码。

偏向锁的释放:
偏向锁的撤销在上述第四步骤中有提到。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

总结

今天就总结这些啦, 继续加油~。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

要一直坚持呀!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值