java基础知识——Java并发机制

章节一:基础模型
章节二:多线程机制[this,join]
章节三:Lock机制(AQS[变量,模版方法,同步队列读写],ReentantLock[重入锁,公平锁,区别],ReentantReadWriterLock[实现机制 I,降级锁],condition[实现机制])
章节四:数据结构(ConcurrentHashMap[1.7,1.8变量,增,扩容,get],ConcurrentLinkedQueue[读,写],BlockingQueue[分类],fork/join[表世界,里世界])
章节五:轻量级同步机制 CountDownLatch CycleBarrier Semaphore Exchanger
章节六:线程池
章节七:Executor
章节八:原子类

基础模型

JMM模型(java memory model):
其中共享内存=堆/本地方法区内存,本地内存=虚拟机栈(操作数栈)等
在这里插入图片描述并发的三个特性:只有满足三个特性才能保证并发正常进行
参考资料
参考资料
原子性:要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
1:32位系统中,除了long和double的赋值都是原子性的
因为long和double是64位,在32位系统中是分两次读取,很有可能低32位是旧数据而高32位是新数据,解决方法是加上volatile。而如果是64位系统则是原子性的
2:所有引用的赋值都是原子性的
可见性:当某一个线程对共享变量进行修改时,主存会立即同步共享变量的值正常情况是写入本地内存,但是没有刷新到主内存
volatile,synchronized和Lock
有序性:程序执行的顺序按照代码的先后顺序执行
利用volatile,s,lock等。程序也有先天的有序性即happens-before原则

三个特性都满足,即为顺序一致性模型:理想化的模型不是JMM模型
1:所有操作必须按照程序的顺序执行
2:所有线程并发运行时,都只看到一个统一的执行顺序(也就是说,每个线程对共享变量的修改,必须立即刷新的主存)

原因:
重排序:在不改变计算结果的前提下,尽可能提高并行度
编译器重排序:不改变单线程的语义的前提下,可以重排序
处理器重排序:
指令级并行重排序:多条指令重叠执行。一条语句是由多条指令构成的,指令之间流水线运行,一旦某条出错就会中断,优化中断的方法就是指令集重排序
内存系统的重排序:由于使用缓存和缓冲区,加载和存储看上去是在乱序执行

解决方法:
对于编译级重排序:禁止某种特定序列的重排序
对于处理器重排序:内存屏障
内存屏障:
loadload:读读
storestore:写写
loadstore:读写
storeload:写读耗费最大,会刷新到主存
而对于操作者来说,即为as-if-serial,happends-before,volatile,synchronized,Lock,FInal的使用等等

数据依赖性:
两条操作数据依赖,例如a=1,a=2写后写,重排序后结果变化。
控制依赖性:当存在控制依赖性时,会利用猜测,即提前计算aa,当if条件为真时,将结果写入变量
if(flag) int I=a
a;

原则:
单线程:
as-if-serial:遵守数据依赖性,对单个线程遵守数据的依赖性,不会重排序
例如对:
pi=3; //A
r=1; //B
area=pirr; //C
A,B对C都有数据依赖性,则AB必须在C之前执行,但是AB两句数据无关,可以重排序
ABC或者BAC

多线程:
happends-before: 本质上是禁止某种重排序
程序顺序规则: (as-if-serial的封装)
监视器锁规则:解锁happends-before加锁
volatile变量规则:写happends-before读
传递性:
start规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作
join规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
1:Ahappens-beforeB,那么第一个操作的结果对第二个操作可见,并且第一个操作会在第二个操作之前发生
但是这只是对程序员的承诺,意思是你按照happens-before的顺序得到的结果和JVM得到的结果是相同的
2:即使有Ahappens-beforeB,也有可能B在A之前执行,只要这种重排序保证不会影响结果

volatile:
volatile可见性:对一个volatile的读,总可以看到对这个变量最终的写
volatile原子性:volatile对单个读/写具有原子性(包括long和double),但是复合操作除外
如a++,a = a+1, a = b
volatile有序性:能保证一定程度上的有序性

实现:
转化为汇编时,多了一条lock前缀指令,会实现两个功能
1:将写入数据立即刷新到主存
2:让其他线程的缓存数据无效(利用缓存一致性原理,缓存会察觉对应缓存的数据被修改,从而让缓存的数据立即生效)

当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新到主内存中;
当读一个volatile变量时,直接从主内存中读取共享变量。

volatile的底层实现是通过插入内存屏障,但是对于编译器来说,发现一个最优布置来最小化插入内存屏障的总数几乎是不可能的,所以,JMM采用保守策略。如下:
在每一个volatile写操作前面插入一个StoreStore屏障
在每一个volatile写操作后面插入一个StoreLoad屏障我理解为实现lock的功能
在每一个volatile读操作后面插入一个LoadLoad屏障
在每一个volatile读操作后面插入一个LoadStore屏障
StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作都已经刷新到主内存中;
StoreLoad屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序
LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序
LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序

在java中,可以使用synchronized和volatile来保证多线程之间操作的有序性。实现方式有所区别:
volatile关键字会禁止指令重排;
synchronized关键字保证同一时刻只允许一条线程操作。 synchronized是万能,他可以同时满足三种特性,这其实也是很多人滥用synchronized的原因。

final:修饰方法,则方法不能被重写,可以重载。修饰类,则类不可用被继承
final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。
final修饰一个成员变量(属性),必须要显示初始化。1:声明初始化 2:构造函数中初始化

final域写:
禁止final域写重排序到构造方法之外
从而保证该对象对所有线程可见时,该对象的final域全部已经初始化过。
final域写之后,构造函数返回前插入一个StoreStore屏障。
在这里插入图片描述
final域读:禁止初次读对象的引用与读该对象包含的final域的重排序。读final域的操作前插入一个LoadLoad屏障。
引用数据类型:在这里插入图片描述
额外增加约束:禁止在构造函数对一个final修饰的对象的成员域的写入与随后将这个被构造的对象的引用赋值给引用变量重排序

重要知识点:
并发三个特性
重排序,内存屏障
as-if-serial,happens-before
volitile

Java线程

1:线程的实现方法

方法一:extends Thread强耦合

public class MyThread extends Thread {
	public void run() {
		System.out.println("MyThread.run()");
	}
MyThread myThread1 = new MyThread();
myThread1.start();

方法二:implements Runnable:当已经继承其他类时,无法继承Thread耦合稍微弱一点

public class MyThread extends OtherClass implements Runnable {
	public void run() {
		System.out.println("MyThread.run()");
	}
} 

//启动 MyThread,需要首先实例化一个 Thread,并传入自己的MyThread 实例:
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();

方法三:如果有返回值时 implements callable和future结合
参考资料
1:join和使用callable有什么区别
2:future
get方法:获取计算结果(如果还没计算完,也是必须等待的)
cancel方法:还没计算完,可以取消计算过程
isDone方法:判断是否计算完
isCancelled方法:判断计算是否被取消
3:三步初始化
在这里插入图片描述
方法四:Callable+ExecutorService+Future

2:线程的生命周期:

在这里插入图片描述
它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞 (Blocked)和死亡(Dead)5 种状态
新建状态(NEW):使用 new 关键字。分配内存,并初始化其成员变量的值共享区分配
就绪状态(RUNNABLE):使用start。为其创建方法调用栈和程序计数器
私有区的分配
运行状态(RUNNING):使用 run()
阻塞状态(BLOCKED):三种情况

等待阻塞(o.wait->等待对列):
wait-notify/notifyAll
1:wait-notify/notifyAll必须在同步synchronized中执行
2:线程A 获得锁,调用wait进入等待阻塞,释放锁。线程B获得锁,调用notify,此时线程A进入同步阻塞(因为没有synchronized锁),需要等线程B释放锁后才能执行

同步阻塞(synchronized):运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线 程放入锁池(lock pool)中。

其他阻塞(sleep/join)
运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时, JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入可运行(runnable)状态。

终止线程的四种方式:
1:线程正常结束
2:使用退出标志。许多守护线程需要一直进行,而退出标志exit,当修改exit时即可做到退出线程
在这里插入图片描述
3:利用stop()方式
程序中可以直接使用 thread.stop()来强行终止线程,但是 stop 方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果
不安全主要是:thread.stop()调用之后,创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子线程所持有的所有锁从而使得加锁的数据出现非一致性,造成不可预知的错误

4:利用Interrupt()
本质作用:抛出InterruptException异常并且修改中断标志不是像stop一样直接中断
所以可以看到Interrupt本质上不具有直接中断的作用,只不过我们可以利用其进行中断线程。
当进入阻塞状态时:如图,执行代码在try{}中,并且在执行代码中阻塞,此时如果要中断,需要子线程调用Interrupt,抛出的异常被catch捕获,结束while循环。
在这里插入图片描述
当未进入阻塞状态:利用isInterrupted()循环判断。当调用Interrupt时,改变导致while中断。

在这里插入图片描述

面试易考点:
Thread.sleep,Object.wait,LockSupport.park()

  1. sleep()方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值