你若想要面试好,进来就把线程搞!!
哈喽,艾瑞巴蒂,那些还在为面试着急(本人),那些因为房租压得喘不过气(本人),那些手里还么的offer(本人)的道友们。接着上一篇多线程的总结,现在来搞一下线程终结者篇。本人知识有限,尽力而为,如果兄弟姐妹们有更好的知识可以评论指出。sanQ
备注:以下主要是总结最近几天牛客刷题遇到的一些问题,和概念。
重排序?
重排序:指的是编写的程序都会经过编译器和CPU的优化,也就是你写的代码顺序和执行代码的顺序不一致,因为虚拟机会优化代码的顺序。产生原因:在虚拟机中内存运行速度远远小于CPU运行速度,为了减少CPU空闲所带来的影响。虚拟机会按照一定规则将其程序编码顺序打乱,也就是前面代码会后执行后面代码会先执行的意思。再举例子说:假如int a = new [1024],和b = 1;明显前者运行速度会慢,而这个时候b=1就可能会先于前者执行。
一个操作执行的结果需要对另外一个操作可见,那么这两个操作之间必须存在happens-before关系。
happens-before规则?
1)程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。2)监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
3)volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
4)传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
5)start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
6)join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
7)线程终止规则
8)对象终结规则
数据依赖?
数据依赖:顾名思义就是数据相互依赖,一个发生改变就可能影响另一个。举个例子:如果此时两个操作(读写)访问同一个变量,那么此时两个操作之前就存在相应的数据依赖。
只要重排序两个操作的执行顺序,程序的执行结果将会被改变。所以,编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理 器不会改变存在数据依赖关系的两个操作的执行顺序。
volatile
volatile:保证了线程间变量的可见性,也就是线程One修改了变量A的时候,One后面的线程都会看见A的变化。这就是可见性。Volatitl虽好但有一个缺点:就是不能保证原子性。并发编程三要素
1 原子性 :指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行2 可见性:指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果。
3 有序性:程序的执行顺序按照代码的先后顺序来执行。
CAS
CAS:Compare and Swap 比较并交换: CAS是一种基于锁的操作,是一种乐观锁。锁的分类:
悲观锁:synchronized是独占锁即悲观锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。 (好比上厕所,一人进去,百人等待,是不是很悲观呢)
乐观锁:每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。(假设每次都是好的去完成任务,失败了再站起来,成功为止,乐观心态)
CAS 操作包含三个操作数 —— 一个当前内存值V、旧的预期值A、将更新的值B。先获取到内存 当中当前的内存值V,再将内存值V和原值A作比较,要是相等就修改为要修 改的值B并返回true,否则自旋,并返回false。CAS是一组原子操作,不会被外部打断,属于硬件级别的操作,效率比加锁高。
CAS存在的问题(ABA):V初次读取是A,再次读取并赋值的时候还是A,那么是不是就认为它没有被操作过呢?显然不是,中间可能会经过A->B->A等其他操作,但CAS会误认为它没有变,没有被修改过。还有就是因为比较过程中,会有自旋的现象,那么CPU资源会一直被占用。
synchronized,CAS,volatile三者比较:
synchronized:悲观锁,抢夺式锁,一旦锁住别的线程就无法去调用。会引起线程阻塞。
CAS:乐观锁,一种冲突检测的操作。不会引起阻塞,非阻塞。
volatile:提供了线程变量的可变性,同时防止指令重排。
Future
Future:能够让主线程将原来需要同步等待的这段时间用来做其他的事情。(因为可以异步获得执行结果,所以不用一直同步等待去获得执行结果)。出现的原因:因为extends Thread 和 implements Runnable 两种方式在执行完任务之后无法获取执行结果。通过实现Callback接口,并用Future可以来接收多线程的执行结果达到目的。
常用并发工具类(JDK提供的并发同步器)
CountDownLatch:是常用的并发工具类,CountDown直译倒计数,也就是计数器倒算,Latch(抓住,阻塞的意思),请看相关APICyclicBarrier : 主要的方法就是一个:await()。await() 方法没被调用一次,计数便会减少1,并阻塞住当前线程。当计数减至0时,阻塞解除,所有在此 CyclicBarrier 上面阻塞的线程开始运行。
Semaphore:Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
Exchanger:Exchanger,V就是可以交换的对象类型。可以在对中对元素进行配对和交换的线程的同步点。每个线程将条目上的某个方法呈现给 exchange 方法,与伙伴线程进行匹配,并且在返回时接收其伙伴的对象.
无锁化编程
无锁化编程:即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。
常见方法:(阿里的原题,当时一脸懵逼,查了一下勉强了解一点点吧。)
1.针对计数器,可以使用原子加
2.只有一个生产者和一个消费者,那么就可以做到免锁访问环形缓冲区(Ring Buffer)
3.RCU(Read-Copy-Update),新旧副本切换机制,对于旧副本可以采用延迟释放的做法
4.CAS(Compare-and-Swap),如无锁栈,无锁队列等待
死锁
死锁:多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对 象的锁”时,就可能会发生“死锁”的问题。(你等我,我等你,谁也不找谁,最后我们就一直僵持着,无限循环。我(线程1)手里有个苹果(资源1),但我必须要得到一个橙子(资源2)才能放手(释放资源1)苹果,你(线程2)手里有橙子(资源2)但你必须得到苹果(资源1)才能放手橙子(释放资源2),所以谁也不放手(不释放),无法进行下一步,导致死锁)。
产生死锁的必要条件:
互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。
预防死锁
破坏四个必要条件即可。
资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
最常见的有银行家算法,有兴趣的朋友可以查一下。
生产者消费者模式
一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消 费者之间相互依赖,互为条件。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。 生产者和消费者在同一时间段内共用同一个存储空间,如下图所示,生产者向空间里存放数据,而消费者取用数据,如果不加以协调可能会出现以下情况:
存储空间已满,而生产者占用着它,消费者等着生产者让出空间从而去除产品,生产者等着消费者消费产品,从而向空间中添加产品。互相等待,从而发生死锁。
解决方式:管程法和信号灯法。
管程法:
package com.lidadaibiao;
/**
* 协作模型:生产者消费者模式之一:管程法
* @author 李大代表
*
*/
public class GuanCFaTest {
public static void main(String[] args) {
Bufferzone bufferzone = new Bufferzone();
Producer producer = new Producer(bufferzone);
Consumer consumer = new Consumer(bufferzone);
new Thread(consumer).start();
new Thread(producer).start();
}
}
//多线程生产者
class Producer implements Runnable{
Bufferzone bufferzone;
public Producer(Bufferzone bufferzone) {
this.bufferzone = bufferzone;
}
@Override
public void run() {
//开始生产饼干
for(int i = 0;i<100;i++){
System.out.println("生产--->"+i);
bufferzone.push(new Biscuits(i));
}
}
}
//多线程消费者
class Consumer implements Runnable{
Bufferzone bufferzone;
public Consumer(Bufferzone bufferzone) {
this.bufferzone = bufferzone;
}
@Override
public void run() {
//开始消费饼干
for(int i = 0;i<1000;i++){
System.out.println("消费---》"+bufferzone.pop().Id);
}
}
}
//缓冲区
class Bufferzone{
Biscuits[] biscuits= new Biscuits[10];
int count = 0;//计数器
//放入
public synchronized void push(Biscuits b){
if(count==biscuits.length){
//如果生产的达到缓冲区大小就等待,等待缓冲区有空间再生产
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
biscuits[count] = b;
count++;
//生产之后唤醒消费者
this.notifyAll();
}
//取出
public synchronized Biscuits pop(){
if(count==0){
//如果缓冲区没有资源,则消费者就等待 生产资源
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
count--;
Biscuits biscuits2 = biscuits[count];
this.notifyAll(); //拿走资源 缓冲区就有了多余的空闲空间 唤醒生产者进行生产
return biscuits2;
}
}
//饼干biscuits
class Biscuits{
int Id;
public Biscuits(int Id) {
this.Id = Id;
}
}
信号灯法:
package com.lidadaibiao;
/**
* 消费者与生产者模式:信号灯法
* @author ASUS
*
*/
public class XinHtTest {
public static void main(String[] args) {
Pot p = new Pot();
new Father1(p).start();
new Me(p).start();
}
}
//锅
class Pot{
String food;
/**
* T 表示 ME等待
* F 表示Father等待
*/
boolean flag = true;
//做饭
public synchronized void doFood(String food){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//做饭
System.out.println("做饭了:"+food);
this.food = food;
//唤醒等待吃饭的我
this.notifyAll();
//切换信号灯
this.flag = !this.flag;
}
//吃饭
public synchronized void eatFood(){
if(flag){
//根据信号灯判断等待或吃饭
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//吃饭
System.out.println("吃饭了;"+food);
//吃完了 唤醒做饭
this.notifyAll();
//切换信号灯
this.flag = !this.flag;
}
}
//父
class Father1 extends Thread{
Pot p;//整个锅
public Father1(Pot p) {
this.p = p;
}
@Override
public void run() {
for(int i = 0;i<30;i++){
if(i%2==0){
this.p.doFood("酸菜鱼");;
}
else if(i%3==0){
this.p.doFood("清汤鱼");
}else{
this.p.doFood("麻辣锅");
}
}
}
}
//我
class Me extends Thread{
Pot p;
public Me(Pot p) {
this.p = p;
}
@Override
public void run() {
for(int i = 0;i<30;i++){
this.p.eatFood();
}
}
}
JAVA并发世界太深太广了,太难了,不过无论如何,我们还是要不断探索哈哈哈哈。
JAVA虐我千百遍,我待JAVA如初恋。
推荐阅读