标签:blocks err 重入 基本类型 park 静态 sys final ons
并发在单核和多核 CPU 上都存在, 对于单核 CPU,通过轮训时间片的方式实现并发.
利用Thread对象, 有两种方式来创建并发程序:
executor.有两种方式创建线程:
Runnable接口的对象.Thread.两种方法的优缺点?
Runnable 总体来说更好一点
Runnable 接口的方式更加灵活, 因为可以继续子类化某个类Runnable 接口的方式可以适配 concurrent包中的高级线程管理 API线程有如下状态:
NEW: 线程已经创建, 但还没有调用 start() 开始执行.RUNNABLE: 线程已经在 JVM 中开始运行, 但有可能在等待系统资源运行.BLOCKED: 线程在等待一个 monitor lock 以进入一个 synchronized 块. 也有可能是这个线程刚执行完 wait(), 其他线程又在获取 wait() 对象的 monitor lock.WAITING: 线程进入等待状态, 以下方法会使得线程进入 wait() 状态:
Object.wait()join()LockSupport.parkTIMED_WAITING: 线程进入有时间限制的等待, 一下方法会使得线程进入此状态:
Thread.SleepObject.wait(long)Thread.join(long)LockSupport.parkNanosLockSupport.parkUntilTERMINATED: 线程执行结束.
注意:
当线程 A 调用某个对象的 Synchronized 方法的时候, 线程就获得了这个对象的 intrinsic lock, 线程的状态是 RUNNABLE. 其他线程假如要获取这把锁, 就会进入 BLOCKED 状态.
当线程 A 调用wait方法, 那线程A 会释放这个对象的锁(但是扔回持有其他对象的锁, 假如有的话), 然后线程转入 WAITING 状态. (若之前很多对象 pending 在这个 lock 上, 那么, 进入 wait 后会唤醒其他某个线程么?)
当其他某个线程 B 在同一个对象(也就是线程 A wait 释放的同一把锁)调用notify 或者 notifyAll时, 线程A 状态由 WAITING 转化为 BLOCKED. 此时, 线程A 并不会自动获取到锁或者状态变成 RUNNABLE, 实际上, 线程 A 也要和其他被阻塞的线程一样竞争这把锁.
WAITING 和 BLOCKED 状态都会阻止线程运行, 但是区别却很大.
WAITING 状态必须被其他线程调用notify从而显式的转化为 BLOCKED 状态. WAITING 状态从来不会直接转化为 RUNNABLE.
当一个 RUNNABLE 线程释放了锁(正常结束或者waiting), 某个被阻塞的线程会自动被唤醒.
notify 和 notifyAll 区别?
notify 唤醒被同一把锁 wait的第一个线程 notifyAll 唤醒同一把锁 wait 的所有线程, 但是优先级最高的先执行
Thread.sleepThread.sleep 导致当前线程暂时挂起一段时间, 其他线程可以有机会获取到 CPU 时间.
两个 API:
Thread.sleep(long ms)
Thread.sleep(long ms, long ns)
时间并不精确, 由于底层 OS 实现的限制.
Sleep 可以被打断, 当线程 A 在休眠, 而另外一个线程 B 调用 A.interrupt()时, 线程 A 就会抛出 InterruptedException
interrupt 会停止当前线程的正在进行的任务并且取做别的事情. 至于一个 thread 应该如何响应一个中断则是由程序员决定的.
根据当前任务的长短, 做不同的处理:
当一个线程正在频繁的调用一个会抛出InterruptedException 的方法时, 它可以通过 try...catch 捕获, 并在catch 中做处理. 有很多方法会抛出InterruptedException, 比如 Thread.sleep, sleep的中断行为被设计成为:终止当前操作并抛出异常.
for (int i = 0; i < ary.length; i++) {
try {
Thread.sleep(4000);
} caatch (InterruptedException e) {
return;
}
System.out.println(importantInfo[i]);
}
当一个线程在执行一个长时间任务, 并且这个任务并没有抛出 InterruptedException 的时候. 那么就需要不停地去检测当前线程有没有被中断:
for (int i = 0; i < inputs.length; i++) {
heavyCrunch(inputs[i]);
if (Thread.interrupted()) {
// or return;
throw new InterruptedException();
}
}
中断机制是由内部标识中断状态的一个标志控制的:
Thread.interrupt时, 这个标志会被设置. Thread.interrupted时, 标志会被清理.Thread.isInterrupted 查询中断状态时, 标志不变.InterruptedException 而退出, 那么中断标志会被清理(但是有可能立即被设置).join 方法让一个线程可以等待另外一个线程执行结束后再往下执行. 和 sleep一样, join 响应中断的方式也是退出并且抛出InterruptedException.
线程间通讯主要靠开放字段的访问或者字段引用的对象. 会带来两个问题:
防止这两种错误的机制就是 线程同步. 然而, 线程同步会带来线程间竞争(thread contention). 饥饿和活锁都是 线程竞争 的表现.
指的是, 一个语句可能被虚拟机拆分成很多步执行, 然而当两个线程交叉执行时, 线程 A 的执行导致线程 B 的运行结果是不准确的. 比如, 线程 A 执行 c++, 线程 B 执行 c--, 开始两个线程读到 c 的值是0, 假如线程 B 在 A 之后执行完, 那么结果是 -1 而不是 0.
Memory Consistency Error 指的是线程对同一份数据却有不一致的值. 防止这种错误的关键是保证 happens-before 关系. 比如:
int count = 0;
线程 A 执行:
count++;
线程 B 打印 count的值:
System.out.println(count);
那么线程 B 打印出的结果可能是 0, 因为线程 A 的自增操作并没有和 B 的打印语句建立一个 happens-before 关系_.
happens-before 关系 保证的是, 某个语句所导致的内存写入动作会对另外的语句可见.
如何建立 happens-before 关系?
synchronized 代码块或者方法), 总是发生在获取同一个 monitor lock(进入 synchronized 代码块或者方法)之前. 由于 happens-before 关系的传播性, 释放锁的方法或者代码块, 总是比获取锁或者代码块之前执行. volatetile 变量总是在读取前执行. Thread.start 建立两种 happens-before 关系:
Thread.start前面的语句会在 Thread.start 之前执行Thread.start前面的语句会在新线程语句之前执行Thread.join返回, 线程 A 中的所有语句会在线程 B Thread.join后面的语句之前执行Java 语言级别提供了两种方法:
注意:
synchronized 修饰, 否则会发生编译错误.不要提早的泄露字段的引用. 比如在构造函数添加下列的语句:
instances.add(this) //就会导致构造函数并未完成, 却将它通过 instances 暴露出去了.
同步的机制是建立在 intrinsic lock 又称 monitor lock 之上的. Intrinsic Lock 作用是
当线程调用一个 synchronized method 的时候, 线程会自动获得对象的 intrinsic lock. 并在下列情况释放:
当线程调用一个 static synchronized method 的时候, 线程会获取与对象关联的 Class 对象的锁. 所以静态同步方法的锁和实例锁是不同的.
写法:
public void addName(String name) {
synchronized(this) {
lastName = name;
nameCount++;
}
nameList.add(name);
}
注意: 在 synchronized method 或者 synchronzied statements 中要避免其他对象的同步代码(方法或代码块).
意思是: 不可打断的操作
Java 中的原子操作:
long 和 double 除外)volatile类型的变量(包括引用, long, double)原子操作不会被拆分, 所以不用担心线程干扰(Thread Interference)的问题, 但是却仍然要注意内存一致性(Memory Consistency)的问题.
使用volatile 可以避免内存一致性错误, 因为写入volatile 变量建立了一种 happen-before 的关系: 写入总比后续读先.
synchronized method 和 synchronized statements 会保证原子操作.
死锁描述了这样一种情况: 两个或者多个线程进入永远的阻塞, 互相等待.
避免方法: 上锁的顺序相同.
如何排查? 通过 JStack 可以查看:
jstack <pid>
Java stack information for the threads listed above:
===================================================
"Thread-1":
at basic.DeadlockBower$Friend.bowBack(DeadlockBower.java:32)
- waiting to lock <0x0000000795706590> (a basic.DeadlockBower$Friend)
at basic.DeadlockBower$Friend.bow(DeadlockBower.java:28)
- locked <0x00000007957065d8> (a basic.DeadlockBower$Friend)
at basic.DeadlockBower$2.run(DeadlockBower.java:49)
at java.lang.Thread.run(Thread.java:745)
"Thread-0":
at basic.DeadlockBower$Friend.bowBack(DeadlockBower.java:32)
- waiting to lock <0x00000007957065d8> (a basic.DeadlockBower$Friend)
at basic.DeadlockBower$Friend.bow(DeadlockBower.java:28)
- locked <0x0000000795706590> (a basic.DeadlockBower$Friend)
at basic.DeadlockBower$1.run(DeadlockBower.java:43)
at java.lang.Thread.run(Thread.java:745)
Found 1 deadlock.
饥饿描述了这样的情况: 线程无法长时间访问不到共享资源, 从而无法取得进展
描述的是: 两个线程互相根据对方的行为做出响应, 导致各自没有实质性的进展
线程之间经常协调他们的行为, 其中最常用的协调方法是 guarded block:
public void guardedJoy() {
// Simple loop guard. Wastes processor time. Don‘t do this!
while (!joy) { }
System.out.println("Joy has been achieved");
}
上面的代码通过不停 检测 joy 的状态来决定是否往下执行. 这样非常的耗费 CPU 时间.
更好的应该用 Object.wait() 来挂起当前线程.
public synchronized void guardedJoy() {
// This guard only loops once for each special event, which may not be
// the event we‘re waiting for.
while (!joy) {
try {
wait();
} catch (InterruptedException e) {}
}
System.out.println("Joy and efficiency have been achieved!");
}
值得注意的是, 确保 wait() 在一个循环中, 因为你不能保证:
InterruptedExcpetion 还是正常唤醒导致 wait() 结束joy 的值改变(尤其是在 notifyAll中)解决了, 解耦了生产者和消费者, 并且更合理的运用了 CPU 时间.
不可变对象是那些构造后状态无法被改变的对象. 由于它的不可变性, 他不会有 Thread interference 和 Memory inconsistent 等问题.
不可变对象的特征:
setter 方法.final + privatefinal如果实例变量中有引用类型, 别让他们被更改:
不要共享他们的引用, 包括
标签:blocks err 重入 基本类型 park 静态 sys final ons
原文地址:http://www.cnblogs.com/pragmatic/p/6143154.html