Day5 - 深入理解Thread3、线程安全和数据同步1
(接上文)
4.8. 关闭线程
4.8.1 进程正常结束
如果一个进程不涉及复杂耗时的逻辑,让它一直执行自动结束即可。
4.8.2 捕获中断信号退出线程
使用前面的interrupt()
方法向线程发送中断信号,再通过isInterrupted()
/interrupted()
捕获。(注意这两个方法的区别)
/**
* 捕获中断信号方式退出线程
*/
public class ThreadExitDemo01 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(){
@Override
public void run() {
System.out.println("I am running");
while (!this.isInterrupted()) {
// run
}
System.out.println("I am exiting");
}
};
t.start();
TimeUnit.SECONDS.sleep(5); // 模拟主线程5秒钟后关闭
System.out.println("main is exiting");
t.interrupt(); // 向t发送中断信号
}
}
I am running
main is exiting
I am exiting
Process finished with exit code 0
4.8.3 使用volatile关键字
以下程序也可以达到相同目的。关于volatile关键字后续学习…
/**
* 声明一个volatile属性成员变量
*/
public class ThreadExitDemo02 {
public static void main(String[] args) throws InterruptedException {
TestThread t = new TestThread();
t.start();
TimeUnit.SECONDS.sleep(5); // 模拟主线程5秒钟后关闭
System.out.println("main is exiting");
t.interrupt(); // 向t发送中断信号
}
}
class TestThread extends Thread {
public volatile boolean closed = false; // 开关,相当于中断信号的作用
@Override
public void run() {
System.out.println("I am running");
while (!closed && !this.isInterrupted()) {
// run
}
System.out.println("I am exiting");
}
public void close() {
this.closed = true;
this.interrupt();
}
}
4.9. 总结
本节学习了Thread类的获取线程信息、获取当前线程、yield()
、sleep()
、join()
、进程中断的相关API方法及其作用,最后了解了几种关闭线程的方法。
五、线程安全和数据同步
5.1 synchronized
5.1.1 synchronized的使用
synchronized
可以修饰方法使该方法成为同步方法,同一时间只有一个线程能够运行该方法;synchronized
也可以将一段代码包住,形成同步块,同一时间只有一个线程能够运行该块代码。
// 同步方法
public synchronized void method {
// 代码
}
// 同步块
public void method {
synchronized (o) {
// code
}
}
5.1.2 synchronized作用分析
先运行synchronized
方法或块的线程会持有锁,将该方法或该段代码锁定,只允许当前线程运行。待此线程运行完成synchronized
方法或块时,会释放对该方法或该段代码锁定,其他线程方可竞争锁。竞争到锁的线程重复上述过程。
5.1.3 synchronized 关键字使用注意
synchronized
的锁对象不能为null;
public class Test extends Thread {
private final Object MUTEX = null; // 错误
public void run() {
while (true) {
synchronized(MUTEX) {
// code
}
}
}
}
synchronized
作用域太大;- 不同的锁对象企图锁相同的代码块;
public class Test implements Runnable {
private final Object MUTEX = new Object();
public void run() {
while (true) {
synchronized(MUTEX) {
// code
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new Test()).start(); // 这里创建的5个线程获得的MUTEX不是同一个对象,无法达到同步的效果。
}
}
}
- 死锁问题
class T1 extends Thread {
public void run() {
synchronized("A") {
TimeUnit.SECOND.sleep(3);
synchronized("B") {
// codes
}
}
}
}
class T2 extends Thread {
public void run() {
synchronized("B") {
TimeUnit.SECOND.sleep(3);
synchronized("A") {
// codes
}
}
}
}
public static void main(String[] args) {
new T1().start();
new T2().start(); // 死锁
}
使用jstack
指令查看线程信息:
5.2 ThisMonitor和ClassMonitor
5.2.1 ThisMonitor
即以当前对象作为锁。形式可以是:
public class T {
public synchronized void method1() { // 加在非静态方法上
// code
}
public void method2() {
synchronized(this) { // 使用this关键字
// code
}
}
}
5.2.2 ClassMonitor
即以当前类的Class对象作为锁。形式可以是:
public class T {
public synchronized static void method1() { // 加在静态方法上
// code
}
public void method2() {
synchronized(T.class) { // 使用类Class对象
// code
}
}
}
注意: 如果想要都添加了
synchronized
的方法同时可以被调用,需要其中一个方法使用synchronized
块的形式:public class T { public synchronized void method1() { // 加在非静态方法上 // code } private final Object MONITOR = new Object(); // 使用另一个对象,避免使用this monitor public void method2() { synchronized(MONITOR) { // code } } }