标签: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.park
TIMED_WAITING
: 线程进入有时间限制的等待, 一下方法会使得线程进入此状态:
Thread.Sleep
Object.wait(long)
Thread.join(long)
LockSupport.parkNanos
LockSupport.parkUntil
TERMINATED
: 线程执行结束.
注意:
当线程 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.sleep
Thread.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
+ private
final
如果实例变量中有引用类型, 别让他们被更改:
不要共享他们的引用, 包括
标签:blocks err 重入 基本类型 park 静态 sys final ons
原文地址:http://www.cnblogs.com/pragmatic/p/6143154.html