并发
目录
第14章 并发
多线程与进程的区别 每个进程拥有一整套变量,而线程则共享数据
14.3 线程状态
- New(新创建)
- Runable(可运行)
- Blocked(被阻塞)
- Waiting(等待)
- Timed waiting(计时等待)
- Terminated(被终止)
要确定一个线程的当前状态,可调用getState方法
抢占式调度与协作式调度
14.3.x 创建线程
1.创建线程独立执行
implements Runnable - 覆盖run()方法
public class MyRunnable implements Runnable{public void run() {//逻辑代码}}
//调用
Runnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
14.5 同步
问题:两个或两个以上的线程需要共享对同一数据的存取.果两个线程存取相同的对象, 并且每一个线程都调用了一个修改该对象状态的方法-学会同步存取
方案:方法的执行过程中可能会被中断。如果能够确保线程在失去控制之前方法运行完成, 那么银行账户对象的状态永远不会出现讹误。
14.5.3 锁对象
//ReentrantLock 类
private Lock myLock = new ReentrantLock();// ReentrantLock implements the Lock interface
myLock.lockO; // a ReentrantLock object
try{}finally{myLock.unlockO;}
14.5.4 条件与对象
问题:避免选择没有足够资金的账户作为转出账户
private Condition sufficientFunds;
private Lock bankLock = new ReentrantLock();
public Bank(){sufficientFunds = bankLock.newConditionO;}
//如果 transfer 方法发现余额不足,它调用
sufficientFunds.await(); //当前线程现在被阻塞了,并放弃了锁。
//阻塞状态,直到另一个线程调用同一条件上的 signalAll 方法时为止
//当另一个线程转账时, 它应该调用
sufficientFunds,signalAll(); //解除该条件的等待集中的所有线程的阻塞状态。
- 可重入锁特性: 被一个锁保护的代码可以调用另一个使用相同的锁的方法-持有计数(hold count)就会累加。
14.5.5 synchronized关键字
从1.0版开始,Java中的每一个对象都有一个内部锁。如果一个方法用 synchronized关键字声明,那么对象的锁将保护整个方法。
public synchronized void method()
{
while (accounts[from] < amount)
wait();
...
notifyAll();
}
- void notify()
随机选择一个在该对象上调用wait方法的线程, 解除其阻塞状态。该方法只能在一个同步方法或同步块中用如果当前线程不是对象锁的持有者,该方法抛出一个IllegalMonitorStateException 异常。 - void wait( )
导致线程进人等待状态直到它被通知。该方法只能在一个同步方法中调用。如果当前线程不是对象锁的持有者,该方法拋出一个 IllegalMonitorStateException 异常 - void wait(long millis)
- void wait(long millis, int nanos)
导致线程进入等待状态直到它被通知或者经过指定的时间。这些方法只能在一个同步方法中调用。如果当前线程不是对象锁的持有者该方法拋出一个 IllegalMonitorStateException异常。
参数: millis 毫秒数
nanos 纳秒数,<1 000 000
14.5.6 同步阻塞
总结: 客户端锁定是非常脆弱的,通常不推荐使用。
原因: 一个线程完全可能在(要运行)方法中被剥夺运行权,于是,另一个线程可能在相同的存储位置存人不同的值,必须确保(要阻塞)类对自己的所有可修改方法都使用内部锁。
synchronized (obj){}
14.5.7 监视器概念
如果一个方法用 synchronized 关键字声明,那么,它表现的就像是一个监视器方法。通过调 wait/notifyAU/notify 来访问条件变量
14.5.8 Volatile域
总结: 仅仅为了读写一个或两个实例域就使用同步,显得开销过大了
Volatile 变量不能提供原子性。例如,方法,不能确保翻转域中的值。不能保证读取、 翻转和写入不被中断。
14.5.11 死锁
问题:锁和条件不能解决多线程中的所有问题
方案:Java 编程语言中没有任何东西可以避免或打破这种死锁现象。必须仔细设计程序, 以确保不会出现死锁。
14.5.14 读/写锁 ReentrantReadWriteLock类
方案:如果很多线程从一个数据结构读取数据而很少线程修改其中数据的话, 后者是十分有用的。
下面是使用读 / 写锁的必要步骤:
1. 构造一个 ReentrantReadWriteLock 对象:
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock():
2) 抽取读锁和写锁:
private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();
3 ) 对所有的获取方法加读锁:
public double getTotalBalanc()
{
readLock.lockO;
try {...}finally { readLock.unlock(); }
}
4 ) 对所有的修改方法加写锁:
public void transfer(...)
{
writeLock.lockO;
try {...}finally { writeLock.unlock(); }
}
14.5.15 为什么弃用 stop 和 suspend 方法
原因:看 stop 方法,该方法终止所有未结束的方法,包括 run 方法当线程被终止,立即释放被它锁住的所有对象的锁。这会导致对象处于不一致的状态。
疑问
ReentrantLock( )
final是线程安全