当前位置:网站首页 > Java基础 > 正文

java基础线程与进程理论




一、线程的概述

1.1 线程的相关概念

1.1.1 进程(Process)

进程(Process)是计算机的程序关于某数据集合上的一次运行活动,是操作系统进行资源分配与调度的基本单位。
可以把进程简单的理解为操作系统中正在有运行的一个程序。

1.1.2 线程

线程(thread)是进程的一个执行单元。
一个线程就是进程中一个单一顺序的控制流,进程的一个执行分支。
进程是线程的容器,一个进程至少有一个线程。一个进程中也可以有多个线程。
在操作系统中是以进程为分配资源,如虚拟存储空间,文件描述符等,每个线程都有各自的线程栈。都有自己的寄存器环境,都有自己的线程本地存储。

1.1.3主线程与子线程

JVM 启动时会创建一个主线程,该主线程负责执行main方法,主线程就是运行main方法的线程。
java中的线程不是孤立的,线程之间也存在一些联系,如果在A线程中创建了B线程,称B线程为A线程的子线程,相应的A线程就是B线程的父线程。

1.1.4 串行,并发与并行

假设有三个任务:

任务A准备5分钟,等待10分钟。

java 异步线程里再次开启线程池_学习

任务C准备10分钟;

java 异步线程里再次开启线程池_System_02

并发可以提高以事物的处理效率,即一段事件内可以处理或者完成更多的事情。
并行是一种更为严格,理想的并发。
从硬件角度来说,如果单核CPU,一个处理器一次只能执行一个线程的情况下,处理器可以使用时间片轮转技术,可以让CPU 快速的在各个线程之间进行切换,对于用户来说,感觉是三个线程在同时执行,如果是多核CPU,可以为不同的线程分配不同的CPU内核。

1.2 线程的创建与启用

在Java中,创建一个线程,就是创建一个Thread类(子类)的对象(实例)。Thread类有两个常用的构造方法:与对应的创先线程的两种方式:
定义Thread类的子类
定义一个Runnable接口的实现类
这两种创建线程的方式没有本质的区别

1.2.1 定义Thread类的子类

运行结果

java 异步线程里再次开启线程池_学习_03

1.2.2 多线程运行结果是随机的

运行结果:

java 异步线程里再次开启线程池_笔记_04


java 异步线程里再次开启线程池_System_05

1.2.3 实现Runnable接口的形式创建线程

运行结果:

java 异步线程里再次开启线程池_java 异步线程里再次开启线程池_06

1.3 线程的常用方法

1.3.1 currentThread 方法

测试结果:

java 异步线程里再次开启线程池_笔记_07


java 异步线程里再次开启线程池_笔记_08


currentThread 复杂案例

运行结果

java 异步线程里再次开启线程池_java_09

1.3.2 setName()/getName()

1.3.3 isAlive()

运行结果

java 异步线程里再次开启线程池_System_10

1.3.4 sleep()

运行结果

java 异步线程里再次开启线程池_System_11

运行结果

java 异步线程里再次开启线程池_java_12

1.3.4.1 使用sleep 实现倒计时器

main方法的args 如何传参

java 异步线程里再次开启线程池_学习_13

java 异步线程里再次开启线程池_java_14


运行结果

java 异步线程里再次开启线程池_java_15

1.3.5 getId()

1.3.6 yield()

java基础线程与进程理论

运行结果:

java 异步线程里再次开启线程池_笔记_16

1.3.7 setPriority()

运行结果:

java 异步线程里再次开启线程池_学习_17

1.3.8 interrupt()

中断线程
注意调用interupt() 方法仅仅是在当前线程打一个停止标志,并不是真正的停止线程。

运行结果:

java 异步线程里再次开启线程池_System_18

1.3.9 setDaemon()

java 中的线程分为用户线程与守护线程。
守护线程是为其他线程提供服务的线程,如垃圾回收器(GC)就是一个典型的守护线程。
守护线程不能单独运行,当JVM中没有其他用户线程,只有守护线程时,守护线程会自动销毁,JVM 会退出。

运行结果

java 异步线程里再次开启线程池_System_19

1.4 线程的生命周期

线程的生命周期是线程对象的生老病死,即线程的状态。
,线程的状态是Thread.state枚举类型定义的,
由一下几种:
: 新建状态,创建了线程对象,在调用start()启动之前的状态;
:可运行状态,它是一个复合状态,包含:READY 和RUNNING 两个状态,READY状态改线程可以被线程调度器进行调度使他处于RUNNING状态。RUNNING状态表示改线程正在执行。
Thread.yield ()方法可以把线程由RUNNING 状态转换为READY状态。
阻塞状态,线程发起阻塞的I/O操作,或者申请由其他线程占用的独占资源,线程会转换为BLOCKED阻塞状态,处于阻塞状态的线程不会占用CPU资源,当阻塞I/O操作执行完,或者线程获得了其神奇的资源,,线程可以转换为RUNNABLE。

:等待状态,线程执行了object.wait(),thread.join()方法会把线程转换为WAITING等待状态,执行object.notify()方法,或者加入的线程执行完毕,当前线程会转换为RUNNABLE状态。

状态,与WAITING 状态类似,都是等待状态,区别在于处于改状态的线程不会无线的等待,如果线程没有在指定的时间范围内完成期望的操作,该线程会自动转换为RUNNABLE。

终止状态,线程结束处于终止状态

java 异步线程里再次开启线程池_java 异步线程里再次开启线程池_20

1.5 多线程编程的优势与存储的风险

多线程编程具有以下优势:

1)提高系统的吞吐率(Throughout)。多线程编程可以使一个进程有多个并发(concurrent,即同时进行的)的操作。
2) 提高响应性(Responsiveness)。Web 服务器会采用一些专门的线程负责用户的请求处理,缩短了用户的等待时间。
3)充分利用多核(Multicore)处理器资源,通过多线程可以充分的利用CPU资源
多线程编程存在的问题与风险:
1) 线程安全(Thread safe)问题:多线程共享数据时,如果没有采用正确的并发访问控制措施,就可能会产生数据一致性问题,如读取脏数据(过期的数据),如丢失数据更新。
2)线程活性(thread liveness)问题。由于程序自身的缺陷或者由资源稀缺性导致线程一直处于非RUNNABLE状态,这就是线程活性问题,常见的活性故障有以下几种

  • 死锁(Deadlock)类似于鹬蚌相争。
  • 锁死(Lockout)类似于睡美人,故事中王子挂了。
  • 活锁(Livelock)类似于小猫咬自己尾巴
  • 饥饿(Starvation)类似于健壮的雏鸟总是从母鸟嘴里抢到实物。

3)上下文切换(Context Switch)处理器从执行一个线程切换到执行另外一个线程
4)可靠性。可能会由一个线程导致JVM意外终止,其他的线程也无法执行。

二、线程安全问题

非线程安全主要是指多个线程对同一个对象的实例变量进行操作时,会出现值被更改,值不同步的情况
线程安全问题表现为三个方面: 原子性,可见性、有序性

2.1 原子性

原子(Atomic)就是不可分割的意思。原子操作的不可分割有两层含义:
1)访问(读、写)某个共享变量的操作从其他线程来看,该操作要么已经执行完毕,要么尚未发生,即其他线程年示到当前操作的中间结果
2)访问同一组共享变量的原子操作时不能够交错的。如现实生活中从ATM机取款,对于用户来说,要么操作成功,用户拿到钱,余额减少了,增加了一条交易记录;要么没拿到钱,相当于取款操作没有发生。
java有两种方式实现原子性:一种是使用锁;另一种利用处理器的CAS(Compare and Swap)指令。
锁具有排他性,保证共享变了在某一时刻只能被一个线程访问。
CAS指令直接在硬件(处理器和内存)层次上实现,看作是硬件锁。

java 异步线程里再次开启线程池_笔记_21


在java中提供了一个线程安全的AtomicInteger类,保证了操作的原子性

java 异步线程里再次开启线程池_学习_22

2.2 可见性

在多线程环境中,一个线程对某个共享变量进行更新之后,后续其他线程可能无法立即读取到这个更新的结果,这个就是线程安全问题的另外一种形式:可见性(visibility)
如果一个线程对共享变量更新后,后续访问该变量的其他线程可以读到更新的结果,称这个线程对共享变量的更新对其他线程可见,否则称这个线程对共享变量的更新对其他线程不可见
多线程程序因为可见性问题可能会导致其他线称读取到了旧数据(脏数据)

运行结果:

java 异步线程里再次开启线程池_笔记_23

2.3 有序性

有序性(Ordering)是指在什么情况下一个处理器上运行的一个线程所执行的,内存访问操作在另外一个处理器运行的其他线程来看是乱序的(Out of Order)。
乱序是指内存访问操作的顺序看起来发生了变化。

2.3.1 重排序

在多核处理器的环境下,编写的顺序结构,这种操作执行的顺序可能是没有保障的:

  • 编译器可能会改变两个操作的顺序;
  • 处理器也可能不会按照目标代码的顺序执行;

这种一个处理器上执行的多个操作,在其他处理器来看它的顺序与目标代码指定的顺序可能不一样,这种现象称为重排序。
重排序是对内存访问有序操作的一种优化,可以在不影响单线程程序正确的情况下提升程序的性能。但是,可能对多线程程序的正确性产生影响,即可能导致线程安全问题。
重排序与可见性问题类型,不是必然出现的。
与内存操作循序有关的几个概念:

  • 源代码顺序,就是源码中指定的内存访问顺序。
  • 程序顺序,处理器上运行的目标代码所指定的内存访问顺序。
  • 执行顺序,内存访问操作在处理器上实际执行顺序。
  • 感知顺序,给定处理器所感知到的该处理器及其他处理器的内存访问操作的顺序。

可以把重排序分为指令重排序与存储子系统重排序两种。
指令重排序主要是由JIT编译器,处理器引起的,指程序顺序与执行顺序不一样。
存储子系统重排序是由高速缓存,写缓冲器引起的,感知顺序与执行顺序不一致

2.3.2 指令重排序

在源码顺序与程序顺序不一致,或者程序顺序与执行顺序不一致的情况下,我们就说发生了指令重排序(Instruction Reorder)。
指令重排序是一种动作,确实对指令的顺序做了调整,重排序的对象指令。
javac编译器一般不会执行指令重排序,而JIT编译器可能执行指令重排序。
处理器也可能执行指令重排序,使得执行顺序与程序顺序不一致。
指令重排不会对单线程程序的结果正确性产生影响,可能导致多线程程序出现非预期的结果。

2.3.3 存储子系统重排序

存储子系统是指写缓冲器与高速缓存。

  • 高速缓存(Cache)是CPU中为了匹配与主内存处理速度不匹配而设计的一个高速缓存。
  • 写缓冲器(Store buffer,Write buffer)用来提高写高速缓存操作的效率。

即使处理器严格按照程序顺序执行两个内存访问操作,在存储子系统的作用下,其他处理器对这两个操作的感知顺序与程序顺序不一致,即这两个操作的顺序,顺序看起来像是发生了变化,这种现象为存储子系统重排序。
存储子系统重排序并没有真正的对指定执行顺序进行调整,而是造成一种指令执行顺序被调整的假象。
存储子系统重排序对象是内存操作的结果,
从处理器角度来看,读内存就是从指定的RAM地址中加载数据到寄存器,称为Load操作;写内存就是把数据存储到指定的地址表示的RAM存储单元中,称为Store操作,内存重排序有以下四种可能:

  • LoadLoad重排序,一个处理器先后执行两个读操作L1和L2,其他处理器对两个内存操作的感知顺序可能L2->L1;
  • StoreStore重排序,一个处理器先后执行两个写操作W1和W2,其他处理器对两个内存操作的感知顺序可能W2->W1;
  • LoadStore重排序,一个处理器先执行读内存操作L1,再执行写内存操作W1,其他处理器对两个内存操作的感知顺序可能是W1->L1;
  • StoreLoad重排序:一个处理器先执行写内存操作W1,在执行读内存操作L1,其他处理器对两个内存操作的感知顺序可能是L1->W1;

内存重排序与具体的处理器微架构有关,不同架构的处理器所允许的内存重排序不同。
内存重排序可能会导致线程安全问题。假设有两个共享变量,int data=0;boolean ready = false;

处理器1

处理器2

data=1; //s1

ready = true; //s2

while(! ready) {} //L3

sout(data); // L4

/div>

h4 id="h30">2.3.4 貌似串行语义

/h4>

p>JIT编译器、处理器、存储子系统是按照一定的规则对指令,内存操作的结果进行重排序,给单线程程序造成一种假象------指令是按照源码的顺序执行的,这种假象称为貌似串行语义。并不能保证多线程环境程序的正确性,


为了保证貌似串行语义,有数据依赖关系的雨具不会被重排序,只有不存在数据依赖关系的雨具才会被重排序。如果两个操作(指令)访问同一个变量,且其中一个操作(指令)为写操作,那么这两个操作之间就存在数据依赖关系(data dependency)。


如:

/p>

blockquote style="margin-top: 5px; margin-bottom: 5px; padding-left: 1em; margin-left: 0px; border-left: 3px solid rgb(238, 238, 238); opacity: 0.6;">

x=1;y=x+1;后一条语句的操作数包含前一条语句的执行结果;
y=x;x=1;先读取X变量,再更新x变量的值;
x=1;x=2;两条语句同时对一个变量进行写操作;

/blockquote>

p>如果不存在数据依赖关系则可能重排序,如:

/p>

blockquote style="margin-top: 5px; margin-bottom: 5px; padding-left: 1em; margin-left: 0px; border-left: 3px solid rgb(238, 238, 238); opacity: 0.6;">

double price = 45.8;//可能重排序
int quantity = 10;// 可能重排序 double sum =
price * quantiry;// 不可能重排序

/blockquote>

p>存在控制依赖关系的语句允许重排,一条语句(指令)的执行结果会决定另一条语句(指令)能否被执行,这两条语句(指令)存在控制依赖关系(Control Dependency)。如在if语句中运行重排,可能存在处理器先执行if代码块,在判断if条件是否成立。

/p>

h4 id="h31">2.3.5 保证内存访问的顺序性

/h4>

p>可以使用volatile关键字,synchronized关键字实现有序性。

/p>

h3 id="h32">2.4 JAVA内存模型

/h3>

p style="text-align:center;">

java 异步线程里再次开启线程池_学习_24

/p>

p style="text-align:center;">

java 异步线程里再次开启线程池_java 异步线程里再次开启线程池_25

/p>

h2 id="h33">三. 线程同步

/h2>

h3 id="h34">3.1 线程同步机制简介

/h3>

p>线程同步机制是一套用于协调线程之间的数据访问的机制,该机制可以保障线程安全。


java 平台提供的线程同步机制包括:锁,volatile 关键字,final关键字,static 关键字,以及相关的API,如Object.wait()/Object.notify()等。

/p>

h3 id="h35">3.2 锁概述

/h3>

p>线程安全问题的产生前提是多个线程并发访问共享数据。


/p>

p>将多个线程对共享数据的并发访问,转换为串行访问,即一个共享数据一次只能被一个线程访问,锁就是利用这种思路来保障线程安全带。


/p>

p>锁(Lock)可以理解为对共享数据进行保护的一个许可证。对于同一个许可证保护的共享数据来说,任何线程想要访问这些共享数据必须先持有该许可证,一个线程只有在持有许可证的情况下,才能对这些共享数据进行访问,并且一个许可证一次只能被一个线程持有;许可证线程结束在结束对共享数据的访问后必须释放其持有的许可证。


/p>

p>一线程在访问共享数据前必须先获得锁,获得锁的线程称为锁的持有线程;一个锁一次只能被一个线程持有,所得持有线程在获得锁之后和释放锁之前,这段时间锁执行的代码称为临界区(Critical Section)。


/p>

p>锁具有排他性(Exclusive),即一个锁一次只能被一个线程持有。这种锁称为排它锁或互斥锁(Mutex)。


/p>

p style="text-align:center;">

java 异步线程里再次开启线程池_学习_26

/p>

p>JVM 把锁分为内部锁和显示锁两种,内部锁通过synchrinized关键字实现;显示锁通过java.concurrent.looks.Lock 接口的实现类实现的。

/p>

h4 id="h36">3.2.1 锁的作用

/h4>

p>锁可以实现对共享数据的安全访问,保障线程的原子性,可见性与有序性。


锁是通过互斥保障原子性。一个锁只能被一个线程持有,这就保证临界区的代码一次只能被一个线程执行。使得临界区代码所执行的操作自然而然的具有不可分割的特性,即具备了原子性。


可见性的保障是通过写线程来冲刷处理器的缓存和读线程刷新处理器缓存这两个动作实现的,在java平台中,锁的获得隐含着刷新处理器缓存的动作,锁的释放隐含着冲刷处理器缓存的动作。


锁能够保障有序性,写线程在临界区所执行的在读线程所执行的临界区看来像是完全按照源码顺序执行的。


注意:使用锁保障线程的安全性,必须满足以下条件:

/p>

ol>

  • 这些线程在访问共享数据时必须使用同一个锁。
  • 即使是读取共享数据的线程也需要使用同步锁。
  • /ol>

    h4 id="h37">3.3.2 锁相关的概念

    /h4>

    ul>

  • 可重入性
    可重入性(Reentrancy)描述这样一个问题:一个线程持有该锁的时候能再次(多次)申请该锁。
  • java 异步线程里再次开启线程池_java 异步线程里再次开启线程池_27


  • java 异步线程里再次开启线程池_学习_28

  • 如果一个线程持有一个锁的时候,还能够继续成功申请该锁,称该锁是可重入的,否则就称为不可重入的。
  • 锁的争用与调度
    java 平台中内部锁属于非公平锁,显示Lock锁即支持公平锁又支持非公平锁。
  • 锁的粒度
    一个锁可以保护的共享数据的数量大小称为锁的粒度。
    锁保护共享数据量大,称该锁的粒度粗,否则就称该锁的粒度细。
    锁的粒度过粗,会导致线程在申请锁时会进行不必要的等待;锁的粒度过细会增加锁调度的开销。
  • /ul>

    h3 id="h38">3.3 内部锁:synchronized关键字

    /h3>

    p>java中的每个对象都有一个与之关联的内部锁(Intrinsic lock),这种锁也称为监视器(Monitor),这种内部锁是一种排他锁,可以保障原子性,可见性与有序性。


    内部锁时通过synchronized关键字实现的,synchronized关键字修饰代码块,修饰该方法。


    修饰代码块的语法:

    /p>

    div>

    /div>

    p>修饰示例方法就称为同步实例方法


    修饰静态方法称为同步静态方法

    /p>

    h4 id="h39">3.3.1 synchronized同步代码块

    /h4>

    h4 id="h40">3.3.1.1 同步代码块 this 锁对象

    /h4>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_System_29

    /p>

    div>

    /div>

    p>

    运行效果

    /p>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_java_30

    /p>

    p>


    /p>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_笔记_31

    /p>

    h4 id="h41">3.3.1.2 锁对象不同,不能实现同步

    /h4>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_System_32

    /p>

    h6>3.3.1.3 使用常量作为锁对象

    /h6>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_笔记_33

    /p>

    h4 id="h42">3.3.1.4 不管是实例方法还是静态方法,只要是同一个锁对象,就能实现同步

    /h4>

    div>

    /div>

    p>

    运行结果

    /p>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_学习_34

    /p>

    h3 id="h43">3.3.2 同步方法

    /h3>

    h4 id="h44">3.3.2.1 同步实例方法

    /h4>

    div>

    /div>

    p>

    运行结果

    /p>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_java 异步线程里再次开启线程池_35

    /p>

    h4 id="h45">3.3.2.2 同步静态方法

    /h4>

    div>

    /div>

    p>

    运行结果

    /p>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_学习_36

    /p>

    h4 id="h46">3.3.2.3 同步方法 和 同步代码块哪个执行效率高

    /h4>

    div>

    /div>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_java_37

    /p>

    h4 id="h47">3.3.2.4 脏读

    /h4>

    div>

    /div>

    p>

    运行结果:出现脏读

    /p>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_笔记_38

    /p>

    p>


    /p>

    p>

    解决:

    /p>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_学习_39

    /p>

    h4 id="h48">3.3.2.5 线程出现异常,会自动释放锁

    /h4>

    div>

    /div>

    p>

    运行结果

    /p>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_笔记_40

    /p>

    h3 id="h49">3.3 死锁

    /h3>

    p>在多线程程序中,同步时可能需要使用多个锁,如果获得锁的顺序不一致,可能会导致死锁;


    如何避免死锁?


    当需要获得多个锁时,所有线程获得锁的顺序保持一致即可

    /p>

    div>

    /div>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_java 异步线程里再次开启线程池_41

    /p>

    h3 id="h50">3.4 轻量级同步机制:volative 关键字

    /h3>

    h4 id="h51">3.4.1 volatile 关键字

    /h4>

    p>volatile关键的作用使变量在多个线程之间可见,


    为什么要使用volatile

    /p>

    div>

    /div>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_System_42

    /p>

    p>


    /p>

    p>使用多线程案例:


    /p>

    div>

    /div>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_java_43

    /p>

    p>


    /p>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_笔记_44

    /p>

    p>

    volatile 与Synchronized 比较

    /p>

    ul>

  • volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比Synchronized要好,volatile只能修饰变量,而Synchronized可以修饰方法、代码块。随着JDK新版本的发布,synchronized的执行效率也有较大的提升,在开发使用Synchronized的比率还是很大的。
  • 多线程访问volatile 变量不会发生阻塞,而Synchronized可能会阻塞
  • volatile 能保证数据的可见性,但是不能保证原子性;而Synchronized可以保证原子性,也可以保证可见性。
  • 关键字volatile解决的是变量在多个线程之间的可见性;而Synchronized关键字解决多个线程之间的访问公共资源同步性。
  • /ul>

    h4 id="h52">3.4.2 volatile 非原子特性

    /h4>

    p>volatile 关键字增加了实例变量在多个线程之间的可见性,但是不具备原子性。

    /p>

    div>

    /div>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_笔记_45

    /p>

    h4 id="h53">3.4.3 常用原子类进行自增自减操作

    /h4>

    p>我们知道i++ 操作不是原子操作,除了使用Synchronized进行同步外,也可以使用AtomicInteger/AtomicLong 原子类进行实现。

    /p>

    div>

    /div>

    p>

    运行结果

    /p>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_java_46

    /p>

    h3 id="h54">3.5 CAS

    /h3>

    h4 id="h55">3.5.1 CAS概述

    /h4>

    p>CAS(Compare And Swap)是由硬件实现的。


    CSA可以将read-modify-write 这类操作转换为原子操作。

    /p>

    blockquote style="margin-top: 5px; margin-bottom: 5px; padding-left: 1em; margin-left: 0px; border-left: 3px solid rgb(238, 238, 238); opacity: 0.6;">

    i++自增操作包括三个子操作:
    从主内存读取i变量值,
    对i的值加1
    在把加1之后的值保存到主内存

    /blockquote>

    p>

    CAS原理:在把数据更新到主内存时,再次读取主内存变量的值,如果现在变量的值与期望的值(操作起始时读取的值)一样就更新。

    /p>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_学习_47

    /p>

    h4 id="h56">3.5.2 使用CAS实现线程安全的计数器:

    /h4>

    div>

    /div>

    p>

    运行结果

    /p>

    p style="text-align:center;">

    java 异步线程里再次开启线程池_学习_48

    /p>

    h4 id="h57">3.5.3 CAS中ABA问题

    /h4>

    p>CAS 实现原子操作背后有一个假设:共享变量的当前值与当前线程提供的期望值相同,就认为这个变量没有被其他线程修改过。


    实际上这种假设不一定总是成立,如有共享变量:count = 0;

    /p>

    blockquote style="margin-top: 5px; margin-bottom: 5px; padding-left: 1em; margin-left: 0px; border-left: 3px solid rgb(238, 238, 238); opacity: 0.6;">

    A线程对count修改为10;
    B线程对count修改为20 ;
    C线程对count 修改为10;

    /blockquote>

    p>当前线程看到count变量的值现在是0,现在是否认为count变量的值没有被其他线程更新呢?这种结果是否能够接受?


    这就是CAS中的ABA问题,即共享变量经历了A->B->A的更新。


    是否能够接受ABA问题跟实现算法有关。


    如果想要规避ABA问题,可以为共享变量引入一个修订号(时间戳),没次修改共享变量时,相应的修订号就会增加1。ABA变量的更新过程:[A,0]-》[B,1]-》[A,2],每次对共享变量的修改都会导致修订号的增加,通过修订号依然可以准确判断变量是否被其他线程修改过。类就是基于这种思想产生的。

    /p>

    h3 id="h58">3.6 原子变量类

    /h3>

    p>原子变量类基于CSA实现的,当对共享变量进行read-modefy-write更新操作时,通过原子变量类可以保障操作的原子性与可见性,对变量的read-modefy-write 更新操作时指当前操作不是一个简单的赋值,而是变量的新值依赖变量的旧值,如自增操作i++;由于volatile 只能保证可见性,无法保障原子性,原子变量类的内部就是借助一个volatile变量,并且保障了该变量的read-modefy-write操作的原子性,有时把原子变量类看作增强的volatile变量,原子变量类有12个


    /p>

    div class="data-table" data-width="" style="outline: none; border-collapse: collapse; width: 100%;">

    /colgroup>

    tbody>

    分组

    原子变量类

    基础数据型

    AtomicInteger,AtomicLong,AtomicBoolean

    数组型

    AtomicIntegerArray, AtomicLongArray,AtomicReferenceArray

    字段更新器

    AtomicIntegerFieldUpdater,AtomicLongFieldUpdaer,AtomicReferenceFieldUpdater

    引用型

    AtomicReference,AtomicStampedRefernce,AtomicMarkableReference

    /tbody>

    br> 该计数器,在整个程序中都能使用,并且所有的地方都使用这一个计数器,这个计数器可以设计为单例

    pre class="language-plain">

    /pre>

    pre class="language-plain">

    /pre>

    strong>运行结果:

    /strong>

    img src='https://s2.51cto.com/images/blog/202410/09013626_67056d9a5d47e9991.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_学习_49' title="在这里插入图片描述" style="width: 521px; visibility: visible;">

    strong>原子更新数组

    /strong>

    pre class="language-plain">

    /pre>

    strong>把原子数组的每个元素自增1000次。

    /strong>

    pre class="language-plain">

    /pre>

    strong>运行结果:

    /strong>

    img src='https://s2.51cto.com/images/blog/202410/09013626_67056d9a9fc0a25378.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_System_50' title="在这里插入图片描述" style="width: 864px; visibility: visible;">

    strong>要求

    /strong>:

    br> 1) 字符必须使用volatile修饰,使线程之间可见;

    br> 2)只能是实例变量,不能是静态变量,也不能是final变量。

    pre class="language-plain">

    /pre>

    pre class="language-plain">

    /pre>

    pre class="language-plain">

    /pre>

    strong>运行结果:

    /strong>

    img src='https://s2.51cto.com/images/blog/202410/09013626_67056d9adb29c94196.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_java 异步线程里再次开启线程池_51' title="在这里插入图片描述" style="width: 389px; visibility: visible;">

    pre class="language-plain">

    /pre>

    strong>运行结果:

    /strong>

    img src='https://s2.51cto.com/images/blog/202410/09013627_67056d9b25e4c29839.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_java 异步线程里再次开启线程池_52' title="在这里插入图片描述" style="width: 464px; visibility: visible;">

    pre class="language-plain">

    /pre>

    br>

    img src='https://s2.51cto.com/images/blog/202410/09013627_67056d9b6e88675949.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_java_53' title="在这里插入图片描述" style="width: 493px; visibility: visible;">

    pre class="language-plain">

    /pre>

    strong>运行结果:

    /strong>

    img src='https://s2.51cto.com/images/blog/202410/09013627_67056d9b9a6f260761.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_java 异步线程里再次开启线程池_54' title="在这里插入图片描述" style="width: 442px; visibility: visible;">

    br> 在多线程编程中,可能A线程的条件没有满足只是暂时的,稍后其他的线程B可能会更新条件,使得A线程的条件得到满足,可以将A线程暂停,直到它的条件得到满足后再将A线程唤醒。

    br>

    strong>它的伪代码:

    /strong>

    pre class="language-plain">

    /pre>

    br>

    strong>注意

    /strong>:

    p>1.wait()方法只能在同步代码块中由锁对象调用。


    2.调用wait()方法,当前线程会释放锁。

    /p>

    strong>它的伪代码:

    /strong>

    pre class="language-plain">

    /pre>

    br>

    strong>它的伪代码如下:

    /strong>

    pre class="language-plain">

    /pre>

    strong>实例代码

    /strong>

    br>

    strong>例1:演示wait 必须不放在同步代码块的效果

    /strong>

    pre class="language-plain">

    /pre>

    img src='https://s2.51cto.com/images/blog/202410/09013627_67056d9bdd64681624.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_System_55' title="在这里插入图片描述" style="width: 733px; visibility: visible;">

    br>

    strong>例2:wait()方法的正确使用

    /strong>

    pre class="language-plain">

    /pre>

    img src='https://s2.51cto.com/images/blog/202410/09013628_67056d9c2a1ce2489.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_java_56' title="在这里插入图片描述" style="width: 485px; visibility: visible;">

    br>

    br>

    pre class="language-plain">

    /pre>

    img src='https://s2.51cto.com/images/blog/202410/09013628_67056d9c6198047800.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_学习_57' title="在这里插入图片描述" style="width: 522px; visibility: visible;">

    pre class="language-plain">

    /pre>

    strong>运行结果:

    /strong>

    img src='https://s2.51cto.com/images/blog/202410/09013628_67056d9ca2e9f66623.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_System_58' title="在这里插入图片描述" style="width: 708px; visibility: visible;">

    pre class="language-plain">

    /pre>

    strong>运行结果:

    /strong>

    img src='https://s2.51cto.com/images/blog/202410/09013628_67056d9cda21069150.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_学习_59' title="在这里插入图片描述" style="width: 280px; visibility: visible;">

    pre class="language-plain">

    /pre>

    strong>运行结果:

    /strong>

    img src='https://s2.51cto.com/images/blog/202410/09013629_67056d9d0ba9a36380.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_System_60' title="在这里插入图片描述" style="width: 631px; visibility: visible;">

    br>

    img src='https://s2.51cto.com/images/blog/202410/09013629_67056d9d3a2d841308.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_学习_61' title="在这里插入图片描述" style="width: 724px; visibility: visible;">

    pre class="language-plain">

    /pre>

    strong>运行结果

    /strong>

    img src='https://s2.51cto.com/images/blog/202410/09013629_67056d9d6059345487.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_笔记_62' title="在这里插入图片描述" style="width: 399px; visibility: visible;">

    br> 通知过早实例:

    pre class="language-plain">

    /pre>

    img src='https://s2.51cto.com/images/blog/202410/09013629_67056d9d9eb3710936.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_笔记_63' title="在这里插入图片描述" style="width: 451px; visibility: visible;">

    br> 线程调度器在什么时候开启这个线程不确定,即调用start()方法的顺序,并不一定就是线程开启的顺序

    br> 在当前实例中,t1线程等待后,再让t2线程唤醒,如果t2线程先唤醒了,就不让t1线程等待了。

    br>

    strong>解决通知过早实例:

    /strong>

    pre class="language-plain">

    /pre>

    img src='https://s2.51cto.com/images/blog/202410/09013629_67056d9ddb48786197.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_笔记_64' title="在这里插入图片描述" style="width: 737px; visibility: visible;">

    pre class="language-plain">

    /pre>

    img src='https://s2.51cto.com/images/blog/202410/09013630_67056d9e1f8fe2941.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_System_65' title="在这里插入图片描述" style="width: 482px; visibility: visible;">

    li>

    生产-消:操作值
    代码示例:

    /li>

    pre class="language-plain">

    /pre>

    pre class="language-plain">

    /pre>

    pre class="language-plain">

    /pre>

    strong>测试 1生产1 消费

    /strong>

    pre class="language-plain">

    /pre>

    strong>测试多生产多消费

    /strong>

    pre class="language-plain">

    /pre>

    strong>运行结果:

    /strong>

    img src='https://s2.51cto.com/images/blog/202410/09013630_67056d9e4e8ae96789.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_System_66' title="在这里插入图片描述" style="width: 529px; visibility: visible;">

    li>

    操作栈

    使生产者把数据存储到List集合中,消费者从list集合中取数据,使用list集合模拟栈。


    实例:

    /li>

    pre class="language-plain">

    /pre>

    pre class="language-plain">

    /pre>

    pre class="language-plain">

    /pre>

    pre class="language-plain">

    /pre>

    pre class="language-plain">

    /pre>

    img src='https://s2.51cto.com/images/blog/202410/09013630_67056d9e7d15f77484.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_学习_67' title="在这里插入图片描述" style="width: 637px; visibility: visible;">

    pre class="language-plain">

    /pre>

    img src='https://s2.51cto.com/images/blog/202410/09013630_67056d9eb21cf99651.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184' alt='java 异步线程里再次开启线程池_System_68' title="在这里插入图片描述" style="width: 1156px; visibility: visible;">

    pre class="language-plain">

    /pre>

    pre class="language-plain">

    /pre>

    pre class="language-plain">

    /pre>

    版权声明


    相关文章:

  • java基础不太行怎么找工作2024-10-16 23:18:01
  • 学java的基础单词2024-10-16 23:18:01
  • java前后端基础不好2024-10-16 23:18:01
  • Java基础继承题目2024-10-16 23:18:01
  • java掌握基础语法2024-10-16 23:18:01
  • java基础类库翻译2024-10-16 23:18:01
  • java基础类型怎么记2024-10-16 23:18:01
  • java基础函数库在哪个包下2024-10-16 23:18:01
  • java基础面试录音2024-10-16 23:18:01
  • 零基础java软件2024-10-16 23:18:01