
并发是指可以同时进行多个任务。
他跟static没有关系,是跟线程有关,java里并发安全我个人认为主要是两点:
1、数据资源竞争,也就是多个线程访问同一个资源,会造成安全问题;
2、原子性 *** 作,也就是说我有一系列 *** 作需要完成,但中间不得有其它线程影响到我的 *** 作。
数据资源竞争:java里有2种变量,成员变量,局部变量
局部变量不会发生安全问题,因为局部变量是在方法里,每次这个方法执行都是一个独立的数据,不会被其他线程访问
成员变量则可能会影响并发安全,比如:非静态成员变量是每个对象自身所有,如果这个对象的某个方法访问了自身的成员变量,当这个对象被不同的线程多次调用这个方法时,你说是不是会有问题?静态成员变量则更加如此了,任何该类的对象都可以调用。
原子性 *** 作:原子性说简单点就是一系列 *** 作或过程必须是一个整体,否则就会造成意外。比如说:多个人在商店买同一个东西,从付钱、拿货、结算必须是一个整体,如果某个人已经把这个东西买走了,另一个人却处于付账阶段,那么最终肯定会出问题,但如果这三个 *** 作是一个整体就不会有问题,只有一个人完全执行完了这3个,另一个人才能执行,二不允许同时进行。
当然我说的不一定准确,这个并发是很复杂的,你可以先了解个大概
java里解决并发安全是会牺牲性能的,但你要知道,1、并不是所有的并发安全都需要解决,有些在实际需求下是允许的,2、并发安全问题,不一定要靠并发安全去解决,也有可能是设计的缺陷。3、并发访问同一个资源不一定会出问题,比如:只有一个线程在写 *** 作或者全部是读 *** 作。
你写的太多了! 我真的看不下去了! 哈哈
你是不是要保证高并发唯一?然后有序递增? 如果要是这样的话 “我一般的解决方案是只能JAVA 程序写 然后JAVA 同步锁” 这样就可以了! 但是只限于单台server 非集群
还有方法就是 写一个单号的池! 效率更高!比加锁要好!
public class StartDi implements Runnable{
Sound s;
public StartDi(Sound s){
thiss=s;
}
public void run(){
sding();
}
}
public class StartDo implements Runnable{
Sound s;
public StartDo(Sound s){
thiss=s;
}
public void run(){
sdong();
}
}
这两个子类的run方法都是只执行一次而已啊。
你可以改为
public class StartDi implements Runnable{
Sound s;
public StartDi(Sound s){
thiss=s;
}
public void run(){
while(true){
sding();
}
}
}
public class StartDo implements Runnable{
Sound s;
public StartDo(Sound s){
thiss=s;
}
public void run(){
while(true){
sdong();
}
}
}
public static void main(String[] args) {
for(Thread t:getThreads()){
tstart();
}
}
public static Thread[] getThreads(){
Thread[] thread = new Thread[10];
for(int i=0;i<10;i++){
final Integer num = new Integer(i);
thread[i] = new Thread(new Runnable(){
public void run() {
int j=5;
while(j-->0){
Systemoutprintln("this is thread"+num);
}
}
});
}
return thread;
}
内存屏障 又称内存栅栏 是一组处理器指令 用于实现对内存 *** 作的顺序限制 本文假定读者已经充分掌握了相关概念和Java内存模型 不讨论并发互斥 并行机制和原子性 内存屏障用来实现并发编程中称为可见性(visibility)的同样重要的作用
内存屏障为何重要?
对主存的一次访问一般花费硬件的数百次时钟周期 处理器通过缓存(caching)能够从数量级上降低内存延迟的成本这些缓存为了性能重新排列待定内存 *** 作的顺序 也就是说 程序的读写 *** 作不一定会按照它要求处理器的顺序执行 当数据是不可变的 同时/或者数据限制在线程范围内 这些优化是无害的
如果把这些优化与对称多处理(symmetric multi processing)和共享可变状态(shared mutable state)结合 那么就是一场噩梦 当基于共享可变状态的内存 *** 作被重新排序时 程序可能行为不定 一个线程写入的数据可能被其他线程可见 原因是数据 写入的顺序不一致 适当的放置内存屏障通过强制处理器顺序执行待定的内存 *** 作来避免这个问题
内存屏障的协调作用
内存屏障不直接由JVM暴露 相反它们被JVM插入到指令序列中以维持语言层并发原语的语义 我们研究几个简单Java程序的源代码和汇编指令 首先快速看一下Dekker算法中的内存屏障 该算法利用volatile变量协调两个线程之间的共享资源访问
请不要关注该算法的出色细节 哪些部分是相关的?每个线程通过发信号试图进入代码第一行的关键区域 如果线程在第三行意识到冲突(两个线程都要访问) 通 过turn变量的 *** 作来解决 在任何时刻只有一个线程可以访问关键区域
// code run by first thread // code run by second thread
intentFirst = true; intentSecond = true;
while (intentSecond) while (intentFirst) // volatile read
if (turn != ) { if (turn != ) { // volatile read
intentFirst = false; intentSecond = false;
while (turn != ) {} while (turn != ) {}
intentFirst = true; intentSecond = true;
} }
criticalSection(); criticalSection();
turn = ; turn = ; // volatile write
intentFirst = false; intentSecond = false; // volatile write
硬件优化可以在没有内存屏障的情况下打乱这段代码 即使编译器按照程序员的想法顺序列出所有的内存 *** 作 考虑第三 四行的两次顺序volatile读 *** 作 每一个线程检查其他线程是否发信号想进入关键区域 然后检查轮到谁 *** 作了 考虑第 行的两次顺序写 *** 作 每一个线程把访问权释放给其他线程 然后撤销自己访问关键区域的意图 读线程应该从不期望在其他线程撤销访问意愿后观察到其他线程对turn变量的写 *** 作 这是个灾难
但是如果这些变量没有 volatile修饰符 这的确会发生!例如 没有volatile修饰符 第二个线程在第一个线程对turn执行写 *** 作(倒数第二行)之前可能会观察到 第一个线程对intentFirst(倒数第一行)的写 *** 作 关键词volatile避免了这种情况 因为它在对turn变量的写 *** 作和对 intentFirst变量的写 *** 作之间创建了一个先后关系 编译器无法重新排序这些写 *** 作 如果必要 它会利用一个内存屏障禁止处理器重排序 让我们来 看看一些实现细节
PrintAssembly HotSpot选项是JVM的一个诊断标志 允许我们获取JIT编译器生成的汇编指令 这需要最新的OpenJDK版本或者新HotSpot update 或者更高版本 通过需要一个反编译插件 Kenai项目提供了用于Solaris Linux和BSD的插件二进制文件 hsdis是另 一款可以在Windows通过源码构建的插件
两次顺序读 *** 作的第一次(第三行)的汇编指令如下 指令流基于Itanium 多处理硬件 JDK update 本文的所有指令流都在左手边以行号标记 相关的读 *** 作 写 *** 作和内存屏障指令都以粗体标记 建议读者不要沉迷于每一行指令
x de c: adds r = r ;; ;
x de a : ld acq r =[r ];; ; b a a
x de a : nop m x ; c
x de ac: sxt r r =r ;; ;
x de b : cmp eq p p = r ; c
x de b : nop i x ;
x de bc: nd dpnt many x de ;
简短的指令流其实内容丰富 第一次volatile位于第二行 Java内存模型确保了JVM会在第二次读 *** 作之前将第一次读 *** 作交给处理器 也就是按照 程序的顺序 但是这单单一行指令是不够的 因为处理器仍然可以自由乱序执行这些 *** 作 为了支持Java内存模型的一致性 JVM在第一次读 *** 作上添加了注解ld acq 也就是 载入获取 (load acquire) 通过使用ld acq 编译器确保第二行的读 *** 作在接下来的读 *** 作之前完成 问题就解决了
请注意这影响了读 *** 作 而不是写 内存屏障强制读或写 *** 作顺序限制不是单向的 强制读和写 *** 作顺序限制的内存屏障是双向的 类似于双向开的栅栏 使用ld acq就是单向内存屏障的例子
一致性具有两面性 如果一个读线程在两次读 *** 作之间插入了内存屏障而另外一个线程没有在两次写 *** 作之间添加内存屏障又有什么用呢?线程为了协调 必须同时 遵守这个协议 就像网络中的节点或者团队中的成员 如果某个线程破坏了这个约定 那么其他所有线程的努力都白费 Dekker算法的最后两行代码的汇编指令应该插入一个内存屏障 两次volatile写之间
$ java XX:+UnlockDiagnosticVMOptions XX:PrintAssemblyOptions=hsdis print bytes
XX:CompileCommand=print WriterReader write WriterReader
x de c : adds r = r ;; ; b
x de c : st rel [r ]=r ;
x de cc: adds r = r ;; ;
x de d : st rel [r ]=r ; a
x de d : mf ;
x de dc: nop i x ;; ;
x de e : mov r =r ;
x de e : mov ret b =r x de e
x de ec: mov i ar pfs=r ; aa
x de f : mov r =r ;
这里我们可以看到在第四行第二次写 *** 作被注解了一个显式内存屏障 通过使用st rel 即 存储释放 (store release) 编译器确保第一次写 *** 作在第二次写 *** 作之前完成 这就完成了两边的约定 因为第一次写 *** 作在第二次写 *** 作之前发生
st rel屏障是单向的 就像ld acq一样 但是在第五行编译器设置了一个双向内存屏障 mf指令 或者称为 内存栅栏 是Itanium 指令集中的完整栅栏 笔者认为是多余的
内存屏障是特定于硬件的
本文不想针对所有内存屏障做一综述 这将是一件不朽的功绩 但是 重要的是认识到这些指令在不同的硬件体系中迥异 下面的指令是连续写 *** 作在多处理 Intel Xeon硬件上编译的结果 本文后面的所有汇编指令除非特殊声明否则都出自于Intel Xeon
x f c: push %ebp ;
x f d: sub $ x %esp ; ec
x f : mov $ x c %edi ; bf c
x f : movb $ x x a f (%edi) ; c d a af
x f f: mfence ; faef
x f : mov $ x %ebp ; bd
x f : mov $ x d %edx ; ba d
x f c: mov l x a f (%edx) %ebx ; fbe a da af
x f : test %ebx %ebx ; db
x f : jne x f ;
x f : movl $ x x a f (%ebp) ; c d a af
x f : movb $ x x a f (%edi) ; c d a af
x f : mfence ; faef
x f b: add $ x %esp ; c
x f e: pop %ebp ; d
我们可以看到x Xeon在第 行执行两次volatile写 *** 作 第二次写 *** 作后面紧跟着mfence *** 作 显式的双向内存屏障 下面的连续写 *** 作基于SPARC
xfb ecc : ldub [ %l + x ] %l ; e c
xfb ecc : cmp %l ; a e
xfb ecc c: bne pn %icc xfb eccb ;
xfb ecc : nop ;
xfb ecc : st %l [ %l + x ] ; e
xfb ecc : clrb [ %l + x ] ; c c
xfb ecc c: membar #StoreLoad ; e
xfb ecca : sethi %hi( xff fc ) %l ; fcff
xfb ecca : ld [ %l ] %g ; c
xfb ecca : ret ; c e
xfb eccac: restore ; e
我们看到在第五 六行存在两次volatile写 *** 作 第二次写 *** 作后面是一个membar指令 显式的双向内存屏障 x 和SPARC的指令流与Itanium的指令流存在一个重要区别 JVM在x 和SPARC上通过内存屏障跟踪连续写 *** 作 但是在两次写 *** 作之间没有放置内存屏障
另一方面 Itanium的指令流在两次写 *** 作之间存在内存屏障 为何JVM在不同的硬件架构之间表现不一?因为硬件架构都有自己的内 存模型 每一个内存模型有一套一致性保障 某些内存模型 如x 和SPARC等 拥有强大的一致性保障 另一些内存模型 如Itanium PowerPC和Alpha 是一种弱保障
例如 x 和SPARC不会重新排序连续写 *** 作 也就没有必要放置内存屏障 Itanium PowerPC和Alpha将重新排序连续写 *** 作 因此JVM必须在两者之间放置内存屏障 JVM使用内存屏障减少Java内存模型和硬件内存模型之间的距离
隐式内存屏障
显式屏障指令不是序列化内存 *** 作的唯一方式 让我们再看一看Counter类这个例子
class Counter{
static int counter = ;
public static void main(String[] _){
for(int i = ; i < ; i++)
inc();
}
static synchronized void inc(){ counter += ; }
}
Counter类执行了一个典型的读 修改 写的 *** 作 静态counter字段不是volatile的 因为所有三个 *** 作必须要原子可见的 因此 inc 方法是synchronized修饰的 我们可以采用下面的命令编译Counter类并查看生成的汇编指令 Java内存模型确保了synchronized区域的退出和volatile内存 *** 作都是相同的可见性 因此我们应该预料到会有另一个内存屏障
$ java XX:+UnlockDiagnosticVMOptions XX:PrintAssemblyOptions=hsdis print bytes
XX: UseBiasedLocking XX:CompileCommand=print Counter inc Counter
x d eda : push %ebp ;
x d eda : mov %esp %ebp ; bec
x d edaa: sub $ x %esp ; ec
x d edad: mov $ x ba %esi ; be ba
x d edb : lea x (%esp) %edi ; d c
x d edb : mov %esi x (%edi) ;
x d edb : mov (%esi) %eax ; b
x d edbb: or $ x %eax ; c
x d edbe: mov %eax (%edi) ;
x d edc : lock cmpxchg %edi (%esi) ; f fb e
x d edc : je x d edda ; f
x d edca: sub %esp %eax ; bc
x d edcc: and $ xfffff %eax ; e f ffff
x d edd : mov %eax (%edi) ;
x d edd : jne x d ee ; f
x d edda: mov $ x ba b %eax ; b b ba
x d eddf: mov x (%eax) %esi ; bb
x d ede : inc %esi ;
x d ede : mov %esi x (%eax) ; b
x d edec: lea x (%esp) %eax ; d
x d edf : mov (%eax) %esi ; b
x d edf : test %esi %esi ; f
x d edf : je x d ee ; f d
x d edfa: mov x (%eax) %edi ; b
x d edfd: lock cmpxchg %esi (%edi) ; f fb
x d ee : jne x d ee f ; f
x d ee : mov %ebp %esp ; be
x d ee : pop %ebp ; d
不出意外 synchronized生成的指令数量比volatile多 第 行做了一次增 *** 作 但是JVM没有显式插入内存屏障 相反 JVM通过在 第 行和第 行cmpxchg的lock前缀一石二鸟 cmpxchg的语义超越了本文的范畴
lock cmpxchg不仅原子性执行写 *** 作 也会刷新等待的读写 *** 作 写 *** 作现在将在所有后续内存 *** 作之前完成 如果我们通过ncurrent atomic AtomicInteger 重构和运行Counter 将看到同样的手段
import ncurrent atomic AtomicInteger;
class Counter{
static AtomicInteger counter = new AtomicInteger( );
public static void main(String[] args){
for(int i = ; i < ; i++)
counter incrementAndGet();
}
}
$ java XX:+UnlockDiagnosticVMOptions XX:PrintAssemblyOptions=hsdis print bytes
XX:CompileCommand=print AtomicInteger incrementAndGet Counter
x f : push %ebp ;
x f : mov %esp %ebp ; bec
x fa: sub $ x %esp ; ec
x fd: jmp x a ; e
x : xchg %ax %ax ;
x : test %eax xb e ; e b
x a: mov x (%ecx) %eax ; b
x d: mov %eax %esi ; bf
x f: inc %esi ;
x : mov $ x a f d %edi ; bfd f a
x : mov x (%edi) %edi ; bbf
x b: mov %ecx %edi ; bf
x d: add $ x %edi ; c
x : lock cmpxchg %esi (%edi) ; f fb
x : mov $ x %eax ; b
x : je x ; f
x f: mov $ x %eax ; b
x : cmp $ x %eax ; f
x : je x ; cb
x : mov %esi %eax ; bc
x b: mov %ebp %esp ; be
x d: pop %ebp ; d
我们又一次在第 行看到了带有lock前缀的写 *** 作 这确保了变量的新值(写 *** 作)会在其他所有后续内存 *** 作之前完成
内存屏障能够避免
JVM非常擅于消除不必要的内存屏障 通常JVM很幸运 因为硬件内存模型的一致性保障强于或者等于Java内存模型 在这种情况下 JVM只是简单地插 入一个no op语句 而不是真实的内存屏障
例如 x 和SPARC内存模型的一致性保障足够强壮以消除读volatile变量时所需的内存屏障 还记得在 Itanium上两次读 *** 作之间的显式单向内存屏障吗?x 上的Dekker算法中连续volatile读 *** 作的汇编指令之间没有任何内存屏障 x 平台上共享内存的连续读 *** 作
x f : mov $ x %ebp ; bd
x f : mov $ x d %edx ; ba d
x f c: mov l x a f (%edx) %ebx ; fbe a da af
x f : test %ebx %ebx ; db
x f : jne x f ;
x f : movl $ x x a f (%ebp) ; c d a af
x f : movb $ x x a f (%edi) ; c d a af
x f : mfence ; faef
x f b: add $ x %esp ; c
x f e: pop %ebp ; d
x f f: test %eax xb ec ; c eb
x f : ret ; c
x f : nopw x (%eax %eax ) ; f f
x f : mov x a f (%ebp) %ebx ; b d d a af
x f : test %edi xb ec ; d c eb
第三行和第十四行存在volatile读 *** 作 而且都没有伴随内存屏障 也就是说 x 和SPARC上的volatile读 *** 作的性能下降对于代码的优 化影响很小 指令本身和常规读 *** 作一样
单向内存屏障本质上比双向屏障性能要好一些 JVM在确保单向屏障即可的情况下会避免使用双向屏障 本文的第一个例子展示了这点 Itanium平台上的 连续两次读 *** 作 入单向内存屏障 如果读 *** 作插入显式双向内存屏障 程序仍然正确 但是延迟比较长
动态编译
静态编译器在构建阶段决定的一切事情 在动态编译器那里都可以在运行时决定 甚至更多 更多信息意味着存在更多机会可以优化 例如 让我们看看JVM在单 处理器运行时如何对待内存屏障 以下指令流来自于通过Dekker算法实现两次连续volatile写 *** 作的运行时编译 程序运行于 x 硬件上的单处理器模式中的VMWare工作站镜像
x b c: push %ebp ;
x b d: sub $ x %esp ; ec
x b : mov $ x c %edi ; bf c
x b : movb $ x x f (%edi) ; c d aaf
x b f: mov $ x %ebp ; bd
x b : mov $ x d %edx ; ba d
x b : mov l x f (%edx) %ebx ; fbe a d aaf
x b : test %ebx %ebx ; db
x b : jne x b ; c
x b : movl $ x x f (%ebp) ; c d aaf
x b : add $ x %esp ; c
x b : pop %ebp ; d
在单处理器系统上 JVM为所有内存屏障插入了一个no op指令 因为内存 *** 作已经序列化了 每一个写 *** 作(第 行)后面都跟着一个屏障 JVM针对原子条件式做了类似的优化 下面的指令流来自于同一 个VMWare镜像的AtomicInteger incrementAndGet动态编译结果
x f : push %ebp ;
x f : mov %esp %ebp ; bec
x fa: sub $ x %esp ; ec
x fd: jmp x a ; e
x : xchg %ax %ax ;
x : test %eax xb b ; bb
x a: mov x (%ecx) %eax ; b
x d: mov %eax %esi ; bf
x f: inc %esi ;
x : mov $ x a f d %edi ; bfd f a
x : mov x (%edi) %edi ; bbf
x b: mov %ecx %edi ; bf
x d: add $ x %edi ; c
x : cmpxchg %esi (%edi) ; fb
x : mov $ x %eax ; b
x : je x ; f
x e: mov $ x %eax ; b
x : cmp $ x %eax ; f
x : je x ; cc
x : mov %esi %eax ; bc
x a: mov %ebp %esp ; be
x c: pop %ebp ; d
注意第 行的cmpxchg指令 之前我们看到编译器通过lock前缀把该指令提供给处理器 由于缺少SMP JVM决定避免这种成本 与静态编译有些不同
结束语
lishixinzhi/Article/program/Java/hx/201311/25723
run(参数)不属于runnable接口,执行时不能识别为线成运行。
如何实现并发,关键看你这个“参数”用来干什么。也就是为什么一定要带这个参数。线程取数据有很多方法,不需要通过参数传递。例如:
可以在Thread中声明一个变量,int a。
启动两个线程,循环读a如果没值就wait一下,如果有值跳出循环往下执行。
需要传参数的时候,为a赋值
Java并发框架javautilconcurrent是JDK5中引入到标准库中的(采用的是Doug
Lea的并发库)。该包下的类可以分为这么块:
Executors
1)接口:
Executor(例子涉及):用来执行提交的Runnable任务的对象。是一个简单的标准化接口,用来定义包括线程池、异步IO、轻量级任务框架等等。任务可以由一个新创建的线程、一个已有任务执行线程、或是线程直接调用execute()来执行,可以串行也可并行执行,取决于使用的是哪个Executor具体类。
ExecutorService(例子涉及):Executor的子接口,提供了一个更加具体的异步任务执行框架:提供了管理结束的方法,以及能够产生Future以跟踪异步任务进程的方法。一个ExcutorService管理着任务队列和任务调度。
ScheduledExecutorService(例子涉及):ExecutorService的子接口,增加了对延迟和定期任务执行的支持。
Callable(例子涉及):一个返回结果或抛出异常的任务,实现类需要实现其中一个没有参数的叫做call的方法。Callabe类似于Runnable,但是Runnable不返回结果且不能抛出checked
exception。ExecutorService提供了安排Callable异步执行的方法。
Future(例子涉及):代表一个异步计算的结果(由于是并发执行,结果可以在一段时间后才计算完成,其名字可能也就是代表这个意思吧),提供了可判断执行是否完成以及取消执行的方法。
2)实现:
ThreadPoolExecutor和ScheduledThreadPoolExecutor:可配置线程池(后者具备延迟或定期调度功能)。
Executors(例子涉及):提供Executor、ExecutorService、ScheduledExecutorService、ThreadFactory以及Callable的工厂方法及工具方法。
FutureTask:对Future的实现
ExecutorCompletionService(例子涉及):帮助协调若干(成组)异步任务的处理。
Queues
非阻塞队列:ConcurrentLinkedQueue类提供了一个高效可伸缩线程安全非阻塞FIFO队列。
阻塞队列:BlockingQueue接口,有五个实现类:LinkedBlockingQueue(例子涉及)、ArrayBlockingQueue、SynchronousQueue、PriorityBlockingQueue和DelayQueue。他们对应了不同的应用环境:生产者/消费者、消息发送、并发任务、以及相关并发设计。
Timing
TimeUnit类(例子涉及):提供了多种时间粒度(包括纳秒)用以表述和控制基于超时的 *** 作。
Synchronizers 提供特定用途同步语境
Semaphore(例子涉及):计数信号量,这是一种经典的并发工具。
CountDownLatch(例子涉及):简单的倒计数同步工具,可以让一个或多个线程等待直到另外一些线程中的一组 *** 作处理完成。
CyclicBarrier(例子涉及):可重置的多路同步工具,可重复使用(CountDownLatch是不能重复使用的)。
Exchanger:允许两个线程在汇合点交换对象,在一些pipeline设计中非常有用。
Concurrent Collections
除队列外,该包还提供了一些为多线程上下文设计的集合实现:ConcurrentHashMap、CopyOnWriteArrayList及CopyOnWriteArraySet。
注意:"Concurrent"前缀的类有别于"synchronized"前缀的类。“concurrent”集合是线程安全的,不需要由单排斥锁控制的(无锁的)。以ConcurrentHashMap为例,允许任何数量的并发读及可调数量的并发写。“Synchronized”类则一般通过一个单锁来防止对集合的所有访问,开销大且伸缩性差。
以上就是关于java中什么是并发性,他和static有什么关系全部的内容,包括:java中什么是并发性,他和static有什么关系、存储过程执行for update与java执行for update并发问题、java一个多线程的小程序,为啥他知执行了一次就不执行了,不是会一直并发式执行下去的吗等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)