线程,看这一篇就够啦
文章目录
-
- 多线性
-
- 单进程或多核线性体系结构
- 多个独立的过程通过共享资源进行协作
- 串行与并行处理的区别在于数据传输路径
- 通过动态地址空间实现高效的上下文切换
- 基于半原子操作的安全一致性算法
- 支持异步协作的任务执行模式
- 协作执行中可能出现的状态停滞现象
- 在何种场景下采用多核心处理模式更有优势?
【采用多线程架构
-
三 线程的状态:
-
新建状态:处于新建状态的新
-
就绪状态:处于就绪状态
-
运行中:运行中
-
阻塞中:等待处理的状态
-
退出系统:退出系统中的
- 四 java并发的三大特性:
-
- 原子性
-
有序性
-
可见性
- 五 实例变量和线程安全
-
- 不共享数据
-
共享数据
- 六 线程方法
- 七 方法使用示例
-
- 如何线程终止
-
如何设置守护线程
- 八 线程的优先级
-
-
synchronized(自旋锁机制的关键特性)
- 简介
- 详细阐述多对象自旋锁机制
- 避免脏读问题的影响
- 探讨自旋锁重入机制
- 提供同步代码示例说明
- 自旋锁字符串操作功能的实现细节
-
可变
-
1. 概述
-
2. 可变性的探讨
-
3. 缺乏原子性的特点
-
4. 对比分析:synchronized与可变性的区别
-
线程通信
-
- 一 wait/notify机制介绍
-
- 常用方法
-
-
第二章 代码实现
-
第二节(1)通信示例分析
-
第二节(2)线程运行状态概述
-
第二节(3)notify()锁的释放问题研究
-
第二节(4)中断处理与等待机制交互分析
-
三 管道输入/输出流
-
- 管道输入/输出流实例
-
四 Thread.join()的使用
-
- 4.1 join方法使用
-
-
4.2 join(long millis)方法的使用
-
-
线程本地机制的应用共包含五个方面
- 包括资源管理、数据保护以及同步控制等核心功能
-
具体实现部分主要聚焦于以下几点:
- 线程间数据隔离特性
- 这种机制确保不同线程之间能够独立访问本地变量
- 支持可继承性设计以提升系统的扩展性
- 针对多线程环境提供高效的同步管理解决方案
- 线程间数据隔离特性
-
InheritableThreadLocal类作为这一机制的核心组件
-
其定义遵循严格的继承原则以保证灵活性与可维护性
-
提供了基础的数据存储与访问接口以简化开发流程
- Lock
-
- 一 Lock接口
-
- 1.1 Lock
- 1.2 Lock的使用
- 1.3 Lock接口的特性
- 1.4 Lock的方法:
-
-
第二部分 ReentrantLock接口的实现
- 2.1节 构造方法
- 2.2节 ReentrantLock的方法
- 2.3节 ReentrantLock示例
关于Condition接口的概述
* 四 ReentrantReadWriteLock
* * 4.1 简介
* 4.2 ReentrantReadWriteLock
* * 公平性选择
* 可重入
* 读写锁
* 锁降级
* 4.3 ReentrantReadWriteLock
* * 读读共享
* 写写互斥
* 读写互斥
* 至此线程就讲完了。
多线程
一 进程和多线程
进程
每个程序都与一个进程相对应。每个程序具有静态属性,并且通常存储在外部存储设备上。运行期间会被加载到内存中形成进程。
这个变化无常的过程就是执行一次程序. 这个操作流程就是系统处理任务的基本单元. 当系统启动一个应用程序时, 就相当于启动了一个完整的进程中从生成到终止的状态变化.
线程
线程是进程内独立执行特定任务的基本单位,在现代计算机系统中被广泛采用作为并行处理的核心机制。每个进程必须包含至少一个线程以保证基本的操作响应性;当一个进程包含超过一个线程时,则称为多线程处理以提高系统的吞吐量和响应速度。
许多线程共同占用一块内存空间以及一组系统资源;每当系统生成一个新的线程时,在生成或转换操作上所需的开销相对较小;正是由于这一特性使得计算机科学中将这类操作称为轻量级进程。
并发和并行
并发:在同一时间点只有一个线程运行,在一毫秒内CPU就快速切换到下一个线程间切换,在宏观上看起来像是多个线程同时执行
并行:同一时刻,多条指令被多个处理机同时执行
上下文切换
在处理线程切换时,CPU必须负责维护当前线程的任务信息,并在随后切换回该线程时依据存储的任务信息继续执行相应的操作流程。同时完成这一操作过程所需的数据存储操作会占用一定系统资源
上下文切换主要包含让步切换与抢占切换两种类型。其中一种方式是执行线段主动释放CPU资源,并以降低锁竞争并采用CAS算法来防止死锁;另一种方式是当时间片用尽时该进程被迫放弃CPU资源,并且可以通过适当减少进程数量并采用协程机制来实现。
注:
- 传统操作系统中,在CPU上执行的基本单位是进程,在执行过程中不仅负责管理系统的资源,并且承担CPU分配的任务。
- 在同一进程中多个任务并发时需要切换到不同线程进行处理,在这种情况下同一个进程中不需要切换任务以避免频繁的系统调用。
- 线程之间的切换会消耗系统资源这也是线程数量过多带来的弊端频繁切换会导致大量 CPU 资源被耗尽。
- 每个Java程序都作为一个独立的进程运行在运行过程中JVM负责进行内存分配而多个线程共享JVM提供的堆内存每个线程还拥有自己的虚拟机栈以及程序计数器。
- 进程间的通信较为复杂因为每个进程都是独立的程序要想实现它们之间的通信难度较大而在这种情况下多个线程共享堆内存使得线程之间的通信相对更加容易。
CAS算法
CAS(Compare-and-swap 操作)是一种无阻塞算法,在不使用锁的情况下实现多线程之间变量的一致性维护。
协程
协程也可称为微线程或轻量级线程,在内存占用上更为节省且运行更加灵活。如Lua和Ruby等编程语言均具备协程概念。Java能够使用开源协限定存库Quasar。
死锁
死锁就是两个线程相互等待,程序不终止也无法执行。
如何避免:
- 防止同一时间点上一个线程获取多个互斥资源
- 确保同一时间点上一个线程仅能使用单一互斥资源
- 建议采用定时互斥机制,并基于指定超时值的一次性锁定机制来实现对内部单个资源的控制
为什么要使用多线程
程序运行加快的原因是运行方式从串行运行变为并发运行,效率会提高。
二 使用多线程
2.1继承Thread类
MyThread.java
public class MyThread extends Thread {
@Override
public void run(){
System.out.println("继承Thread启动一个线程");
}
//test
public static void main(String[] args) {
new MyThread().start();
System.out.println("main finished");
new MyThread().start();
}
}
运行结果:
main finished
继承Thread启动一个线程
继承Thread启动一个线程
从运行结果可以看出可知:线程被视为一个子任务而CPU则以无规律的时间段调用线程中的run方法
2.2实现Runnable接口
采用Runnable接口的方式进行多线程开发,在Java中虽然遵循单一继承原则但仍然支持多个接口的实现。
MyRunnable.java
public class MyRunnable implements Runnable {
public void run() {
System.out.println("实现Runnable启动一个线程");
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
new Thread(myRunnable).start();
}
}
运行结果:
实现Runnable启动一个线程
实现Runnable启动一个线程
2.3实现Callable接口,callable+futureTask
MyThread3.java
public class MyThread3 implements Callable<String> {
public String call() throws Exception {
return "callable+futureTask,通过future.get()获取";
}
public static void main(String[] args) throws Exception{
Callable<String> myThread3=new MyThread3();
FutureTask<String> futureTask=new FutureTask<String>(myThread3);
new Thread(futureTask).start();
//通过futureTask.get()获取结果
System.out.println(futureTask.get());
}
}
运行结果
callable+futureTask,通过future.get()获取
2.4实现Callable接口,线程池+future
MyThread4.java
public class MyThread4 {
public static void main(String[] args) throws Exception {
//缓存线程池,4大线程池之一
ExecutorService threadPool = Executors.newCachedThreadPool();
Future<String> future = threadPool.submit(new Callable<String>() {
public String call() throws Exception {
return "callable+线程池,通过future.get()获取";
}
});
System.out.println(future.get());
}
}
运行结果
callable+线程池,通过future.get()获取
三 线程的状态:
新建状态 new
MyThread2 myThread2=new MyThread2();
当new一个线程后,该线程处于新建状态。
就绪状态 runnable
new Thread(myThread2).start();
启动 start() 方法后会使得该线程处于就绪状态;随后该线程会被放入 ready queue,并在 CPU 分配时间片之前保持静默。
运行状态 running
public void run() {
System.out.println(Thread.currentThread().getName()+"实现Runnable启动一个线程");
}
- 线程获取CPU资源权限后运行线程体run()方法。在CPU分配时间内若run()未能完成,则该线程的状态将转换为等待状态。
- 当一个线程从运行状态转变为就绪状态时, CPU会保存该线程当前的上下文信息,以便在必要时重新切换回该线程以继续执行任务,而多线程环境下的上下文切换过程通常会占用一定的CPU资源。
阻塞状态 blocked
public static void main(String[] args) {
MyThread myThread=new MyThread();
myThread.start();
try {
myThread.sleep(30);
myThread.join();
myThread.wait();
}catch (Exception e){
e.printStackTrace();
}
}
死亡状态 dead
* 线程体run()方法执行完毕会进入死亡状态。
* 死亡线程是无法start()的。
注: run()与start()的主要区别在于:run()方法本身即为线程的执行主体,在该方法体内可完成具体的逻辑操作;而start()则是一个启动特定线程的操作函数,在调用start()后会创建并启动新的线程,并将该新线程指定为负责执行后续操作的任务。具体来说,在调用start()之后所创建的新的运行态线程会自动执行其对应的run()方法以完成相应的功能实现。
四 java并发的三大特性:
原子性
有序性
指令重排
内存屏障
可见性
volatile确保了顺序性和可访问性。当一个变量被声明为volatile时,它使得该变量在多线程环境中保持可见。
volatile does not guarantee atomicity, which implies that when multiple threads attempt to modify the variable simultaneously, there will still be concurrent issues.
AQS:abstractQueuedSynchronizer 抽象队列同步器
五 实例变量和线程安全
定义线程类中的实例变量针对其他线程可以有共享和不共享之分
不共享数据
NoShare.java
public class NoShare extends Thread {
private int count = 5;
public NoShare(String name) {
super();
this.setName(name);
}
@Override
public void run() {
super.run();
while (count > 0) {
count--;
System.out.println("由 " + Noshare.currentThread().getName()
+ " 计算,count=" + count);
}
}
public static void main(String[] args) {
NoShare a = new NoShare("A");
NoShare b = new NoShare("B");
NoShare c = new NoShare("C");
a.start();
b.start();
c.start();
}
}
运行结果:
由 B 计算,count=4
由 A 计算,count=4
由 A 计算,count=3
由 A 计算,count=2
由 A 计算,count=1
由 A 计算,count=0
由 B 计算,count=3
由 B 计算,count=2
由 B 计算,count=1
由 B 计算,count=0
由 C 计算,count=4
由 C 计算,count=3
由 C 计算,count=2
由 C 计算,count=1
由 C 计算,count=0
可以看到每个线程都拥有独立的实例变量counter,并且彼此之间没有相互影响
共享数据
Share.java
public class Share extends Thread {
private int count = 5;
@Override
public void run() {
super.run();
count--;
System.out.println("由 " + Share.currentThread().getName() + " 计算,count=" + count);
}
public static void main(String[] args) {
Share share = new Share();
//下列线程都是通过share对象创建的
Thread a = new Thread(share, "A");
Thread b = new Thread(share, "B");
Thread c = new Thread(share, "C");
Thread d = new Thread(share, "D");
Thread e = new Thread(share, "E");
a.start();
b.start();
c.start();
d.start();
e.start();
}
}
运行结果:
由 A 计算,count=4
由 B 计算,count=3
由 C 计算,count=2
由 E 计算,count=1
由 D 计算,count=0
六 线程方法
-
currentThread()
返回对当前正在执行的线程对象的引用。 -
getId()
返回此线程的标识符 -
getName()
返回此线程的名称 -
getPriority()
返回此线程的优先级 -
isAlive()
测试这个线程是否还处于活动状态。
活动状态是什么呢?
活动状态即为线程已处于启动状态但尚未停止运行的情况。该状态包括线程正在运行中以及即将投入运行两种情形。
通过调用 sleep(long millis) 函数可以使当前线程进入指定的毫秒睡眠状态(即暂时停止执行),其效果主要受系统的定时器精确度和调度机制的影响。
-
interrupt()
中断线程。 -
interrupted(): 判断当前线程是否处于被中断的状态,并使相关状态标志被重置为False。
-
isInterrupted(): 检查Thread对象是否处于被中断的状态,并无法获取相关信息。
-
setName(String name)
将此线程的名称更改为等于参数 name 。 -
isDaemon()
测试这个线程是否是守护线程。 -
setDaemon(boolean on)
将此线程标记为 daemon线程或用户线程。
-
通常情况下,在主线程序执行过程中会启动多个子程序以分担工作量。如果某个子程序需要执行耗时且复杂的运算任务,在主线程序完成其他常规操作后需要依赖于该特定的子程序结果,则必须先等待该特定的子程序完成运算过程。为了实现这一目标,在特定条件下可以使用join()方法来实现与目标子程序同步运行的功能。
join()方法的主要作用是:用于等待指定的子进程或协处理器完成其当前执行任务。需要注意的是,在调用完join()方法之后的所有代码块将不会立即执行,而是会等到指定的目标进程(通常是主进程)完成了当前的任务之后才会继续运行下去。
也就是说,在调用join()方法之后的所有代码块将不会立即执行直到指定的目标进程(通常是主进程)完成了当前的任务之后才会继续运行下去。
yield()方法的作用是将当前的CPU资源分配给其他任务使用,并放弃自己的CPU时间片。注意:释放的时间不确定,在某些情况下可能会立即重新获取CPU资源片。
- setPriority(int newPriority)
更改此线程的优先级
七 方法使用示例
如何线程终止
使用isInterrupted()+return
TestInterruptUseReturn.java
public class TestInterruptUseReturn extends Thread {
@Override
public void run() {
while (true) {
if (this.isInterrupted()) {
System.out.println("ֹͣ停止了!");
return;
}
System.out.println("time=" + System.currentTimeMillis());
}
}
public static void main(String[] args) throws InterruptedException {
TestInterruptUseReturn testInterruptUseReturn = new TestInterruptUseReturn();
testInterruptUseReturn.start();
Thread.sleep(2000);
testInterruptUseReturn.interrupt();
}
}
运行结果:
time=1547013999650
time=1547013999650
time=1547013999650
time=1547013999650
time=1547013999650
time=1547013999650
time=1547013999650
ֹͣ停止了!
如何设置守护线程
通过调用thead 类的setDaemon(true)方法配置当前线程为daemon thread
TestDaemon.java:
public class TestDaemon extends Thread {
private int i = 0;
@Override
public void run() {
try {
while (true) {
i++;
System.out.println("i=" + i);
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
TestDaemon testDaemon = new TestDaemon();
testDaemon.setDaemon(true);
testDaemon.start();
Thread.sleep(5000);
System.out.println("停止!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
i=42
i=43
i=44
i=45
i=46
i=47
i=48
i=49
i=50
停止!
注意事项 :
setDaemon(true)应在start()方法之前设置为true, 否则会导致IllegalThreadStateException异常。
由守护线程中所产生的新线程同样是守护线程。
并非所有任务都可被分配给守护线程执行,如读写操作或计算逻辑。
八 线程的优先级
每个线程都拥有独特的优先级等级,并由程序可依据各线程的优先权设置其重要程度。当多个线程处于就绪状态时,系统将按照各线程的优先权顺序选择最先启动的那个。然而需要注意的是,并非所有低 优先级的任务都无法运行;它们只是启动机会相对较少而已。具体而言,在多数情况下,垃圾回收相关的特殊线程通常被赋予较低的优先权等级。因此,在多数情况下,这类垃圾回收相关的任务无法得到及时处理。
- 线程的优先级具有遗传特性:若A线程启动B线程,则B线程的优先级与A相同。
- 线程的优先级呈现随机性:即并非每次都能立即执行完毕。
在Thread类中使用成员变量来具体表示不同线程的优先等级。这些成员变量包括以下几种特定值:** Thread.MIN_Priority** 对应于整数值 1、** Thread.NORM_Priority** 对应于整数值 5 以及 ** Thread.MAX_Priority** 对应于整数值 10 。每个线程都有一个优先等级,在整个范围从 ** Thread.MIN_Priority 到 ** Thread.MAX_Priority 之间变化;而初始状态下,默认设置为 ** Thread.NORM_Priority 。
TestPriorityFather.java:
public class TestPriorityFather extends Thread {
@Override
public void run() {
System.out.println("Father run priority=" + this.getPriority());
TestPrioritySon testPrioritySon = new TestPrioritySon();
testPrioritySon.start();
}
}
TestPrioritySon.java
public class TestPrioritySon extends Thread {
@Override
public void run() {
System.out.println("son run priority=" + this.getPriority());
}
public static void main(String[] args) {
System.out.println("main thread begin priority="
+ Thread.currentThread().getPriority());
Thread.currentThread().setPriority(4);
System.out.println("main thread end priority="
+ Thread.currentThread().getPriority());
TestPriorityFather testPriorityFather=new TestPriorityFather();
testPriorityFather.start();
}
}
运行结果:
main thread begin priority=5
main thread end priority=4
Father run priority=4
son run priority=4
synchronized
一 简介
synchronized是一种独占锁,也是悲观锁。
在JavaSE 1.6版本之后增加了偏向锁和轻量级锁等优化措施以及其它改进措施后,在某些特定情况下不再显得那么明显了。
二 synchronized锁多个对象
先看例子:
HasSelfPrivateNum.java
public class HasSelfPrivateNum {
private int num = 0;
public synchronized void addName(String username) {
try {
if (username.equals("a")) {
num = 100;
System.out.println("a set over!");
Thread.sleep(2000);//如果去掉thread.sleep(2000),那么运行结果就会显示为同步的效果
} else {
num = 200;
System.out.println("b set over!");
}
System.out.println(username + " num=" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
HasSelfPrivateNum hasSelfPrivateNum = new HasSelfPrivateNum();
HasSelfPrivateNum hasSelfPrivateNum1 = new HasSelfPrivateNum();
ThreadA threadA = new ThreadA(hasSelfPrivateNum);
ThreadB threadB = new ThreadB(hasSelfPrivateNum1);
threadA.start();
threadB.start();
}
}
ThreadA.java
public class ThreadA extends Thread {
private HasSelfPrivateNum hasSelfPrivateNum;
public ThreadA(HasSelfPrivateNum hasSelfPrivateNum) {
super();
this.hasSelfPrivateNum = hasSelfPrivateNum;
}
@Override
public void run() {
hasSelfPrivateNum.addName("a");
}
}
ThreadB.java
public class ThreadB extends Thread {
private HasSelfPrivateNum hasSelfPrivateNum;
public ThreadB(HasSelfPrivateNum hasSelfPrivateNum) {
this.hasSelfPrivateNum = hasSelfPrivateNum;
}
@Override
public void run() {
hasSelfPrivateNum.addName("b");
}
}
运行结果:
a set over!
b set over!
b num=200
a num=100
a num=100停顿一会才执行
由于采用了带有synchronized关键字的方法,并非所有线程都能直接访问同一个对象。具体而言,在某个特定的实例上实现这一机制时,并非所有线程都能直接访问同一个对象。在上述代码中创建了两个HasSelfPrivateNum实例类后,则会生成对应的两个互斥锁。
三 脏读
发生脏读的情况是在读取实例变量时,此值已经被其他线程更改过。
PublicFiled.java
public class PublicFiled {
public String username = "A";
public String password = "AA";
public synchronized void setValue(String username, String password) {
try {
this.username = username;
Thread.sleep(5000);
this.password = password;
System.out.println("setValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//该方法前加上synchronized关键字就同步了
public void getValue() {
System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password);
}
public static void main(String[] args) {
try {
PublicFiled publicFiled = new PublicFiled();
DirtyReadA dirtyReadA = new DirtyReadA(publicFiled);
dirtyReadA.start();
Thread.sleep(200);
publicFiled.getValue();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
DirtyReadA.java
public class DirtyReadA extends Thread {
private PublicVar publicVar;
public DirtyReadA(PublicVar publicVar) {
super();
this.publicVar = publicVar;
}
@Override
public void run() {
super.run();
publicVar.setValue("B", "BB");
}
}
运行结果:
getValue method thread name=main username=B password=AA
setValue method thread name=Thread-0 username=B password=BB
解决办法:getValue()方法前加上synchronized关键字即可。
加上synchronized关键字后的运行结果:
setValue method thread name=Thread-0 username=B password=BB
getValue method thread name=main username=B password=BB
四 synchronized锁重入
可重入锁定的概念是指:自己能够再次获得自己的内部锁定。例如,在一个线程中获得了某对象的一个内部锁定时(即获得了该对象的一个内部锁定),当前该对象的内部锁定还未被释放(即未被释放),那么当线程试图再次获取该对象内部锁定时仍然可以成功(即能够成功)。如果无法进行重入定点,则会导致死锁问题的发生。
MyThread.java
public class MyThread extends Thread {
@Override
public void run() {
MyThread myThread=new MyThread();
myThread.service1();
}
public synchronized void service1() {
System.out.println("service1");
service2();
}
public synchronized void service2() {
System.out.println("service2");
service3();
}
public synchronized void service3() {
System.out.println("service3");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
运行结果:
service1
service2
service3
另外可重入锁也支持在父子类继承的环境中
Father.java
public class Father {
public int i = 10;
synchronized public void doFatherMethod() {
try {
i--;
System.out.println("father print i=" + i);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Sub.java
public class Sub extends Father {
synchronized public void doSubMethod() {
try {
while (i > 0) {
i--;
System.out.println("sub print i=" + i);
Thread.sleep(100);
this.doFatherMethod();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
TestFatherAndSub.java
public class TestFatherAndSub extends Thread {
@Override
public void run() {
Sub sub = new Sub();
sub.doSubMethod();
}
public static void main(String[] args) {
TestFatherAndSub testFatherAndSub = new TestFatherAndSub();
testFatherAndSub.start();
}
}
运行结果:
sub print i=9
father print i=8
sub print i=7
father print i=6
sub print i=5
father print i=4
sub print i=3
father print i=2
sub print i=1
father print i=0
在父子类继承关系存在的条件下,在这种情况下,在这种情况下,在这种情况下,在这种情况下,在这种情况下,在这种情况下,在这种情况下,在这种情况下,在这种情况下,在这种情况下,在这种情况下,在这种情况下,在这种情况下,在这种情况下,在这种情况下,在这种情况下在这样的场景下
出现异常时,其持有的锁会自动释放。
注意:
-
同步不具备继承特性
-
假设父类中存在一个带有synchronized关键字的方法,则子类继承并重写了该方法。
但同步无法被继承,
因此,在子类方法中仍需手动添加该关键字。- synchronized是串行
Task.java
public class Task {
private String getData1;
private String getData2;
public synchronized void doLongTimeTask() {
try {
System.out.println("begin task");
Thread.sleep(3000);
getData1 = "长时间处理任务后从远程返回的值1 threadName=" + Thread.currentThread().getName();
getData2 = "长时间处理任务后从远程返回的值2 threadName=" + Thread.currentThread().getName();
System.out.println(getData1);
System.out.println(getData2);
System.out.println("end task");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Task task = new Task();
SynThread1 synThread1 = new SynThread1(task);
synThread1.start();
SynThread2 synThread2 = new SynThread2(task);
synThread2.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long beginTime = CommonUtils.beginTime1;
if (CommonUtils.beginTime2 < CommonUtils.beginTime1) {
beginTime = CommonUtils.beginTime2;
}
long endTime = CommonUtils.endTime1;
if (CommonUtils.endTime2 > CommonUtils.endTime1) {
endTime = CommonUtils.endTime2;
}
System.out.println("耗时:" + ((endTime - beginTime) / 1000));
}
}
SynThread1.java
public class SynThread1 extends Thread{
private Task task;
public SynThread1(Task task) {
super();
this.task = task;
}
@Override
public void run() {
super.run();
CommonUtils.beginTime1 = System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime1 = System.currentTimeMillis();
}
}
SynThread2.java
public class SynThread2 extends Thread{
private Task task;
public SynThread2(Task task) {
super();
this.task = task;
}
@Override
public void run() {
super.run();
CommonUtils.beginTime2 = System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime2 = System.currentTimeMillis();
}
}
运行结果:
begin task
长时间处理任务后从远程返回的值1 threadName=Thread-0
长时间处理任务后从远程返回的值2 threadName=Thread-0
end task
begin task
长时间处理任务后从远程返回的值1 threadName=Thread-1
长时间处理任务后从远程返回的值2 threadName=Thread-1
end task
耗时:6
synchronized程序运行时间较长, 可以通过采用同步代码块的方式来优化此问题
五 synchronized同步代码块示例
修改Task.java中的doLongTimeTask()如下:
public void doLongTimeTask() {
try {
System.out.println("begin task");
Thread.sleep(3000);
// getData1 = "长时间处理任务后从远程返回的值1 threadName=" + Thread.currentThread().getName();
// getData2 = "长时间处理任务后从远程返回的值2 threadName=" + Thread.currentThread().getName();
String privateGetData1 = "长时间处理任务后从远程返回的值1 threadName=" + Thread.currentThread().getName();
String privateGetData2 = "长时间处理任务后从远程返回的值2 threadName=" + Thread.currentThread().getName();
synchronized (this) {
getData1 = privateGetData1;
getData2 = privateGetData2;
}
System.out.println(getData1);
System.out.println(getData2);
System.out.println("end task");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果:
begin task
begin task
长时间处理任务后从远程返回的值1 threadName=Thread-0
长时间处理任务后从远程返回的值2 threadName=Thread-0
end task
长时间处理任务后从远程返回的值1 threadName=Thread-1
长时间处理任务后从远程返回的值2 threadName=Thread-1
end task
耗时:3
synchronized(object)代码块间使用
MyObject.java
public class MyObject {
}
SynObjThread1.java
public class SynObjThread1 extends Thread {
private TestSynObject testSynObject;
private MyObject object;
public SynObjThread1(TestSynObject testSynObject, MyObject object) {
super();
this.testSynObject = testSynObject;
this.object = object;
}
@Override
public void run() {
super.run();
testSynObject.testMethod(object);
}
}
SynObjThread2.java
public class SynObjThread2 extends Thread{
private TestSynObject testSynObject;
private MyObject object;
public SynObjThread2(TestSynObject testSynObject, MyObject object) {
super();
this.testSynObject = testSynObject;
this.object = object;
}
@Override
public void run() {
super.run();
testSynObject.testMethod(object);
}
}
TestSynObject.java
public class TestSynObject {
public void testMethod(MyObject object) {
synchronized (object) {
try {
System.out.println("testMethod ____getLock time=" + System.currentTimeMillis() + " run ThreadName=" + Thread.currentThread().getName());
Thread.sleep(2000);
System.out.println("testMethod releaseLock time=" + System.currentTimeMillis() + " run ThreadName=" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
TestSynObject testSynObject = new TestSynObject();
MyObject object = new MyObject();
SynObjThread1 synObjThread1 = new SynObjThread1(testSynObject, object);
synObjThread1.setName("a");
synObjThread1.start();
SynObjThread2 synObjThread2 = new SynObjThread2(testSynObject, object);
synObjThread2.setName("b");
synObjThread2.start();
}
}
运行结果:
testMethod ____getLock time=1547021705247 run ThreadName=a
testMethod releaseLock time=1547021707248 run ThreadName=a
testMethod ____getLock time=1547021707248 run ThreadName=b
testMethod releaseLock time=1547021709248 run ThreadName=b
两个线程采用了同一个对象监视器object ,因此运行结果实现了同步性。这将导致可能出现什么样的效果呢?
修改TestSynObject.java中的main()如下:
public static void main(String[] args) {
TestSynObject testSynObject = new TestSynObject();
MyObject object = new MyObject();
MyObject object1 = new MyObject();
SynObjThread1 synObjThread1 = new SynObjThread1(testSynObject, object);
synObjThread1.setName("a");
synObjThread1.start();
SynObjThread2 synObjThread2 = new SynObjThread2(testSynObject, object1);
synObjThread2.setName("b");
synObjThread2.start();
}
运行结果:
testMethod ____getLock time=1547023045744 run ThreadName=a
testMethod ____getLock time=1547023045744 run ThreadName=b
testMethod releaseLock time=1547023047745 run ThreadName=a
testMethod releaseLock time=1547023047745 run ThreadName=b
两个线程使用了不同的对象,所以运行结果不同步。
每当一个对象调用synchronized(this)方法时
非当前线程的执行实体中的被同步控制的方法以及基于this关键字实现的同步代码块均能确保互斥行为。
如果两个线程使用了同一个“对象监视器”,运行结果同步,否则不同步.
六 synchronized(string)
在Jvm中具有String常量池缓存的功能
String s1 = "a";
String s2 = "a";
System.out.println(s1 == s2);//true
字符串常量池中的字符串只有个一个“a”,所以此时锁的是同一个对象。
volatile
一 简介
在程序设计领域中(尤其是使用C、C++、C#以及Java这些编程语言),通过使用volatile关键字声明的变量或对象往往具备与代码优化以及多线程处理相关的独特属性。一般来说,在编译器层面(如果一段代码无法被修改或重新编译),则会被视为无法进行优化)。例如,在C语言中(volatile关键字可以用来提醒编译器后面所定义的变量可能会随时发生更改)。因此,在编译完成后(每当程序需要存储或读取这个变量时),系统会直接从该变量的实际地址中读取数值以避免潜在的问题)。如果没有volatile关键字,则在编译阶段可能对读取和存储操作进行优化处理(这可能导致临时将该值存放在寄存器中)。如果另一个进程在此期间对这个变量进行了更新,则会导致数据不一致的问题出现)。
在 JDK 1.2 之前,默认情况下所有操作均基于主存(即共享内存)。然而,在现代 Java 内存模型中,多线程机制允许将变量缓存在本地存储(如CPU寄存器)中。这种机制可能导致一个问题:当一个线程在主存中修改某个变量时,另一个使用该变量缓存在寄存器中的线程可能会继续使用旧值。这会导致数据不一致的问题。
为了有效解决该问题,在声明变量时必须将其定义为volatile类型
二 volatile的可见性
当volatile修饰的成员变量被任意线程访问时,在每次操作中都会强制从主存(即共享内存)中读取该成员变量的当前值。此外,在任何修改该成员变量值的情况下,系统也会强制要求相关线程将新值写回主存(共享内存)。这样一来,在任何时候两个不同线程都能看到同一个成员变量的具体数值,并因此保证了同步数据的一致性。
TestVolatile.java
public class TestVolatile extends Thread {
private boolean isRunning = true;
int m;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
@Override
public void run() {
System.out.println("进入run");
while (isRunning == true) {
int a = 2;
int b = 3;
int c = a + b;
m = c;
}
System.out.println(m);
System.out.println("停止!");
}
public static void main(String[] args) throws InterruptedException {
TestVolatile testVolatile = new TestVolatile();
testVolatile.start();
Thread.sleep(1000);
testVolatile.setRunning(false);
System.out.println("赋值:false");
}
}
运行结果:
进入run
赋值:false
当TestVolatile类中的isRunning变量未声明volatile关键字时,执行上述代码会导致死锁现象发生,因为即使该变量发生更改但更改内容未保存至主内存中,从而使得该线程在本地内存中始终保持标记状态,这就导致了死锁的产生。解决方法非常直接:在isRunning前添加volatile关键字即可解决问题,经过这一改动后运行程序将不会出现死锁现象
加上volatile关键字后的运行结果:
进入run
赋值:false
5
停止!
假如你把while循环代码里加上任意一个输出语句
@Override
public void run() {
System.out.println("进入run");
while (isRunning == true) {
int a = 2;
int b = 3;
int c = a + b;
m = c;
System.out.println("while内"+m);;//输出语句
}
System.out.println(m);
System.out.println("停止!");
}
结果如下:
5
5
5
5
5
5
5
5
停止!
赋值:false
或者加上sleep方法
@Override
public void run() {
System.out.println("进入run");
while (isRunning == true) {
int a = 2;
int b = 3;
int c = a + b;
m = c;
try {
Thread.sleep(1000);//sleep方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(m);
System.out.println("停止!");
}
结果如下:
进入run
赋值:false
5
停止!
您会发现死循环最终会停止,并且无论isRunning变量如何配置为upper volatile。
因为 JVM 会尽其最大努力维持内存的一致性(一致性),即使该变量未被声明为同步变量(即未使用 synchronized 关键字)。这种与 volatile 关键字的本质区别在于, volatile 关键字会强制确保线程可见性, 而不加此关键字的情况下, JVM 仍会尽力维持变量值的一致性.然而, 如果 CPU 在执行其他任务时总是被占用(处于一直占用状态), JVM 就无法强制要求 CPU 分段获取最新的变量值. 只有在适当的时候添加输出语句或 sleep 语句后, CPU 才有机会维持内存的一致性, 因此 while 循环得以终止.
三 volatile不保证原子性
TestVolatileAtomicity
public class TestVolatileAtomicity extends Thread {
volatile public static int count;
private static void addCount() {
for (int i = 0; i < 100; i++) {
count = i;
}
System.out.println("count=" + count);
}
@Override
public void run() {
addCount();
}
public static void main(String[] args) {
TestVolatileAtomicity[] testVolatileAtomicities = new TestVolatileAtomicity[100];
for (int i = 0; i < 100; i++) {
testVolatileAtomicities[i] = new TestVolatileAtomicity();
}
for (int i = 0; i < 100; i++) {
testVolatileAtomicities[i].start();
}
}
}
运行结果:
count=99
count=99
count=2
count=99
count=99
count=55
count=97
count=99
count=99
count=i属于一个不可分割的整体操作;然而,在大多数情况下都会得到预期值99;然而,在少数情况下会出现与预期不符的结果
解决办法:借助synchronized关键字实施加锁机制(除了Lock类外,AtomicInteger也是一种可行的选择)
修改TestVolatileAtomicity.java如下:
public class TestVolatileAtomicity extends Thread {
public static int count;
private synchronized static void addCount() {
for (int i = 0; i < 100; i++) {
count=i;
}
System.out.println("count=" + count);
}
@Override
public void run() {
addCount();
}
}
在运行过程中输出的count值都会变为99,并且为了确保数据的一致性必须使用synchronized关键字
四 synchronized和volatile比较
- 该轻量级机制主要用于处理线程同步问题,在性能表现上优于
synchronized关键字。 volatile仅限于修饰变量,在JavaSE1.6及以后版本中引入了偏向锁和轻量级锁等优化措施以提升效率;synchronized则允许修饰方法或代码块,并且在性能消耗上更为高效。- 多个线程同时访问时不会出现阻塞现象;然而,在某些特殊情况下(可能)会出现阻塞情况。
volatile确保数据可见但不具备原子性特征;相比之下,则能同时保障数据的可见性和原子性。volatile主要用于解决变量间的可见性问题;而synchronized则专门负责确保多个线程访问资源时的行为同步。
线程通信
一 wait/notify机制介绍
该wait/notify机制涉及两条线索:一条线索A被调用wait()方法进入等待状态;另一条线索B被调用notify()或 notifyAll()方法向其发出通知信息。当线索A接收到通知后会从等待队列中脱离并进入可执行状态以处理后续操作;基于对象O进行操作的这两条线索彼此之间通过wait与notify相互作用以实现同步管理;前者负责等待后者的通知,并根据情况决定是否释放资源供其他操作继续进行。
常用方法
notify() 随机地将处于等待同一共享资源状态中的一个线程唤醒,并使其从等待队列中退出,并进入可运行的状态;也就是说 notify() 方法仅用于通知‘一个线程’
通知所有人(notifyAll())负责让每一个处于等待同一共享资源对象(shared resource object)而处于等待状态的所有线程从其等待队列中脱离出来并进入可运行状态。此时,在运行时优先级最高的那个线程最先执行相应任务。但也有机会以随机的方式被选择。这取决于JVM虚拟机的具体实现细节。
让执行该方法的线程释放共享资源锁后立即退出运行状态,并立即加入等待队列直至再次被唤醒
*wait函数带有超时功能,在长时间保持待机状态后会触发超时并返回。其中参数指定为以毫秒为单位的时间间隔,在未接收到任何响应的情况下会触发超时并返回。
- wait(long,int)
对于超时时间更细力度的控制,可以达到纳秒
二 代码实现
2.1 通信示例
MyList.java
public class MyList {
private static List<String> list = new ArrayList<String>();
public static void add() {
list.add("anyString");
}
public static int size() {
return list.size();
}
public static void main(String[] args) {
try {
Object lock = new Object();
TestAdviceThreadA testAdviceThreadA = new TestAdviceThreadA(lock);
testAdviceThreadA.start();
Thread.sleep(50);
TestAdviceThreadB testAdviceThreadB = new TestAdviceThreadB(lock);
testAdviceThreadB.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
TestAdviceThreadA.java
public class TestAdviceThreadA extends Thread {
private Object lock;
public TestAdviceThreadA(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
try {
synchronized (lock) {
if (MyList.size() != 5) {
System.out.println("wait begin "
+ System.currentTimeMillis());
lock.wait();
System.out.println("wait end "
+ System.currentTimeMillis());
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
TestAdviceThreadB.java
public class TestAdviceThreadB extends Thread {
private Object lock;
public TestAdviceThreadB(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
try {
synchronized (lock) {
for (int i = 0; i < 10; i++) {
MyList.add();
if (MyList.size() == 5) {
lock.notify();
System.out.println("已发出通知!");
}
System.out.println("添加了" + (i + 1) + "个元素!");
Thread.sleep(1000);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
wait begin 1547103633419
添加了1个元素!
添加了2个元素!
添加了3个元素!
添加了4个元素!
已发出通知!
添加了5个元素!
添加了6个元素!
添加了7个元素!
添加了8个元素!
添加了9个元素!
添加了10个元素!
wait end 1547103643621
Synchronized关键字允许将任意一个Object视为同步目标,并且它规定了Java如何处理多态性问题:每个Object都实现了相应的等待与通知机制(wait/notify)。这些机制必须应用于基于synchronized关键字所锁定的对象临界区。通过调用wait()方法可以让正在使用临界区的线程暂时暂停其执行;而notify()方法能够唤醒那些因用了wait而导致被阻塞的线程。被重新唤醒的线程会试图重新获得该锁并继续执行随后定义的操作;如果发出notify命令时没有任何处于阻塞状态中的线程响应,则该命令会被忽略。
2.2 线程的基本状态
新建(new):新创建了一个线程对象。
该类(Runnable):当一个Line thread object is created之后, other thread instances, such as the main thread, called the start() method on this object. The line threads in this state are part of a runnable thread pool, waiting to be selected by a thread scheduler to obtain CPU access.
该类(Runnable):当一个Line thread object is created之后, other thread instances, such as the main thread, called the start() method on this object. The line threads in this state are part of a runnable thread pool, waiting to be selected by a thread scheduler to obtain CPU access.
一个处于可运行状态的线程被分配了CPU时间片(timeslice),随后运行相应的程序代码。
阻塞(block):当线程因特定原因失去了对CPU的时间片控制权时会进入阻塞(state)状态;此时该线程会暂时失去对CPU的访问权限直至返回到可执行(Ready)状态才能重新获取CPU并转为运行(Running)状态。
- 被阻塞:运行中的线程调用o.wait()方法后会被加入wait队列。
- 被阻塞:在线丝申请同步锁时若已被占用,则会被放到lock池。
- 其他情况导致被阻塞包括调用睡眠或join方法以及产生I/O请求的情况。
死亡(dead):当该线程的run()或main()方法退出后(包括因异常退出),则该线程结束其生命周期。已死亡的线程将无法重新复活。
备注 :
用早起坐地铁的过程来比喻这一现象:
- 还没起床: sleeping
- 起床后收拾好就可以直接乘坐地铁前往:Runnable
- 等待地铁到达时会遇到I/O阻塞问题
- 上车后发现没有空座位时会触发synchronized阻塞
- 最终抵达目的地时的状态标记为Dead
2.3 notify()锁释放问题
一旦调用wait()方法完成执行,则锁会自动被释放。
一旦调用notify()完成后,则锁不会自动被释放。
只有在完成与 notify() 方法相关的 synchronized 区块之后才能使锁脱离。
2.4 当interrupt方法遇到wait方法
当一个线程处于wait状态时, 对该线程对象invoke interrupt method会导致抛出InterruptionException异常
Service.java
public class Service {
public void testMethod(Object lock) {
try {
synchronized (lock) {
System.out.println("begin wait()");
lock.wait();
System.out.println("end wait()");
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("出现异常了,因为呈wait状态的线程被interrupt了!");
}
}
public static void main(String[] args) {
try {
Object lock = new Object();
ServiceThreadA serviceThreadA = new ServiceThreadA(lock);
serviceThreadA.start();
Thread.sleep(5000);
serviceThreadA.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ServiceThreadA.java
public class ServiceThreadA extends Thread {
private Object lock;
public ServiceThreadA(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
Service service = new Service();
service.testMethod(lock);
}
}
运行结果:
begin wait()
出现异常了,因为呈wait状态的线程被interrupt了!
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at advice.Service.testMethod(Service.java:8)
at advice.ServiceThreadA.run(ServiceThreadA.java:15)
三 管道输入/输出流
在处理管道输入/输出流与普通文件以及网络输入/输出流时的不同之处主要体现在它们各自的作用机制上。其中,在处理过程中,管道输入/输出流与普通文件以及网络输入/输出流之间的一个显著区别在于它们各自的功能定位不同:前者主要用于通过内存实现线程间的高效数据传输
面向字节: PipedOutputStream、 PipedInputStream
面向字符: PipedWriter、 PipedReader
管道输入/输出流实例
WriteMethod.java
public class WriteMethod {
public void writeMethod(PipedOutputStream out) {
try {
System.out.println("write :");
for (int i = 0; i < 300; i++) {
String outData = "" + (i + 1);
out.write(outData.getBytes());
System.out.print(outData);
}
System.out.println();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ReadMethod.java
public class ReadMethod {
public void readMethod(PipedInputStream input) {
try {
System.out.println("read :");
byte[] byteArray = new byte[20];
int readLength = input.read(byteArray);
while (readLength != -1) {
String newData = new String(byteArray, 0, readLength);
System.out.print(newData);
readLength = input.read(byteArray);
}
System.out.println();
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
WriteThread.java
public class WriteThread extends Thread {
private WriteMethod write;
private PipedOutputStream pipedOutputStream;
public WriteThread(WriteMethod write, PipedOutputStream pipedOutputStream) {
super();
this.write = write;
this.pipedOutputStream = pipedOutputStream;
}
@Override
public void run() {
write.writeMethod(pipedOutputStream);
}
}
ReadThread.java
public class ReadThread extends Thread {
private ReadMethod read;
private PipedInputStream pipedInputStream;
public ReadThread(ReadMethod read, PipedInputStream pipedInputStream) {
super();
this.read = read;
this.pipedInputStream = pipedInputStream;
}
@Override
public void run() {
read.readMethod(pipedInputStream);
}
}
public static void main(String[] args) {
try {
WriteMethod writeData = new WriteMethod();
ReadMethod readData = new ReadMethod();
PipedInputStream inputStream = new PipedInputStream();
PipedOutputStream outputStream = new PipedOutputStream();
// inputStream.connect(outputStream);
outputStream.connect(inputStream);
ReadThread readThread = new ReadThread(readData, inputStream);
readThread.start();
Thread.sleep(2000);
WriteThread writeThread = new WriteThread(writeData, outputStream);
writeThread.start();
} catch (Exception e) {
e.printStackTrace();
}
}
我们为实现数据传输功能而设计了两个核心接口:writeMethod 和 readMethod。其中前者主要用于写入字节或字符数据(具体实现依赖于使用的是 PipedOutputStream 还是 PipedWriter),而后者则负责读取字节或字符数据(具体实现则基于所选的 PipedInputStream 或 PipedReader)。为了协调数据传输过程,在此基础上我们还设置了两个处理线程:readThread 和 writeThread 。其中 readThread 线程负责调用 readMethod 方法完成读取操作;同时 writeThread 线程则负责调用 writeMethod 方法完成写入操作。随后通过 outputStream 与 inputStream 之间的连接实现了这两个管道的串联。这样一来就形成了一个完整的数据传输通道。
运行结果:
read :
write :
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202123456789101112131412032042052062072082092102112122132142155161718192021222324252627282930313233343216217218536373839404142434442195464748495051525354522055657585960616263646221222566676869707172737472235767778798081828384822422522622722822958687888990919293949596979899100101102102302312323104105106107108109110111112113114115116233234117118119120121122123124125126127128129123523630131132133134135136137138139140141142143144145146147148149123750151152153154155156238157158159160161162163164165166167168169123970171172173174175176177178179180181182182403184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
四 Thread.join()的使用
在多种场景下,主线程通常会启动子线程以执行特定任务。当子线程需要执行耗时运算时,在大多数情况下主线程序通常会比子线程序提前完成任务。然而,在某些情况下当主线程序处理完其他事务后却需要等待子线程序完成后再继续执行任务这种情况下就需要使用到join()方法来实现同步机制。此外Thread类除了提供基本的join()方法外还提供了带有超时特性的join(long millis)以及双参数版本的方法这些超时功能允许开发者在指定的时间内强制终止未完成的任务从而避免长时间阻塞的问题
4.1 join方法使用
不使用join方法的弊端演示:
NotUseJoin.java
public class NotUseJoin {
public static void main(String[] args) throws InterruptedException {
MyThread threadTest = new MyThread();
threadTest.start();
//Thread.sleep(?);//因为不知道子线程要花的时间这里不知道填多少时间
System.out.println("我想当threadTest对象执行完毕后我再执行");
}
static public class MyThread extends Thread {
@Override
public void run() {
System.out.println("我想先执行");
}
}
}
可以看到,在子线程结束后会执行后续操作;由于子线程运行所需的时间具有不确定性,则 sleep 的时长自然也就无法预先确定;因此为了确保后续操作能够顺利衔接上位机任务,请考虑采用 join 方法来进行同步处理;为了解决上述问题,请考虑采用 join 方法来进行同步处理
UseJoin.java
public class UseJoin {
public static void main(String[] args) throws InterruptedException {
MyThread threadTest = new MyThread();
threadTest.start();
//Thread.sleep(?);//因为不知道子线程要花的时间这里不知道填多少时间
threadTest.join();
System.out.println("我想当threadTest对象执行完毕后我再执行");
}
static public class MyThread extends Thread {
@Override
public void run() {
System.out.println("我想先执行");
}
}
}
4.2 join(long millis)方法的使用
join(long millis)中的参数就是设定的等待时间。
TestJoinLong.java
public class TestJoinLong {
public static void main(String[] args) {
try {
MyThread threadTest = new MyThread();
threadTest.start();
threadTest.join(2000);// 只等2秒
//Thread.sleep(2000);
System.out.println(" end timer=" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static public class MyThread extends Thread {
@Override
public void run() {
try {
System.out.println("begin Timer=" + System.currentTimeMillis());
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
begin Timer=1547193074070
end timer=1547193076069
无论调用threadTest.start() join()函数(参数设置为2)还是调用 Thread.Sleep()函数(参数设置为2),都会在指定时间点触发 'end timer=...' 事件,并每隔两秒就会输出一次。'end timer=...' 事件发生后程序还会持续运行一段时间,在线程的run方法中包含了一个 Thread.Sleep() 语句以实现延时功能。另一个区别是 threadTest.start().join()会等待子线程完成并释放主线程锁而 Thread.Sleep()则不会释放任何锁。
五 ThreadLocal的使用
常用方法
-
get()
返回当前线程局部变量副本中的值。 -
set(T value)
将当前线程的局部变量的副本设置为指定的值 -
remove(
删除此线程局部变量的当前值。 -
initialValue()
返回此线程局部变量初始值
代码实现
TestThreadLocal.java
public class TestThreadLocal {
public static ThreadLocal<String> threadLocal= new ThreadLocal<String>();
public static void main(String[] args) {
if (threadLocal.get() == null) {
System.out.println("为ThreadLocal类对象放入值:aaa");
threadLocal.set("aaaֵ");
}
System.out.println(threadLocal.get());//aaa
System.out.println(threadLocal.get());//aaa
}
}
运行结果:
为ThreadLocal类对象放入值:aaa
aaaֵ
aaaֵ
通过观察运行结果可以看出,在首次调用ThreadLocal对象的get()方法时返回null的状态下,默认无法直接赋值给该对象。为了实现赋值操作,则需要先通过调用set()方法给ThreadLocal对象赋予相应的初始值。如果希望解决get()方法返回null的问题,则应考虑使用ThreadLocal对象提供的initialValue属性来设置初始值以避免这种情况的发生
TestThreadLocal2.java
public class TestThreadLocal2 {
public static ThreadLocalExt threadLocalExt = new ThreadLocalExt();
public static void main(String[] args) {
if (threadLocalExt.get() == null) {
System.out.println("从未放过值");
threadLocalExt.set("我的值");
}
System.out.println(threadLocalExt.get());
System.out.println(threadLocalExt.get());
}
public static class ThreadLocalExt extends ThreadLocal {
@Override
protected Object initialValue() {
return "我是默认值 第一次get不再为null";
}
}
}
运行结果:
我是默认值 第一次get不再为null
我是默认值 第一次get不再为null
线程变量隔离性
TestIsolationOfThreadVariable.java
public class TestIsolationOfThreadVariable {
public static void main(String[] args) {
try {
for (int i = 0; i < 10; i++) {
System.out.println(" 在Main线程中取值=" + Tools.threadLocalExt.get());
Thread.sleep(100);
}
Thread.sleep(5000);
ThreadA a = new ThreadA();
a.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static class Tools {
public static ThreadLocalExt threadLocalExt = new ThreadLocalExt();
}
public static class ThreadLocalExt extends ThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
}
static public class ThreadA extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
System.out.println("在ThreadA线程中取值=" + Tools.threadLocalExt.get());
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
在Main线程中取值=1547194170774
在Main线程中取值=1547194170774
在Main线程中取值=1547194170774
在Main线程中取值=1547194170774
在Main线程中取值=1547194170774
在Main线程中取值=1547194170774
在Main线程中取值=1547194170774
在Main线程中取值=1547194170774
在Main线程中取值=1547194170774
在Main线程中取值=1547194170774
在ThreadA线程中取值=1547194176785
在ThreadA线程中取值=1547194176785
在ThreadA线程中取值=1547194176785
在ThreadA线程中取值=1547194176785
在ThreadA线程中取值=1547194176785
在ThreadA线程中取值=1547194176785
在ThreadA线程中取值=1547194176785
在ThreadA线程中取值=1547194176785
在ThreadA线程中取值=1547194176785
在ThreadA线程中取值=1547194176785
从运行结果可以看出子线程和父线程各自拥有各自的值。
InheritableThreadLocal
该类具有良好的性能;然而子线程无法继承父线程中的ThreadLocal实例;通过InheritableThreadLocal类实现了跨线程引用的能力;获取父进程的相关信息时需要考虑多态机制;修改Test3.java的内部类Tools 和 ThreadLocalExt类如下:
static public class Tools {
// public static ThreadLocalExt tl = new ThreadLocalExt();
public static InheritableThreadLocalExt inheritableThreadLocalExt = new InheritableThreadLocalExt();
}
// static public class ThreadLocalExt extends ThreadLocal {
// @Override
// protected Object initialValue() {
// return new Date().getTime();
// }
// }
static public class InheritableThreadLocalExt extends InheritableThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
}
inheritable:可继承
运行结果:
在Main线程中取值=1547195178543
在Main线程中取值=1547195178543
在Main线程中取值=1547195178543
在Main线程中取值=1547195178543
在Main线程中取值=1547195178543
在Main线程中取值=1547195178543
在Main线程中取值=1547195178543
在Main线程中取值=1547195178543
在Main线程中取值=1547195178543
在Main线程中取值=1547195178543
在ThreadA线程中取值=1547195178543
在ThreadA线程中取值=1547195178543
在ThreadA线程中取值=1547195178543
在ThreadA线程中取值=1547195178543
在ThreadA线程中取值=1547195178543
在ThreadA线程中取值=1547195178543
在ThreadA线程中取值=1547195178543
在ThreadA线程中取值=1547195178543
在ThreadA线程中取值=1547195178543
在ThreadA线程中取值=1547195178543
获取父线程的值并对其进行修改:
重写TestIsolationOfThreadVariable类中的Tools和InheritableThreadLocalExt内置于其子类中以实现隔离功能
static public class Tools {
// public static ThreadLocalExt tl = new ThreadLocalExt();
public static InheritableThreadLocalExt inheritableThreadLocalExt = new InheritableThreadLocalExt();
}
// static public class ThreadLocalExt extends ThreadLocal {
// @Override
// protected Object initialValue() {
// return new Date().getTime();
// }
// }
static public class InheritableThreadLocalExt extends InheritableThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
@Override
protected Object childValue(Object parentValue) {
return parentValue + " 我在子线程加的~!";
}
}
复制代码运行结果:
在Main线程中取值=1547195528408
在Main线程中取值=1547195528408
在Main线程中取值=1547195528408
在Main线程中取值=1547195528408
在Main线程中取值=1547195528408
在Main线程中取值=1547195528408
在Main线程中取值=1547195528408
在Main线程中取值=1547195528408
在Main线程中取值=1547195528408
在Main线程中取值=1547195528408
在ThreadA线程中取值=1547195528408 我在子线程加的~!
在ThreadA线程中取值=1547195528408 我在子线程加的~!
在ThreadA线程中取值=1547195528408 我在子线程加的~!
在ThreadA线程中取值=1547195528408 我在子线程加的~!
在ThreadA线程中取值=1547195528408 我在子线程加的~!
在ThreadA线程中取值=1547195528408 我在子线程加的~!
在ThreadA线程中取值=1547195528408 我在子线程加的~!
在ThreadA线程中取值=1547195528408 我在子线程加的~!
在ThreadA线程中取值=1547195528408 我在子线程加的~!
在ThreadA线程中取值=1547195528408 我在子线程加的~!
注意:当子线程在同一时间获取该值时,并且在此期间主线程修改了InheritableThreadLocal中的数据,则在这种操作完成后发现子线程序仍持有旧数值的情况会发生
Lock
一 Lock接口
1.1 Lock
锁是一种通过多线程管理对共享资源访问的关键工具。
一般情况下, 锁确保了对共享资源的独特访问权: 任何一个线程在任何时候只能持有该锁一次, 并且在获取该锁之前必须完成对该资源的所有操作。
然而, 在某些特定设计中, 可能会允许多个线程同时进行部分操作, 这种情形下我们通常会采用ReadWriteLock等特殊的同步机制。
Java traditionally utilized the synchronized keyword to emulate lock functionality within its programming model.
JDK1.5之后, 并发包中新增了Lock接口以及相关实现类来更精确地管理这种复杂的同步需求。
lock机制是synchronized关键字的高级应用。
掌握locking机制有助于深入理解并编写正确的多线程代码。
在并发编程中,许多类采用了ReentrantLock接口来实现安全的同步机制。
Reentrant Lock , Reentrant ReadWrite Lock.Read Lock , Reentrant ReadWrite Lock.Write Lock
1.2 Lock的使用
Lock lock=new ReentrantLock();
lock.lock();
try{
}finally{
lock.unlock();
}
1.3 Lock接口的特性
Lock接口提供的synchronized关键字不具备的主要特性:
通过以非阻塞方式来实现对锁定操作,并进一步说明当一个线程试图在当前时刻获得该资源时,在此特定时间点上资源未被其他线程占用,则该线程能够成功地获得并持有该资源。
在被中断的情况下获取锁,在相应的线程能够响应中断的情况下,在相关线程受到中断时会导致异常被捕获,并且此时相应的锁也会被释放
在指定的时间段内取得锁,在指定时间内获取锁;一旦超过截止时间就无法再取得锁
1.4 Lock的方法:
-
void lock()
获取锁。如果无法获得lock,则当前thread将被暂停执行以进行process scheduling。该thread处于wait state until acquisition of lock. -
void lockInterruptibly() 用来获取锁(即)若能够立即取得,则会立即返回 当无法取得该锁时 将导致该线程暂时被暂停调度流程 置于休眠状态 与使用lock()方法不同的是 在获取锁的过程中允许当前线程被中断
通过获取等待通知组件并与其进行绑定,在本方法执行时当前线程必须先取得对应的锁。在完成对该组件的wait()方法调用操作后,在此过程中当前线程将会释放相应的锁以完成资源管理任务。
- boolean tryLock()
该函数只有在被调用时才能获得锁。如果该锁可用,则成功地获取了锁定权,并立即返回布尔值true;否则立即返回布尔值false。
boolean tryLock(long time, TimeUnit unit)
通过超时机制获取锁的状态标记。当且仅当以下任一条件成立时返回true:
1. 当前线程在超时时间内成功获得锁;
2. 当前线程在超时时间内发生中断事件;
3. 经过超时时间后未成功获得锁。
- void unlock()
释放锁。
二 Lock接口的实现类:ReentrantLock
ReentrantLock与synchronized关键字同样能够用于实现线程间的同步互斥机制;然而,在功能上相比synchronized关键字更为强大且更加灵活。
2.1 构造方法:
- ReentrantLock()
创建一个 ReentrantLock的实例。
实现一种指定类型的lock(如公平或非公平)的一个ReentrantLock实例
2.2 ReentrantLock的方法
int getHoldCount() 返回当前被此锁保护的线程数量
protected Thread getOwner()
该方法将返回拥有此锁的线程;若不具备该锁,则将被设为null。
该方法用于返回一个包含所有等待获取此锁的线程集合。
-
int getQueueLength()
返回等待获取此锁的线程数的估计。 -
protected Collection getWaitingThreads(Condition condition)
该方法返回与指定条件相关的阻塞线程集合。
int getWaitQueueLength(Condition condition) 表示为与此锁相关联的满足特定条件下的等待线程数的估算
boolean hasQueuedThread(Thread thread)
确定指定线程是否正等待此锁。
- boolean hasQueuedThreads()
查询是否有线程正在等待获取此锁。
boolean checkCondition(Condition condition) 检查任何线程是否waited for与此锁 associated with this lock 的 given condition.
-
boolean isFair()
查看是否是公平锁,如果此锁为公平锁,返回 true 。 -
boolean isHeldByCurrentThread()
查询此锁是否由当前线程持有。
held:持有
- boolean isLocked()
查询此锁是否由任何线程持有。
2.3 ReentrantLock示例
TestReentrantLock.java
public class TestReentrantLock {
public static void main(String[] args) {
MyService service = new MyService();
MyThread a1 = new MyThread(service);
MyThread a2 = new MyThread(service);
MyThread a3 = new MyThread(service);
MyThread a4 = new MyThread(service);
MyThread a5 = new MyThread(service);
a1.start();
a2.start();
a3.start();
a4.start();
a5.start();
}
public static class MyService {
private Lock lock = new ReentrantLock();
public void testMethod() {
lock.lock();
try {
for (int i = 0; i < 5; i++) {
System.out.println("ThreadName=" + Thread.currentThread().getName() + (" " + (i + 1)));
}
} finally {
lock.unlock();
}
}
}
public static class MyThread extends Thread {
private MyService service;
public MyThread(MyService service) {
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
}
运行结果:
ThreadName=Thread-1 1
ThreadName=Thread-1 2
ThreadName=Thread-1 3
ThreadName=Thread-1 4
ThreadName=Thread-1 5
ThreadName=Thread-3 1
ThreadName=Thread-3 2
ThreadName=Thread-3 3
ThreadName=Thread-3 4
ThreadName=Thread-3 5
ThreadName=Thread-0 1
ThreadName=Thread-0 2
ThreadName=Thread-0 3
ThreadName=Thread-0 4
ThreadName=Thread-0 5
ThreadName=Thread-2 1
ThreadName=Thread-2 2
ThreadName=Thread-2 3
ThreadName=Thread-2 4
ThreadName=Thread-2 5
ThreadName=Thread-4 1
ThreadName=Thread-4 2
ThreadName=Thread-4 3
ThreadName=Thread-4 4
ThreadName=Thread-4 5
通过观察运行结果可知,在完成任务后正确地将锁释放会使其他线程得以执行,并且其执行顺序并非固定
三 Condition接口简介
在多线程编程中,默认情况下不支持递归同步(Recursive Synchronization),因此需要手动引入ReentrantLock并配合使用Condition接口以实现互斥操作。
Condition接口默认支持通过ReentrantLock机制配合Condition实例来实现主动通知机制
synchronized关键字本质上等同于Lock对象中的唯一一个Condition实例。每个线程都注册在其对应的Condition实例上。当调用notifyAll()方法时会通知所有处于等待状态的线程此举会导致效率显著下降。相比之下,signalAll()方法仅唤醒其自身条件上注册的所有等待线程。
3.1 Condition方法:
- void await()
相当于Object类的wait方法
boolean await(long time, TimeUnit unit) 等价于父类wait方法(long timeout)
-
signal()
相当于Object类的notify方法 -
signalAll()
相当于Object类的notifyAll方法
3.2 使用Condition实现等待/通知机制
3.2.1. 单个Condition实例
UseSingleConditionWaitNotify.java
public class UseSingleConditionWaitNotify {
public static void main(String[] args) throws InterruptedException {
MyService service = new MyService();
ThreadA a = new ThreadA(service);
a.start();
Thread.sleep(3000);
service.signal();
}
public static class MyService {
private Lock lock = new ReentrantLock();
public Condition condition = lock.newCondition();
public void await() {
lock.lock();
try {
System.out.println("await时间为" + System.currentTimeMillis());
condition.await();
System.out.println("这是condition.await()方法之后的语句,condition.signal()方法之后才被执行");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void signal() throws InterruptedException {
lock.lock();
try {
System.out.println("signal时间为" + System.currentTimeMillis());
condition.signal();
Thread.sleep(3000);
System.out.println("这是condition.signal()方法之后的语句");
} finally {
lock.unlock();
}
}
}
public static class ThreadA extends Thread {
private MyService service;
public ThreadA(MyService service) {
super();
this.service = service;
}
@Override
public void run() {
service.await();
}
}
}
运行结果:
await时间为1547198270336
signal时间为1547198273336
这是condition.signal()方法之后的语句
这是condition.await()方法之后的语句,condition.signal()方法之后我才被执行
为了实现等待通知机制,在使用wait/notify时应先完成位于notify()方法所在synchronized代码块中的操作后才能释放锁。同样地,在这里也需要在signal所在的try语句块结束后才进行锁的释放。只有在条件await完成后相应的操作才能得以执行。请注意,在调用condition.await()之前需要先调用lock.lock()以获取同步监视器。
3.2.2. 多个Condition实例
MyserviceMoreCondition.java
public class MyserviceMoreCondition {
private Lock lock = new ReentrantLock();
public Condition conditionA = lock.newCondition();
public Condition conditionB = lock.newCondition();
public void awaitA() {
lock.lock();
try {
System.out.println("begin awaitA时间为" + System.currentTimeMillis()
+ " ThreadName=" + Thread.currentThread().getName());
conditionA.await();
System.out.println(" end awaitA时间为" + System.currentTimeMillis()
+ " ThreadName=" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void awaitB() {
lock.lock();
try {
System.out.println("begin awaitB时间为" + System.currentTimeMillis()
+ " ThreadName=" + Thread.currentThread().getName());
conditionB.await();
System.out.println(" end awaitB时间为" + System.currentTimeMillis()
+ " ThreadName=" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void signalAll_A() {
lock.lock();
try {
System.out.println(" signalAll_A时间为" + System.currentTimeMillis()
+ " ThreadName=" + Thread.currentThread().getName());
conditionA.signalAll();
} finally {
lock.unlock();
}
}
public void signalAll_B() {
lock.lock();
try {
System.out.println(" signalAll_B时间为" + System.currentTimeMillis()
+ " ThreadName=" + Thread.currentThread().getName());
conditionB.signalAll();
} finally {
lock.unlock();
}
}
}
UseMoreConditionWaitNotify.java
public class UseMoreConditionWaitNotify {
public static void main(String[] args) throws InterruptedException {
MyserviceMoreCondition service = new MyserviceMoreCondition();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
Thread.sleep(3000);
service.signalAll_A();
}
public static class ThreadA extends Thread {
private MyserviceMoreCondition service;
public ThreadA(MyserviceMoreCondition service) {
super();
this.service = service;
}
@Override
public void run() {
service.awaitA();
}
}
public static class ThreadB extends Thread {
private MyserviceMoreCondition service;
public ThreadB(MyserviceMoreCondition service) {
super();
this.service = service;
}
@Override
public void run() {
service.awaitB();
}
}
}
运行结果:
begin awaitA时间为1547208939857 ThreadName=A
begin awaitB时间为1547208939860 ThreadName=B
signalAll_A时间为1547208942857 ThreadName=main
end awaitA时间为1547208942857 ThreadName=A
只有A线程被唤醒了。
3.2.3. Condition实现顺序执行
ConditionSeqExec.java
public class ConditionSeqExec {
private static volatile int nextPrintWho = 1;
private static ReentrantLock lock = new ReentrantLock();
private static final Condition conditionA = lock.newCondition();
private static final Condition conditionB = lock.newCondition();
private static final Condition conditionC = lock.newCondition();
public static void main(String[] args) {
Thread threadA = new Thread() {
public void run() {
try {
lock.lock();
while (nextPrintWho != 1) {
conditionA.await();
}
for (int i = 0; i < 3; i++) {
System.out.println("ThreadA " + (i + 1));
}
nextPrintWho = 2;
//通知conditionB实例的线程运行
conditionB.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
};
Thread threadB = new Thread() {
public void run() {
try {
lock.lock();
while (nextPrintWho != 2) {
conditionB.await();
}
for (int i = 0; i < 3; i++) {
System.out.println("ThreadB " + (i + 1));
}
nextPrintWho = 3;
//通知conditionC实例的线程运行
conditionC.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
};
Thread threadC = new Thread() {
public void run() {
try {
lock.lock();
while (nextPrintWho != 3) {
conditionC.await();
}
for (int i = 0; i < 3; i++) {
System.out.println("ThreadC " + (i + 1));
}
nextPrintWho = 1;
//通知conditionA实例的线程运行
conditionA.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
};
Thread[] aArray = new Thread[5];
Thread[] bArray = new Thread[5];
Thread[] cArray = new Thread[5];
for (int i = 0; i < 5; i++) {
aArray[i] = new Thread(threadA);
bArray[i] = new Thread(threadB);
cArray[i] = new Thread(threadC);
aArray[i].start();
bArray[i].start();
cArray[i].start();
}
}
}
运行结果:
ThreadA 1
ThreadA 2
ThreadA 3
ThreadB 1
ThreadB 2
ThreadB 3
ThreadC 1
ThreadC 2
ThreadC 3
ThreadA 1
ThreadA 2
ThreadA 3
ThreadB 1
ThreadB 2
ThreadB 3
ThreadC 1
ThreadC 2
ThreadC 3
ThreadA 1
ThreadA 2
ThreadA 3
ThreadB 1
ThreadB 2
ThreadB 3
ThreadC 1
ThreadC 2
ThreadC 3
ThreadA 1
ThreadA 2
ThreadA 3
ThreadB 1
ThreadB 2
ThreadB 3
ThreadC 1
ThreadC 2
ThreadC 3
ThreadA 1
ThreadA 2
ThreadA 3
ThreadB 1
ThreadB 2
ThreadB 3
ThreadC 1
ThreadC 2
ThreadC 3
当一个线程执行完毕时,通过调用condition.signal()或condition.signalAll()方法来通知下一个特定的运行单元。从而实现无限循环。
注意: 默认情况下ReentranLock类使用的是非公平锁
3.2.4 公平锁与非公平锁
Lock分为两种:公平并发锁 和 非公平并发 lock。其中前者遵循 First-In-First-Out 的原则进行资源分配;后者则采用竞争性地争夺 lock 的方式实现资源管理,在这种机制下前者的规则是先进者优先但未必能率先获得资源 其结果可能导致资源分配不公
public class FairOrNoFairLock {
public static void main(String[] args) throws InterruptedException {
final Service service = new Service(true);//true为公平锁,false为非公平锁
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName()
+ "运行了");
service.serviceMethod();
}
};
Thread[] threadArray = new Thread[10];
for (int i = 0; i < 10; i++) {
threadArray[i] = new Thread(runnable);
}
for (int i = 0; i < 10; i++) {
threadArray[i].start();
}
}
public static class Service {
private ReentrantLock lock;
public Service(boolean isFair) {
super();
lock = new ReentrantLock(isFair);
}
public void serviceMethod() {
lock.lock();
try {
System.out.println("ThreadName=" + Thread.currentThread().getName()
+ "获得锁定");
} finally {
lock.unlock();
}
}
}
}
运行结果:
线程Thread-0运行了
ThreadName=Thread-0获得锁定
线程Thread-1运行了
ThreadName=Thread-1获得锁定
线程Thread-2运行了
ThreadName=Thread-2获得锁定
线程Thread-3运行了
ThreadName=Thread-3获得锁定
线程Thread-4运行了
ThreadName=Thread-4获得锁定
线程Thread-5运行了
线程Thread-6运行了
ThreadName=Thread-5获得锁定
ThreadName=Thread-6获得锁定
线程Thread-7运行了
ThreadName=Thread-7获得锁定
线程Thread-8运行了
ThreadName=Thread-8获得锁定
线程Thread-9运行了
ThreadName=Thread-9获得锁定
lock类型的执行结果具有确定性顺序。
当Service参数设置为false时,则表示非公平锁。
final Service service = new Service(false);//true为公平锁,false为非公平锁
运行结果:
线程Thread-1运行了
ThreadName=Thread-1获得锁定
线程Thread-0运行了
线程Thread-2运行了
ThreadName=Thread-0获得锁定
ThreadName=Thread-2获得锁定
线程Thread-4运行了
ThreadName=Thread-4获得锁定
线程Thread-5运行了
ThreadName=Thread-5获得锁定
线程Thread-6运行了
ThreadName=Thread-6获得锁定
线程Thread-7运行了
ThreadName=Thread-7获得锁定
线程Thread-8运行了
ThreadName=Thread-8获得锁定
线程Thread-9运行了
ThreadName=Thread-9获得锁定
线程Thread-3运行了
ThreadName=Thread-3获得锁定
非公平锁的运行结果是无序的。
四 ReentrantReadWriteLock
ReentrantReadWriteLock是ReadWriteLock接口的实现类。
4.1 简介
ReentrantLock(排他锁)实现了严格互斥的效果,并且确保只有一个线程能在同一时刻执行。尽管如此,在这种情况下仍需考虑单个线程的安全性问题,并且运行效率极其低下。ReadWriteLock接口的设计目标是解决这一问题。
该机制实现了两个互斥资源的管理,并对读操作相关的资源赋予为共享资源、对写操作相关的资源指定为排他资源。相比传统排他机制,在并发性能上有显著提升。
- 多个 read locks 之间没有互斥关系;read locks 和 write locks 互相排斥;任意两个 write locks 不能同时存在;一旦发生 write 操作即为互斥。
- 在没有任何线程执行 write 操作时,在任何时刻都有可能有多个 thread 获得 read lock;但若某一线程执行 write 操作,则必须先获取 write lock 才能对该 write 操作执行。
- 多个 thread 可以同时进行 read 操作;但仅有一个 thread 可以对同一个对象执行 write 操作在同一时间段内。
4.2 ReentrantReadWriteLock
公平性选择
支持非公平(默认)和公平的锁获取方式:
- ReentrantReadWriteLock()
- ReentrantReadWriteLock(boolean fair)
可重入
支持重入时的逻辑是:当一个读线程获得了某类资源的互斥资源(例如文件句柄),那么它不仅能重新取得该类型的互斥资源(即再获读 lock),而且还可以尝试与其他类型互斥资源(如文件描述符)进行竞争互斥以获得相应的资源。对于write line程而言,在正常情况下,在获得write lock后就可以再次获得write lock;如果当前正在被另一个write line程竞争互斥地使用同一个资源,则当前line程序只能放弃争夺该资源,并等待其释放或轮换机会;只有当该特定write lock被释放后才有可能继续争夺同一资源的write lock支持重入机制
读写锁
- 一旦获得 write lock 之后,则在同一时间只能被一个用户所拥有。
- 加入 write lock 的用户必须对该共享数据进行修改操作。
- 如果你获得了 read lock,则该资源上可同时存在多个用户对该资源进行 read 操作。
- 一旦获取 read lock,则该资源上不能再添加 write lock。
注:Java锁的分类请参看:<>
锁降级
遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级称为读锁
4.3 ReentrantReadWriteLock
读读共享
两个线程可并行执行read函数,在观察到lock()操作及其后续代码得以同步执行后发现这两条指令所花费的时间呈现完全相同的时间值。通过这种方式使得整个程序的操作效率提升了大约20%左右
private ReentrantReadWriteLock lock;
public TestPipe(Boolean isFair){
this.lock=new ReentrantReadWriteLock(isFair);
}
@Override
public void run() {
super.run();
read();
}
public void read() {
try {
lock.writeLock().lock();
System.out.println("获得读锁" + Thread.currentThread().getName()
+ " " + System.currentTimeMillis());
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
public static void main(String[] args) {
TestPipe testPipe=new TestPipe(true);
Thread a=new Thread(testPipe);
Thread b=new Thread(testPipe);
a.start();
b.start();
}
结果:
获得读锁Thread-2 1564471409644
获得读锁Thread-1 1564471409644
写写互斥
把上面的代码的
lock.readLock().lock();
改为:
lock.writeLock().lock();
运行结果:
获得读锁Thread-1 1564471587448
获得读锁Thread-2 1564471597448
两个线程并发地执行read函数,在其中一个是编号158的线程和另一个是编号159的线程的情况下发现
读写互斥
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read() {
try {
lock.readLock().lock();
System.out.println("获得读锁" + Thread.currentThread().getName()
+ " " + System.currentTimeMillis());
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
public void write() {
try {
lock.writeLock().lock();
System.out.println("获得写锁" + Thread.currentThread().getName()
+ " " + System.currentTimeMillis());
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
测试代码:
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
Thread.sleep(1000);
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
启动两个基于同一Service对象实例的线程a和b,在该框架中,线程a负责调用前述read方法而线程b则处理前述write方法。每当涉及书写操作时即需实施互斥机制以确保数据一致性
