您的位置:首页 > 编程语言 > Java开发

聊聊并发处理和java线程

2017-12-20 12:49 246 查看

并发处理

并发处理的引入为了加速,是为了“压榨”计算机的运算能力,例如CPU运算能力比其他存储、通讯子系统的速度快太多。

Amdahl(阿姆达尔定律):

S=1/(1-a+a/n)

S:固定负载情况下描述并行处理效果的加速比
a:为并行计算部分所占比例;
n:为并行处理结点个数;

当a=0时(即只有串行,没有并行),最小加速比s=1。

当1-a=0时,(即没有串行,只有并行)最大加速比s=n,当n→∞时,极限加速比s→ 1/(1-a),这也就是加速比的上限。

例如,若串行代码占整个代码的25%,则并行处理的总体性能不可能超过4

如果要拿这个算程序处理用几个线程,还要考虑线程切换本身的消耗。

曲线参考

加速不仅仅靠并发

加速的最终目标是为了让计算机帮我们处理跟多的“任务”,而每个“任务”,不是仅靠“运算”就能完成,至少要和内存交换数据。

引入读写速度接近处理器的高速缓冲

对输入代码进行乱序(out-of-order execution)执行但是保障执行结果不变,充分利用运算单远()

上面两个策略也带来2个问题

缓存一致性(处理高速缓冲和共享内存的数据一致性)

指令重排序(执行顺序跟代码顺序不一定一致)

java内存模型

主要目标是定义程序各个变量的访问规则,虚拟机中将变量存储到内存和从内存中取出的底层细节(不包括线程私有的,局部变量、方法参数)。

线程 工作内存操作主内存
java线程1<---->工作内存1(sava/load)主内存(共享)
java线程2<---->工作内存2(sava/load)主内存(共享)
这里讲主内存和工作内存和java堆、栈、方法区不是同一层一次的内存划分。
一定要关联主内存堆(物理:内存),工作内存虚拟机栈中(物理:提前放到寄存器和高速缓存)

内存交互操作

定义了8种操作:

lock:作用于主内存变量,标记被一个线程独占,引发清空工作内存此变量值,用前重新load,assign。

unlock:作用于主内存变量,把锁定的变量释放出来,unlock前必须执行store,write写回主内存。

read:作用于主内存变量,主内存传输到线程工作内存,以便后面load操作。

load:作用于工作内存变量,把read得到的变量值,放入工作内存的变量副本中。

use:作用于工作内存变量,把变量值传给执行引擎。

assign:作用于工作内存变量,从执行引擎收到的值赋值给工作内存变量。

store:作用于工作内存变量,包工作内存的值传到主内存,以便随后的write操作。

write:作用于主内存变量,包store拿到的值放入到主内存变量中。



java虚拟只保障顺序的执行read和load(store和write),但是不保证连线执行。可能中间执行了其他指令。

但是对上面8种基本操作加了一些规则:

变量只能在主内存“诞生”

对变量“lock”,必须先清空此变量的工作内存值,在执行引擎执行的时候,重新load。

其他的就是,不能无缘无顾中间开始或结束,不多讲。

volatile变量访问规则

“可见性”:一个线程改变volatiel变量值,另外一个线程使用时可以立即可见,由于每次使用前先刷新,所以对执行引擎来说看起来是一致的。但是运算不一定是原子性的,所以运算在并发下不一定安全(真实是存在不一致的)。

“禁止指令重排序”:添加内存屏障,内存屏障之后对指令不能放到内存屏障之前执行。

volatile bool initailized=false;
//  initialize xxxx

initailized=true;//会添加内存屏障,保证判断变量时之前(initialize)代码全部执行完。

if(initailized){
//do something
}

volatile使用场景:

运算结果不依赖变量当前值(如:i++),或者保证只有一个线程改变变量值。

变量不需要与其他的状态变量共同参与不变约束。

long 和dubbo变量

“long/dubbo非原则性协定”:内存模型要求“lock...write"这8个操作原子性,但是对于没有被volatile修饰的64位的数据类型划分成2次操作,不保证原子性。(一般商用虚拟机还是保证原子性的)

先行发生原则(happens-before)

两项操作直接的偏序关系,A操作先于B操作,就代表A操作产生的影响,B操作可见。

program order rule

monitor lock rule

volatile varible rule

thread start rule

thread termination rule

thread interruption rule

finalize rule

transitivity

java线程

内核:操作系统的最基础部分。

内核线程(KLT:kernel-level thread):操作系统内核支持的线程,内核调度scheduler调度线程,将线程任务映射到处理器。

轻量级线程(LWP:light weight process):操作内核线程的高级接口。

用户线程(UT:user thread):线程都在用户态实现,内核不知道用户线程的存在。



实现方式:

使用内核线程实现:使用了内核线程优势,但是切换成本高。

使用用户线程实现:没有内核线程支持,受限制

使用混合实现:UT---LWP,多对多

java线程调度

cooperative(协作,线程自己控制时间) 和preemptive(抢占,系统分配执行时间) Thread-scheduling

线程状态

new

runnable

waiting

timed waiting

blocked

terminated

线程安全

一个对象被多线程同时使用,依然可以得到正确的结果,那么这个对象是安全的。

不可变:final、不可变类型:String、枚举类型、java.lang.Number的子类

绝对线程安全与相对线程安全:如:vector:ArrayIdnexOutOfBoundsException

线程兼容:可以正确的使用,打到线程安全

线程对立:怎么用都是有风险的,比如thread.suspend和resume

线程安全实现方法

相关概念梳理:

Blocking Synchronize:基于线程阻塞和唤醒实现。问题就是线程切换的成本问题。

Non-Blocking Synchronize:乐观并发策略,先进行操作,再根据检查是否有其他线程竞争共享数据,如没有成功,有失败。

互斥同步

保证同一个时间,只有一个线程访问,互斥是方法,同步是目的,同步实现上一些特性如下:

可重入:同步块,同一线程可一重复进入,防止自己锁自己的死锁。

等待可中断:正在等待锁的线程可以选择放弃等待。

乐观并发策略

依赖硬件指令的原子性实现,比如常见指令(熟悉redis的会觉得眼熟):

Test-and-Set

Fecth-And-Increment

Swap

Compare-and-swap

Load-Linked/Store-Conditional

当然也可以自己实现,比如循环重试,成功为止。

其他实现

避开共享数据和公用资源,自然线程安全。

线程本地存储,避开公共资源争用。

锁优化

JDK1.5到1.6一个重要优化是锁优化,synchronize和ReenTrantLock的性能差距就没有那么大了,主要看实现是的锁的监视对象力度粗细。

自旋锁:想让线程“等待”时,不放弃CPU处理,以自己“忙循环”,等下看下锁是不是释放了,对锁释放很快的场景,可以避开线程切换开销,循环策略优化引入自适应自旋锁,尝试一定次数后放弃,进入线程等待。

锁消除:代码上要求同步,但是不可能存在共享数据竞争,可以在即时编译是消除锁。比如局部变量,stringBuffer.append().

public synchronized StringBuffer append(StringBuffer sb) {
toStringCache = null;
super.append(sb);
return this;
}


锁粗化:连续对同一个对象加锁解锁(比如循环体内),可以把锁放到外面,通过方法内联分析(inlining)。

public int arrayCount() {
int result = 0;
for (int i = 0; i < intArr.length; i++) {
result += getEntry(i);
}
return result; }
public synchronized int getEntry(int entry){
return intArr[entry];
}

优化后:
public int arrayCount() { int result = 0;
synchronized {
for (int i = 0; i < intArr.length; i++) {
result += intArr[entry];
}
}
return result;
}


轻量级锁
“轻量”相对于操作系统互斥量来实现的锁而言,通过对象头部分存储锁标志(Mark Word),标识对象是不是被锁定,相当于在使用重量级锁之前,先对轻量级锁标志进行CAS操作解决一部分同步问题。
轻量级锁能提高性能的依据是经验告诉我们“绝大部分的锁,在整个生命周期內都不存在竞争”。

偏向锁 “偏向”锁偏向于第一个获得它的线程,基于轻量级锁实现,如果竞争对象标水,线程获得过锁,后面该线程在进入就同步块,CAS操作也省了。当另外一个线程来竞争时,偏向结束。

最后补一个概念

公平锁:是指竞争锁时锁分配的公平性,谁先来申请谁优先给谁,这种算“公平”,synchronized非公平,ReentrantLock默认也非公平。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息