本文档建立在之前博客基础上。
Thread
类或Runnable
对象的run()
方法,只会执行统一线程中的任务,而不会启动新线程。调用start()
方法,此方法将创建一个执行run
方法的新线程。调用interrupt()
方法,可以请求终止线程。调用Thread.currentThread()
得到当前线程,再使用isInterrupt()
来判断线程是否被终止。
若线程被阻塞(调用sleep
或wait
),则无法检测中断状态。
• void interrupt()
向线程发送中断请求。线程的中断状态将被设置为true。如果目前该线程被一个sleep调用阻塞,那么,InterruptedException 异常被抛出。
• static boolean interrupted()
测试当前线程(即正在执行这一命令的线程)是否被中断。注意,这是一个静态方法。这一调用会产生副作用—它将当前线程的中断状态重置为 false。
• boolean islnterrupted()
测试线程是否被终止。不像静态的中断方法,这一调用不改变线程的中断状态。
• static Thread currentThread()
返回代表当前执行线程的 Thread 对象。
New (新创建)
Runnable (可运行)
Blocked (被阻塞)
Waiting (等待)
Timed waiting (计时等待)
Terminated (被终止)
getState()
方法。在任何给定时刻,一个可运行的线程可能正在运行也可能没有运行(这就是为什 么将这个状态称为可运行而不是运行
线程优先级、守护线程、 线程组以及处理未捕 获异常的处理器。
线程优先级
setPriority
方法提高或降低任何一个线程的优先级。守护线程
t.setDaemon(true)
; 将线程转换为守护线程。 这一方法必须在线程启动之前调用。 守护线程的唯一用途 是为其他线程提供服务。 当只剩下守护线程时, 虚拟机就退出了 。未捕获异常处理器
线程的 run方法不能抛出任何受查异常(必须在内部使用try...catch
来解决发生的任何必须处理异常), 但是,非受査异常(典型代表:RuntimeException
)会导致线程终止。在这种情 况下,线程就死亡了。
在线程死亡之前, 异常被传递到一个用于未捕获异常的处理器。 该处理器必须属于一个实现 Thread.UncaughtExceptionHandler
接口的类。这个接口只有一个方法:
void uncaughtException(Thread t, Throwable e)
可以用 setUncaughtExceptionHandler
方法为任何线程安装一个处理器。也可以用 Thread 类的静态方法 setDefaultUncaughtExceptionHandler
为所有线程安装一个默认的处理器。
如果不安装默认的处理器, 默认的处理器为空。但是, 如果不为独立的线程安装处理 器,此时的处理器就是该线程的 ThreadGroup
对象。
Thread aThread = new Thread(() -> {
System.out.println("执行开始");
int a = 10 / 0;
System.out.println("执行结束");
}, "A");
aThread.start();
// 默认线程组处理结果
/*
执行开始
Exception in thread "A" java.lang.ArithmeticException: / by zero
at BaseLearn.multithreadingTest.Test6.Main.lambda$main$0(Main.java:11)
at java.lang.Thread.run(Thread.java:748)
*/
Thread aThread = new Thread(() -> {
System.out.println("执行开始");
int a = 10 / 0;
System.out.println("执行结束");
}, "A");
aThread.setUncaughtExceptionHandler((Thread t, Throwable e) -> {
System.out.println("发现错误了");
System.out.println(e.toString());
});
aThread.start();
// 添加自定义异常处理
Thread aThread = new Thread(() -> {
System.out.println("执行开始");
int a = 10 / 0;
System.out.println("执行结束");
}, "A");
aThread.setUncaughtExceptionHandler((Thread t, Throwable e) -> {
System.out.println("发现错误了");
e.printStackTrace();
});
aThread.start();
/*
执行开始
java.lang.ArithmeticException: / by zero
发现错误了
at BaseLearn.multithreadingTest.Test6.Main.lambda$main$0(Main.java:11)
at java.lang.Thread.run(Thread.java:748)
*/
线程组是一个可以统一管理的线程集合。默认情况下,创建的所有线程属于相同的线程组, 但是, 也可能会建立其他的组。现在引入了更好的特性用于线程集合的操作, 所以建议不要在自己的程序中使用线程组。
在大多数实际的多线程应用中, 两个或两个以上的线程需要共享对同一数据的存取。若线程调用了一个修改该对象状态的方法,根据各线程访问数据的次序,可能会产生讹误的对象。这样一个情况通常称为竞争条件。
竞争条件例子:(示例来自Java核心技术卷14.5.1,643页)
模拟一个有若干账户的银行。随机地生成在这些账户之间转移钱 款的交易。每一个账户有一个线程。每一笔交易中, 会从线程所服务的账户中随机转移一定 数目的钱款到另一个随机账户。
// 第一部分截取
public void transfer(int from, int to, double amount {
System.out.print(Thread,currentThread0);
// 从accounts的第from账户中扣钱
accounts[from] -= amount;
System.out.printff" X10.2f from Xd to Xd", amount, from, to);
// 加给to账户
accounts[to] += amount;
System.out.printf("Total Balance: X10.2fXn", getTotalBalance());
}
// 第二部分截取
Runnable r = () -> {
try {
while (true) {
// 转义账户随机生成
int toAccount = (int) (bank.sizeO * Math.random());
// 转义钱数
double amount = MAX_AMOUNT * Math.random();
// 转移
bank.transfer(fromAccount, toAccount, amount);
// 暂停一会
Thread,sleep((int) (DELAY * Math.randomO));
}
} catch (InterruptedExeeption e){
}
} ;
示例程序源代码存放在个人码云示例中点击访问
执行结果:
Thread[Thread-26,5,main] 82.57 from 26 to 35 Total Balance: 100000.00
Thread[Thread-44,5,main] 819.26 from 44 to 49 Total Balance: 100000.00
Thread[Thread-37,5,main] 966.78 from 37 to 40 Total Balance: 100000.00
Thread[Thread-5,5,main] 811.87 from 5 to 89 Total Balance: 100000.00
Thread[Thread-81,5,main] 344.01 from 81 to 3 Total Balance: 100000.00
Thread[Thread-14,5,main] 730.87 from 14 to 17 Total Balance: 100000.00
Thread[Thread-87,5,main] 965.71 from 87 to 97 Total Balance: 100000.00
Thread[Thread-6,5,main]Thread[Thread-62,5,main]Thread[Thread-39,5,main] 313.93 from 39 to 56 Total Balance: 99331.28
Thread[Thread-57,5,main] 31.16 from 57 to 36 Total Balance: 99331.28
Thread[Thread-77,5,main]Thread[Thread-57,5,main] 523.69 from 57 to 19 Total Balance: 98750.24
Thread[Thread-17,5,main] 537.68 from 17 to 22 Total Balance: 98750.24
581.04 from 77 to 13 Total Balance: 99331.28
Thread[Thread-13,5,main] 212.00 from 13 to 46 Total Balance: 99331.28
Thread[Thread-91,5,main] 98.60 from 91 to 34 Total Balance: 99331.28
Thread[Thread-20,5,main] 898.50 from 20 to 95 Total Balance: 99331.28
15.54 from 62 to 70 Total Balance: 99346.82
653.18 from 6 to 34 Total Balance: 100000.00
Thread[Thread-20,5,main] 777.97 from 20 to 14 Total Balance: 100000.00
Thread[Thread-13,5,main] 194.07 from 13 to 40 Total Balance: 100000.00
Thread[Thread-74,5,main] 815.60 from 74 to 58 Total Balance: 100000.00
Thread[Thread-70,5,main] 844.52 from 70 to 73 Total Balance: 100000.00
由结果可以看出。一段时间之后, 错误不知不觉地出现了,总额要么增加, 要么变少。当两个线程试图同时更新同一个账户的时候, 这个问题就出现了。
个人分析:因为线程执行时有抢占资源的问题,假定现在Thread-1线程为A向B转钱,A账户钱数减少时,Thread-2线程此时抢占到了资源开始运行,此时A的钱数少了,所以银行总钱数减少,但是后续线程A又拿到了运行权,此时才会继续执行下面的增加B账户钱数的运算。所以后面钱数又回归正常了。
官方分析:假定两个线程同时执行指令
accounts[to] += amount;
问题在于这不是原子操作。该指令可能被处理如下:
现在,假定第 1 个线程执行步骤 1 和 2, 然后, 它被剥夺了运行权。假定第 2 个线程被 唤醒并修改了 accounts 数组中的同一项。然后,第 1 个线程被唤醒并完成其第 3 步。 这样, 这一动作擦去了第二个线程所做的更新。于是, 总金额不再正确。(见图 14-4J 我们的测试程序检测到这一i化误。(当然, 如果线程在运行这一测试时被中断,也有可能 会出现失败警告!)
锁对象
有两种机制防止代码块受并发访问的干扰。Java语言提供一个 synchronized
关键字达 到这一目的,并且 Java SE 5.0引入了 ReentrantLock
类。synchronized
关键字自动提供一个 锁以及相关的“ 条件”, 对于大多数需要显式锁的情况, 这是很便利的。
用 ReentrantLock 保护代码块的基本结构如下:
myLock.lock();
try {
critical section
} finally {
myLock.unlockO;
}
这一结构确保任何时刻只有一个线程进人临界区。一旦一个线程封锁了锁对象, 其他任 何线程都无法通过 lock语句。当其他线程调用 lock 时,它们被阻塞,直到第一个线程释放 锁对象。
把解锁操作括在 finally 子句之内是至关重要的。如果在临界区的代码抛出异常, 锁必须被释放。否则, 其他线程将永远阻塞。
如果使用锁, 就不能使用带资源的 try语句。
修改示例:
public class Bank {
private Lock bankLock = new ReentrantLock0;
public void transfer(int from, int to, int amount) t bankLock.lock();
try {
System.out.print(Thread.currentThread0); accounts[from] -= amount; System.out.printf(" X10.2f from %A to Xd", amount, from, to);
accounts[to] += amount; System.out.printf(" Total Balance: X10.2fXn", getTotalBalanceO);
} finally {
banklock.unlock();
}
}
结果:
101.10 from 93 to 68 Total Balance: 100000.00
Thread[Thread-66,5,main] 20.39 from 66 to 41 Total Balance: 100000.00
Thread[Thread-5,5,main] 905.92 from 5 to 89 Total Balance: 100000.00
Thread[Thread-87,5,main] 608.07 from 87 to 41 Total Balance: 100000.00
Thread[Thread-38,5,main] 942.93 from 38 to 84 Total Balance: 100000.00
Thread[Thread-39,5,main] 757.74 from 39 to 32 Total Balance: 100000.00
Thread[Thread-62,5,main] 503.86 from 62 to 16 Total Balance: 100000.00
Thread[Thread-4,5,main] 417.09 from 4 to 51 Total Balance: 100000.00
Thread[Thread-67,5,main] 781.40 from 67 to 67 Total Balance: 100000.00
Thread[Thread-56,5,main] 972.45 from 56 to 74 Total Balance: 100000.00
Thread[Thread-91,5,main] 102.22 from 91 to 65 Total Balance: 100000.00
Thread[Thread-58,5,main] 112.92 from 58 to 51 Total Balance: 100000.00
Thread[Thread-61,5,main] 375.14 from 61 to 64 Total Balance: 100000.00
Thread[Thread-1,5,main] 275.49 from 1 to 40 Total Balance: 100000.00
Thread[Thread-33,5,main] 368.64 from 33 to 53 Total Balance: 100000.00
Thread[Thread-25,5,main] 875.35 from 25 to 86 Total Balance: 100000.00
Thread[Thread-30,5,main] 104.90 from 30 to 21 Total Balance: 100000.00