Luckylau's Blog

Java并发之线程的理解

并发的理解

​ 对于一个CPU来说,无论我创建多少个线程,所谓的“并发执行”、“同时”其实都不是真正意义上的“同时”。操作系统将进程线程进行管理,轮流(没有固定的顺序)分配每个进程很短的一段时间(不一定是均分),然后在每个线程内部,程序代码自己处理该进程内部线程的时间分配,多个线程之间相互的切换去执行,这个切换时间也是非常短的。因此多任务、多进程、多线程都是操作系统给人的一种宏观感受,从微观角度看,程序的运行是异步执行的。虽然操作系统是多线程的,但CPU每一时刻只能做一件事。另外上下文开销是多线程面临的主要问题,可以通过无锁并发编程、CAS算法、使用最少线程和使用协程的方式解决。

线程和进程的区别

1,每个进程都有独立的代码和数据空间,进程间的切换会有较大的开销;
2,线程可以看成是轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器,线程切换的开销小;
3,多进程:在操作系统中能同时运行多个任务(程序);
4,多线程:在同一应用程序中有多个顺序流同时执行;

参考可见:线程与进程的区别

线程的实现

实现线程主要有3种方式:使用内核线程实现、 使用用户线程实现和使用用户线程加轻量级进程混合实现。

使用内核线程实现

​ 内核线程(Kernel-Level Thread,KLT)就是直接由操作系统内核(Kernel,下称内核)支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上。 每个内核线程可以视为内核的一个分身,这样操作系统就有能力同时处理多件事情,支持多线程的内核就叫做多线程内核(MultiThreads Kernel)。由于内核线程的支持,每个轻量级进程都成为一个独立的调度单元,即使有一个轻量级进程在系统调用中阻塞了,也不会影响整个进程继续工作,但是轻量级进程具有它的局限性:首先,由于是基于内核线程实现的,所以各种线程操作,如创建、 析构及同步,都需要进行系统调用。 而系统调用的代价相对较高,需要在用户态(User Mode)和内核态(KernelMode)中来回切换。 其次,每个轻量级进程都需要有一个内核线程的支持,因此轻量级进程要消耗一定的内核资源(如内核线程的栈空间),因此一个系统支持轻量级进程的数量是有限的。

使用用户线程实现

​ 从广义上来讲,一个线程只要不是内核线程,就可以认为是用户线程(User Thread,UT),因此,从这个定义上来讲,轻量级进程也属于用户线程,但轻量级进程的实现始终是建立在内核之上的,许多操作都要进行系统调用,效率会受到限制。而狭义上的用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现。 用户线程的建立、 同步、 销毁和调度完全在用户态中完成,不需要内核的帮助。如果程序实现得当,这种线程不需要切换到内核态,因此操作可以是非常快速且低消耗的,也可以支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。

​ 使用用户线程的优势在于不需要系统内核支援,劣势也在于没有系统内核的支援,所有的线程操作都需要用户程序自己处理。 线程的创建、 切换和调度都是需要考虑的问题,而且由于操作系统只把处理器资源分配到进程,那诸如“阻塞如何处理”、 “多处理器系统中如何将线程映射到其他处理器上”这类问题解决起来将会异常困难,甚至不可能完成。 因而使用用户线程实现的程序一般都比较复杂,除了以前在不支持多线程的操作系统中(如DOS)的多线程程序与少数有特殊需求的程序外,现在使用用户线程的程序越来越少了,Java、Ruby等语言都曾经使用过用户线程,最终又都放弃使用它。

使用用户线程加轻量级进程混合实现

​ 用户线程还是完全建立在用户空间中,因此用户线程的创建、 切换、 析构等操作依然廉价,并且可以支持大规模的用户线程并发。 而操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级线程来完成,大大降低了整个进程被完全阻塞的风险。

线程的状态

线程的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Calculator implements Runnable {
private int number;
public Calculator(int number){
this.number = number;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i=1; i<=10; i++){
System.out.printf("%s: %d * %d = %d\n",Thread.currentThread().getName(),number,i,i*number);
}
}
}
1
2
3
4
5
6
7
8
9
public class Main {
public static void main(String[] args) {
for (int i=1; i<=2; i++){
Calculator calculator=new Calculator(i);
Thread thread=new Thread(calculator);
thread.start();
}
}
}

​ 每个Java程序最少有一个执行线程。当你运行程序的时候,JVM运行负责调用main()方法的执行线程。当调用Thread对象的start()方法时,我们创建了另一个执行线程。在这些start()方法调用之后,我们的程序就有了多个执行线程。当全部的线程执行结束时(更具体点,所有非守护线程结束时),Java程序就结束了。如果初始线程(执行main()方法的主线程)运行结束,其他的线程还是会继续执行直到执行完成。但是如果某个线程调用System.exit()指示终结程序,那么全部的线程都会结束执行。创建一个Thread类的对象不会创建新的执行线程。同样,调用实现Runnable接口的run()方法也不会创建一个新的执行线程。只有调用start()方法才能创建一个新的执行线程。

获取和设置线程信息

Thread类的对象中保存了一些属性信息能够帮助我们辨别每一个线程

ID:每个线程的独特标示;

Name:线程的名称;

Priority:线程对象的优先级。优先级别在1-10之间,1是最低级,10是最高级。

Status:线程状态。(new,runnable,blocked,waiting,time waiting 或terminated)

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Calculator implements Runnable {
private int number;
public Calculator(int number){
this.number = number;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i=1; i<=10; i++){
System.out.printf(" %s: %d * %d = %d\n",Thread.currentThread().getName(),number,i,i*number);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.io.FileWriter;
import java.io.PrintWriter;
import java.lang.Thread.State;
public class Main {
public static void main(String[] args) {
Thread[] threads = new Thread[10];
Thread.State[] status = new Thread.State[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(new Calculator(i));
if((i%2) == 0)
threads[i].setPriority(Thread.MAX_PRIORITY);
else
threads[i].setPriority(Thread.MIN_PRIORITY);
threads[i].setName("Thread "+i);
}
try{
FileWriter file = new FileWriter(".\\log.txt");
PrintWriter pw = new PrintWriter(file);
for(int i =0;i<10;i++){
pw.println("main: status of thread "+i+": "+threads[i].getState());
status[i] = threads[i].getState();
}
for(int i =0;i<10;i++){
threads[i].start();
}
boolean finish = false;
while(!finish){
for (int i = 0; i < 10; i++) {
if(threads[i].getState()!=status[i]){
writeThreadInfo(pw,threads[i],status[i]);
}
}
finish = true;
}
pw.close();
}catch(Exception e){
}
}
private static void writeThreadInfo(PrintWriter pw, Thread thread, State state) {
pw.printf("Main : %s\n",thread.getName());
pw.printf("Main : Priority: %d\n",thread.getPriority());
pw.printf("Main : Old State: %s\n",state);
pw.printf("Main : New State: %s\n",thread.getState());
pw.printf("Main : ************************************\n");
}
}

线程的中断

​ 一个多个线程在执行的Java程序,只有当其全部的线程执行结束时(更具体的说,是所有非守护线程结束或者某个线程调用System.exit()方法的时候),它才会结束运行。有时,你需要为了终止程序而结束一个线程,或者当程序的用户想要取消某个Thread对象正在做的任务。
​ Java提供中断机制来通知线程表明我们想要结束它。中断机制的特性是线程需要检查是否被中断,而且还可以决定是否相应结束的请求。所以,线程可以忽略中断请求并且继续运行。
​ Thread类还有其他的可以检查线程是否被中断的方法。例如,静态方法interrupted()能检查正在运行的线程是否被中断。isInterrupted()和interrupted()方法有着很重要的区别。第一个测试线程是否被中断,第二个是用来中断线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Clocker extends Thread {
@Override
public void run() {
// TODO Auto-generated method stub
long number =1L;
while(true){
System.out.println("clock is "+number+" s");
if(isInterrupted()){
System.out.printf("The Clocker has been Interrupted");
return;
}
number ++;
}
}
public static void main(String[] args) {
Thread task = new Clocker();
task.start();
try {
sleep(5000);
} catch (Exception e) {
// TODO: handle exception
}
task.interrupt();
}
}

操作线程的中断机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.io.File;
public class FileSearch implements Runnable {
private String initPath;
private String fileName;
public FileSearch(String initPath, String fileName) {
this.initPath = initPath;
this.fileName = fileName;
}
@Override
public void run() {
// TODO Auto-generated method stub
File file = new File(initPath);
if (file.isDirectory()) {
try {
directoryProcess(file);
} catch (InterruptedException e) {
System.out.printf("%s: The search has been interrupted",Thread.currentThread().getName());
}
}
}
private void directoryProcess(File file) throws InterruptedException {
File list[] = file.listFiles();
if (list != null) {
for (int i = 0; i < list.length; i++) {
if (list[i].isDirectory()) {
directoryProcess(list[i]);
} else {
fileProcess(list[i]);
}
}
}
if (Thread.interrupted()) {
throw new InterruptedException();
}
}
private void fileProcess(File file) throws InterruptedException
{
if (file.getName().equals(fileName)) {
System.out.printf("%s : %s\n",Thread.currentThread().getName() ,file.getAbsolutePath());
}
if (Thread.interrupted()) {
throw new InterruptedException();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) {
FileSearch searcher=new FileSearch("D:\\","log.txt");
Thread thread=new Thread(searcher);
thread.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class FileClock implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 10; i++) {
System.out.printf("%s\n", new Date());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.printf("The FileClock has been interrupted");
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) {
FileClock clock = new FileClock();
Thread thread = new Thread(clock);
thread.start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
thread.interrupt();
}
}

线程的暂停,恢复和停止

线程的暂停、恢复和停止操作为suspend()、resume()和stop()。这些API是过期的,也就是不建议使用的。

线程的睡眠

​ Thread类的sleep方法或者使用TimeUnit列举元素的sleep方法。Thread离开CPU并在一段时间内停止运行。在这段时间内,它是不消耗CPU时间的,但它并不释放对象锁。例如有两个线程同时执行(没有synchronized)一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完毕后,低优先级的线程才能够执行;但是高优先级的线程sleep(500)后,低优先级就有机会执行了。如果有synchronized,低优先级的线程也不会执行。

​ Thread类使用yield()方法,它向JVM表示线程对象可以让CPU执行其他任务。JVM不保证遵守请求,通常它只是用来调试。只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。

等待线程的终结

我们可能会遇到程序在执行前需要初始化资源。在执行剩下的代码之前,我们需要等待线程完成初始化任务。为了达到此目的,我们使用Thread类的join()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class DataSourcesLoader implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.printf("Beginning data sources loading : %s\n", new Date());
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Data sources loading has finished :%s\n",new Date());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class NetworkConnectionsLoader implements Runnable {
private Thread thread;
public NetworkConnectionsLoader(Thread thread){
this.thread = thread;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.printf("Beginning Net sources loading : %s\n", new Date());
try {
thread.start();
thread.join();
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Net sources loading has finished :%s\n",new Date());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.Date;
public class Main {
public static void main(String[] args) {
DataSourcesLoader dsLoader = new DataSourcesLoader();
Thread thread1 = new Thread(dsLoader,"DataSourcesLoader");
NetworkConnectionsLoader ncLoader = new NetworkConnectionsLoader(thread1);
Thread thread2 = new Thread(ncLoader,"NetworkConnectionsLoader");
thread2.start();
try {
thread2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.printf("Main: Configuration has been loaded: %s\n",new Date());
}
}
Luckylau wechat
如果对您有价值,看官可以打赏的!