第十二章 并发
线程
package chapter12_concurrent.threads;public class ThreadsTest {public static final int DELAY = 10;public static final int STEPS = 100;public static final double MAX_AMOUNT = 1000;public static void main(String[] args) {var bank = new Bank(4, 100000);//Runnalbe 是一个函数式接口,可以用一个lambda表达式创建一个实例Runnable task1 = () -> {try {for (int i = 0; i < STEPS; i++) {double amount = MAX_AMOUNT * Math.random();bank.transfer(0, 1, amount);Thread.sleep((long) (DELAY * Math.random()));}} catch (InterruptedException e) {}};Runnable task2 = () -> {try {for (int i = 0; i < STEPS; i++) {double amount = MAX_AMOUNT * Math.random();bank.transfer(2, 3, amount);//休眠制定的毫秒数Thread.sleep((long) ( DELAY * Math.random()));}} catch (InterruptedException e) {}};//构造一个Thread对象,启动线程new Thread(task1).start();new Thread(task2).start();}
}
线程状态
New, Runnable, Blocked, Waiting, Timed waiting, Terminated
分别表示线程的六种状态: 新建, 可运行, 阻塞, 等待, 计时等待, 终止
新建线程
new Thread(r)
新建线程, 但是还没有开始运行
可运行线程
- 调用
start
方法后线程就属于可运行状态, 处于runnable
可能运行也可能没有运行
阻塞和等待进程
- 线程处于阻塞和等待状态不活动,不运行任何代码,消耗最少资源
- 当线程试图获取一个内部的对象锁,而该锁目前被其他线程占有,该线程就会被阻塞.所有其他线程释放这个锁,并且线程调度器允许该线程持有这个锁,他将变成非阻塞状态
- 当线程等待另一个线程通知调度器出现一个条件时,这个线程会进入等待状态
- 有的方法有超时参数,调用这些方法会让线程进入计时等待状态.这一状态会一直等待到超时期满或者接收到适当的通知
终止线程
run
方法正常退出- 一个没有捕获的异常终止了run方法
线程属性
中断线程
interrupt
方法可以用来请求终止一个线程,程序调用该方法就会设置线程中断状态- 测试是否设置了中断状态:
Thread.currentThread()
获得当前线程
while(!Thread.currentThread().isInterrupted()){...
}
-
线程被阻塞就无法检查中断状态
-
当在一个被
sleep, wait
调用的阻塞的线程上调用interrupt
方法,那个阻塞调用将被一个InterruptedException
异常中断 -
一般将中断解释为一个中止请求
-
如果循环调用了
sleep
,不要检测中断状态,应当捕获InterruptedException
-
interrupted
: 静态方法, 检查当前线程是否中断,并且清楚线程中断状态 -
isInterrupted
: 实例方法, 检查是否有线程被中断,不改变中断状态 -
不要抑制
InterruptedException
异常: -
-
catch
子句中调用Thread.currentThread().interrupt()
设置中断状态,调用者就可以检查中断状态了 -
void mySubTask(){...try{sleep(delay);}catch(InterruptedException e){Thread.currentThread().interrupt()} }
-
更好的是,标记方法,去掉try语句块
-
void mySubTask() throw InterruptedException { ... sleep(delay); }
-
守护线程
t.setDaemon(true);
- 守护线程唯一作用就是为其他线程提供服务
线程名
- 默认状态下有容易记忆的名字
- 改名:
setName(...)
未捕获异常的处理器
- 线程组
ThreadGroup
类实现了Thread.UncaughtExceptionHandler
接口,其uncaughtException
方法执行以下操作: -
- 该线程若有父线程那么调用父线程的
uncaughtException
方法 - 如果
Thread.getDefaultExceptionHandler
方法返回一个非null
处理器,调用该处理器 - 否则,如果
Throwable
类是ThreadDeath
的一个实例,什么也不做 - 否则将线程的名字和
Throwable
栈轨迹输出到System.err
- 该线程若有父线程那么调用父线程的
线程的优先级
setPriority
方法提高或者降低任何一个线程的优先级MIN_PRIORITY(1), NORM_PRIORITY(5), MAX_PRIORITY(10)
同步
- 竞态条件: 两个及以上线程共享对同一组数据的存取, 线程相互覆盖导致对象破坏
- 不同步的操作:
package chapter12_concurrent.unsynch;public class UnsynchBankTest {public static final int NACCOUNT = 100;public static final double INITIAL_BALANCE = 1000;public static final double MAX_AMOUNT = 1000;public static final int DELAY = 10;public static void main(String[] args) {var bank = new Bank(NACCOUNT, INITIAL_BALANCE);for (int i = 0; i < NACCOUNT; i++) {int fromAccount = i;Runnable r = () -> {try {while (true) {int toAccount = (int) (bank.size() * Math.random());double amount = MAX_AMOUNT * Math.random();bank.transfer(fromAccount, toAccount, amount);Thread.sleep((int) (DELAY * Math.random()));}} catch (InterruptedException e) {}};var t = new Thread(r);t.start();}}
}
锁对象
- 两种机制可以防止并发访问代码块:
synchronized
,ReentrantLock
- 基本结构:
mylock.lock();
try{critical section
}
finally{myLock.unlock();
}
- 该结构确保任何时刻只有一个线程进入临界区,一旦一个线程锁定了锁对象,其他任何线程无法通过
lock
语句 - 要把
unlock
放入finally
语句.如果临界区代码异常,锁必须释放,否则线程永远阻塞
public class bank{private ReentrantLock bankLock = new ReentrantLock();......public void transfer(int from, int to, double amount){bankLock.lock();try {if (accounts[from] < amount) return;System.out.print(Thread.currentThread());accounts[from] -= amount;System.out.printf(" %10.2f from %d to %d", amount, from, to);accounts[to] += amount;System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());}finally {bankLock.unlock();}}
}
- 每个bank有自己的
ReentrantLock
对象,两个线程视图访问同一个bank对象,锁可以用来保证串行化访问 - 两个线程访问不同的bank对象,每个线程会得到不同的锁对象,线程不会阻塞
条件对象
- 下面的
Bank
类加入了锁对象, 保护金额转账不够时候线程覆盖的情况 - 引入了条件对象, 调用
await
方法将当前线程暂停,放弃锁,允许其他线程执行 - 另一个线程完成转账后调用
signalAll
重新激活等待这个条件的所有线程 - 其中的某个线程将从
await
调用处返回得到这个锁,继续执行之前暂停的地方
package chapter12_concurrent.synch;import java.util.*;
import java.util.concurrent.locks.*;public class Bank {private final double[] accounts;private ReentrantLock banklock;private Condition sufficientFunds;public Bank(int n, double initialBalance) {accounts = new double[n];Arrays.fill(accounts, initialBalance);banklock = new ReentrantLock();sufficientFunds = banklock.newCondition();}public void transfer(int from, int to, double amount) throws InterruptedException{banklock.lock();try {while (accounts[from] < amount) {sufficientFunds.await();}System.out.println(Thread.currentThread());accounts[from] -= amount;System.out.printf(" %10.2f from %d to %d.", amount, from, to);accounts[to] += amount;System.out.printf(" Total balance: %10.2f%n", getTotalBanlance());sufficientFunds.signalAll();}finally {banklock.unlock();}}public double getTotalBanlance() {banklock.lock();try {double sum = 0.0;for (double a: accounts)sum += a;return sum;}finally {banklock.unlock();}}public int size() {return accounts.length;}
}