Skip to content

Commit 8ec5c6d

Browse files
committed
线程
1 parent 19b66db commit 8ec5c6d

6 files changed

Lines changed: 222 additions & 4 deletions

File tree

src/com/yale/test/java/fanshe/perfma/VolatileDemo.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,33 @@
1414
* volatile解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。
1515
说明:如果是count++操作,使用如下类实现:AtomicInteger count = new AtomicInteger(); count.addAndGet(1);
1616
如果是JDK8,推荐使用LongAdder对象,比AtomicLong性能更好(减少乐观锁的重试次数)。《阿里巴巴Java开发手册(泰山版).
17+
*
18+
* 为什么要对线程间共享的变量用关键字volatile声明?这涉及到Java的内存模型。在Java虚拟机中,变量的值保存在主内存中,但是,当线程访问变量时,它会先获取一个副本,并保存在自己的工作内存中。
19+
* 如果线程修改了变量的值,虚拟机会在某个时刻把修改后的值回写到主内存,但是,这个时间是不确定的!
20+
* ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
21+
Main Memory
22+
│ │
23+
┌───────┐┌───────┐┌───────┐
24+
│ │ var A ││ var B ││ var C │ │
25+
└───────┘└───────┘└───────┘
26+
│ │ ▲ │ ▲ │
27+
─ ─ ─│─│─ ─ ─ ─ ─ ─ ─ ─│─│─ ─ ─
28+
│ │ │ │
29+
┌ ─ ─ ┼ ┼ ─ ─ ┐ ┌ ─ ─ ┼ ┼ ─ ─ ┐
30+
▼ │ ▼ │
31+
│ ┌───────┐ │ │ ┌───────┐ │
32+
│ var A │ │ var C │
33+
│ └───────┘ │ │ └───────┘ │
34+
Thread 1 Thread 2
35+
└ ─ ─ ─ ─ ─ ─ ┘ └ ─ ─ ─ ─ ─ ─ ┘
36+
* 这会导致如果一个线程更新了某个变量,另一个线程读取的值可能还是更新前的。例如,主内存的变量a = true,线程1执行a = false时,它在此刻仅仅是把变量a的副本变成了false,
37+
* 主内存的变量a还是true,在JVM把修改后的a回写到主内存之前,其他线程读取到的a的值仍然是true,这就造成了多线程之间共享的变量不一致。
38+
* 因此,volatile关键字的目的是告诉虚拟机:
39+
* 每次访问变量时,总是获取主内存的最新值;
40+
* 每次修改变量后,立刻回写到主内存。
41+
* volatile关键字解决的是可见性问题:当一个线程修改了某个共享变量的值,其他线程能够立刻看到修改后的值。
42+
* 如果我们去掉volatile关键字,运行上述程序,发现效果和带volatile差不多,这是因为在x86的架构下,JVM回写主内存的速度非常快,但是,换成ARM的架构,就会有显著的延迟。
43+
* https://www.liaoxuefeng.com/wiki/1252599548343744/1306580767211554
1744
* @author dell
1845
*/
1946
public class VolatileDemo {

src/com/yale/test/thread/DameonThread.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,37 @@ void wirteFile () throws IOException, InterruptedException {
3535
outputStream.close();
3636
}
3737
}
38+
39+
/*
40+
* 守护线程
41+
* Java程序入口就是由JVM启动main线程,main线程又可以启动其他线程。当所有线程都运行结束时,JVM退出,进程结束。
42+
* 如果有一个线程没有退出,JVM进程就不会退出。所以,必须保证所有线程都能及时结束。
43+
* 但是有一种线程的目的就是无限循环,例如,一个定时触发任务的线程:
44+
* class TimerThread extends Thread {
45+
@Override
46+
public void run() {
47+
while (true) {//这个线程谁来结束他呢?答案是使用守护线程,等其他非守护线程结束了,这个守护线程就自己退出了.
48+
System.out.println(LocalTime.now());
49+
try {
50+
Thread.sleep(1000);
51+
} catch (InterruptedException e) {
52+
break;
53+
}
54+
}
55+
}
56+
}
57+
* 如果这个线程不结束,JVM进程就无法结束。问题是,由谁负责结束这个线程?
58+
* 然而这类线程经常没有负责人来负责结束它们。但是,当其他线程结束时,JVM进程又必须要结束,怎么办?
59+
* 答案是使用守护线程(Daemon Thread)。
60+
* 守护线程是指为其他线程服务的线程。在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出。
61+
* 因此,JVM退出时,不必关心守护线程是否已结束。
62+
* 如何创建守护线程呢?方法和普通线程一样,只是在调用start()方法前,调用setDaemon(true)把该线程标记为守护线程:
63+
* 在守护线程中,编写代码要注意:守护线程不能持有任何需要关闭的资源,例如打开文件等,因为虚拟机退出时,守护线程没有任何机会来关闭文件,这会导致数据丢失。
64+
* 小结
65+
* 守护线程是为其他线程服务的线程;
66+
* 所有非守护线程都执行完毕后,虚拟机退出;
67+
* 守护线程不能持有需要关闭的资源(如打开文件等)
68+
*/
3869
public class DameonThread {
3970
public static void main (String [] args) {
4071
System.out.println("主线程"+Thread.currentThread().getName()+"开始运行");

src/com/yale/test/thread/mldn/ThreadInterruptedDemo.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
* 我们举个栗子:假设从网络下载一个100M的文件,如果网速很慢,用户等得不耐烦,就可能在下载过程中点“取消”,这时,程序就需要中断下载线程的执行。
99
* 中断一个线程非常简单,只需要在其他线程中对目标线程调用interrupt()方法,目标线程需要反复检测自身状态是否是interrupted状态,如果是,就立刻结束运行。
1010
* 我们还是看示例代码:
11+
* 小结
12+
* 对目标线程调用interrupt()方法可以请求中断一个线程,目标线程通过检测isInterrupted()标志获取自身是否已中断。如果目标线程处于等待状态,该线程会捕获到InterruptedException;
13+
* 目标线程检测到isInterrupted()为true或者捕获了InterruptedException都应该立刻结束自身线程;
14+
* 通过标志位判断需要正确使用volatile关键字;
15+
* volatile关键字解决了共享变量在线程间的可见性问题。
1116
*/
1217
public class ThreadInterruptedDemo {
1318

src/com/yale/test/thread/mldn/ThreadInterruptedDemo2.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,20 @@
88
* 我们举个栗子:假设从网络下载一个100M的文件,如果网速很慢,用户等得不耐烦,就可能在下载过程中点“取消”,这时,程序就需要中断下载线程的执行。
99
* 中断一个线程非常简单,只需要在其他线程中对目标线程调用interrupt()方法,目标线程需要反复检测自身状态是否是interrupted状态,如果是,就立刻结束运行。
1010
* 我们还是看示例代码:
11+
* 小结
12+
* 对目标线程调用interrupt()方法可以请求中断一个线程,目标线程通过检测isInterrupted()标志获取自身是否已中断。如果目标线程处于等待状态,该线程会捕获到InterruptedException;
13+
* 目标线程检测到isInterrupted()为true或者捕获了InterruptedException都应该立刻结束自身线程;
14+
* 通过标志位判断需要正确使用volatile关键字;
15+
* volatile关键字解决了共享变量在线程间的可见性问题。
1116
*/
1217
public class ThreadInterruptedDemo2 {
1318

14-
public static void main(String[] args) {
19+
public static void main(String[] args) throws InterruptedException {
1520
/*
1621
* 下面代码来自廖雪峰JAVA教程
1722
* https://www.liaoxuefeng.com/wiki/1252599548343744/1306580767211554
1823
* 仔细看下述代码,main线程通过调用t.interrupt()方法中断t线程,但是要注意,interrupt()方法仅仅向t线程发出了“中断请求”,
1924
* 至于t线程是否能立刻响应,要看具体代码。而t线程的while循环会检测isInterrupted(),所以上述代码能正确响应interrupt()请求,使得自身立刻结束运行run()方法。
20-
* 如果线程处于等待状态,例如,t.join()会让main线程进入等待状态,此时,如果对main线程调用interrupt(),
21-
* join()方法会立刻抛出InterruptedException,因此,目标线程只要捕获到join()方法抛出的InterruptedException,
22-
* 就说明有其他线程对其调用了interrupt()方法,通常情况下该线程应该立刻结束运行。
2325
*/
2426
try {
2527
MyThreadIsInterrupted mtii = new MyThreadIsInterrupted();
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package com.yale.test.thread.mldn;
2+
3+
/*
4+
* https://blog.csdn.net/qq_39682377/article/details/81449451
5+
* https://www.liaoxuefeng.com/wiki/1252599548343744/1306580767211554
6+
* 中断线程
7+
* 如果线程需要执行一个长时间任务,就可能需要能中断线程。中断线程就是其他线程注意是其他线程给该线程发一个信号,该线程收到信号后结束执行run()方法,使得自身线程能立刻结束运行。
8+
* 我们举个栗子:假设从网络下载一个100M的文件,如果网速很慢,用户等得不耐烦,就可能在下载过程中点“取消”,这时,程序就需要中断下载线程的执行。
9+
* 中断一个线程非常简单,只需要在其他线程中对目标线程调用interrupt()方法,目标线程需要反复检测自身状态是否是interrupted状态,如果是,就立刻结束运行。
10+
* 我们还是看示例代码:
11+
* 小结
12+
* 对目标线程调用interrupt()方法可以请求中断一个线程,目标线程通过检测isInterrupted()标志获取自身是否已中断。如果目标线程处于等待状态,该线程会捕获到InterruptedException;
13+
* 目标线程检测到isInterrupted()为true或者捕获了InterruptedException都应该立刻结束自身线程;
14+
* 通过标志位判断需要正确使用volatile关键字;
15+
* volatile关键字解决了共享变量在线程间的可见性问题。
16+
*
17+
* https://www.liaoxuefeng.com/wiki/1252599548343744/1306580767211554
18+
* 失眠是因为闲 评论: 那么中断线程用哪个方法好呢?interrupt()还是running?
19+
* 廖雪峰答:实际情况是线程要自己根据条件1、条件2、条件3决定是否退出:interrupt一般用于清理,但很多程序都是直接kill退出,懒得清理
20+
* 廖雪峰这里说的一般用于清理,但很多程序都是直接kill退出,懒得清理。这句话的意思是,interrupt一般用于你要关闭服务器时,如果要通知线程结束,然后等待所有线程都结束,再关闭服务器。
21+
* 但是大多数人,都是直接关闭服务器的,不会通知线程结束的。
22+
*
23+
* 廖雪峰答:线程退出就是run()方法执行完毕,至于内部捕获InterruptedException跟普通代码是一样的。
24+
*
25+
* young233_54314问: 中断这个词真的不好。还没看文章之前我还以为是计算机组成原理/操作系统 里面讲的中断。 仔细一个原来是 中止的意思
26+
* https://tse4-mm.cn.bing.net/th/id/OIP.NXZbsGumE1saNcrt8AaCWgAAAA?pid=Api&rs=1
27+
* 廖雪峰答:那你理解错了,interrupt真的是中断,因为线程可以选择不终止。
28+
*/
29+
public class ThreadInterruptedDemo3 {
30+
public static void main(String[] args) throws InterruptedException {
31+
/*
32+
* 如果线程处于等待状态,例如,t.join()会让main线程进入等待状态,此时,如果对main线程调用interrupt(),
33+
* join()方法会立刻抛出InterruptedException,因此,目标线程只要捕获到join()方法抛出的InterruptedException,
34+
* 就说明有其他线程对其调用了interrupt()方法,通常情况下该线程应该立刻结束运行。
35+
* 我们来看下面的示例代码:
36+
* main线程通过调用t.interrupt()从而通知t线程中断,而此时t线程正位于hello.join()的等待中,此方法会立刻结束等待并抛出InterruptedException。
37+
* 由于我们在t线程中捕获了InterruptedException,因此,就可以准备结束该线程。在t线程结束前,对hello线程也进行了interrupt()调用通知其中断。
38+
* 如果去掉这一行代码,可以发现hello线程仍然会继续运行,且JVM不会退出。
39+
*/
40+
Thread t = new MyThreadInterruptedException();
41+
t.start();
42+
Thread.sleep(1000);
43+
t.interrupt();//中断线程
44+
t.join();//等待t线程结束
45+
System.out.println("演示结束");
46+
47+
/*
48+
* 另一个常用的中断线程的方法是设置标志位。我们通常会用一个running标志位来标识线程是否应该继续运行,在外部线程中,通过把HelloThread.running置为false,就可以让线程结束:
49+
*/
50+
}
51+
}
52+
53+
class MyThreadInterruptedException extends Thread {
54+
@Override
55+
public void run() {
56+
// try {
57+
/*
58+
* https://www.liaoxuefeng.com/wiki/1252599548343744/1306580767211554
59+
* 下面的 wlwheart 评论说:若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常。
60+
* 这里有一个很有意思的问题sleep方法抛出的是InterruptedException异常,wait方法抛出的是IllegalMonitorStateException,值得思考一下
61+
*/
62+
// Thread.sleep(100000);
63+
// } catch (IllegalMonitorStateException e) {
64+
// System.out.println("IllegalMonitorStateException是运行时异常,不需要捕获的");
65+
// e.printStackTrace();
66+
// } catch (InterruptedException e) {
67+
// System.out.println("InterruptedException是正常异常,必须要被捕获的");
68+
// e.printStackTrace();
69+
// }
70+
// try {
71+
/*
72+
* https://www.liaoxuefeng.com/wiki/1252599548343744/1306580767211554
73+
* 下面的 wlwheart 评论说:若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常。
74+
* 这里有一个很有意思的问题sleep方法抛出的是InterruptedException异常,wait方法抛出的是IllegalMonitorStateException,值得思考一下
75+
*/
76+
// this.wait(10000000);
77+
// } catch (IllegalMonitorStateException e) {
78+
// System.out.println("IllegalMonitorStateException是运行时异常,不需要捕获的");
79+
// e.printStackTrace();
80+
// } catch (InterruptedException e) {
81+
// System.out.println("InterruptedException是正常异常,必须要被捕获的");
82+
// e.printStackTrace();
83+
// }
84+
Thread hello = new HelloThread();
85+
hello.start();
86+
try {
87+
hello.join();
88+
} catch (InterruptedException e) {
89+
System.out.println("拦截到InterruptedException异常,我被中断了,interrupted!");
90+
}
91+
hello.interrupt();
92+
}
93+
}
94+
95+
class HelloThread extends Thread {
96+
@Override
97+
public void run() {
98+
int n =0;
99+
while (!isInterrupted()) {
100+
n++;
101+
System.out.println(n + "hello!");
102+
try {
103+
Thread.sleep(100);
104+
} catch (InterruptedException e) {
105+
System.out.println("拦截到InterruptedException异常,这里会抛出异常吗??");
106+
break;
107+
}
108+
}
109+
}
110+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.yale.test.thread.mldn;
2+
3+
/*
4+
* https://blog.csdn.net/qq_39682377/article/details/81449451
5+
* https://www.liaoxuefeng.com/wiki/1252599548343744/1306580767211554
6+
* 中断线程
7+
* 如果线程需要执行一个长时间任务,就可能需要能中断线程。中断线程就是其他线程注意是其他线程给该线程发一个信号,该线程收到信号后结束执行run()方法,使得自身线程能立刻结束运行。
8+
* 我们举个栗子:假设从网络下载一个100M的文件,如果网速很慢,用户等得不耐烦,就可能在下载过程中点“取消”,这时,程序就需要中断下载线程的执行。
9+
* 中断一个线程非常简单,只需要在其他线程中对目标线程调用interrupt()方法,目标线程需要反复检测自身状态是否是interrupted状态,如果是,就立刻结束运行。
10+
* 我们还是看示例代码:
11+
* 小结
12+
* 对目标线程调用interrupt()方法可以请求中断一个线程,目标线程通过检测isInterrupted()标志获取自身是否已中断。如果目标线程处于等待状态,该线程会捕获到InterruptedException;
13+
* 目标线程检测到isInterrupted()为true或者捕获了InterruptedException都应该立刻结束自身线程;
14+
* 通过标志位判断需要正确使用volatile关键字;
15+
* volatile关键字解决了共享变量在线程间的可见性问题。
16+
*/
17+
public class ThreadInterruptedMarkDemo {
18+
public static void main(String[] args) throws InterruptedException {
19+
/*
20+
* 另一个常用的中断线程的方法是设置标志位。我们通常会用一个running标志位来标识线程是否应该继续运行,在外部线程中,
21+
* 通过把HelloThread.running置为false,就可以让线程结束:
22+
* 注意到HelloThread的标志位boolean running是一个线程间共享的变量。
23+
* 线程间共享变量需要使用volatile关键字标记,确保每个线程都能读取到更新后的变量值。
24+
*/
25+
HelloMarkThread hmt = new HelloMarkThread();
26+
hmt.start();
27+
Thread.sleep(1);
28+
hmt.running = false;//标志位设置为false,告诉线程要结束了
29+
}
30+
}
31+
32+
class HelloMarkThread extends Thread {
33+
public volatile boolean running = true;
34+
@Override
35+
public void run() {
36+
int n =0;
37+
while (running) {
38+
n++;
39+
System.out.println(n + "hello!");
40+
}
41+
System.out.println("线程结束了end!");
42+
}
43+
}

0 commit comments

Comments
 (0)