一个进程可以包含多个线程,但至少要有一个线程。

多进程模式:每个进程只有一个线程

多线程模式:一个进程有多个线程

优缺点:

  • 进程比线程开销大、通信慢
  • 多进程比多线程稳定性高

一个java程序就是一个JVM进程,JVM进程用一个主线程来执行main()方法,在main()方法内部又可以启动多个线程。多线程需要同步读取共享数据。

方法一:从Thread派生一个子类,并覆写run()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.example;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.LinkedList;
import java.util.List;

public class Main {
public static void main(String[] args) throws IOException {
Thread t1 = new Thread();
t1.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello World");
}
}

方法二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.example;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.LinkedList;
import java.util.List;

public class Main {
public static void main(String[] args) throws IOException {
Thread t1 = new Thread(new MyRunnable());
ti.start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Hello World");
}
}

方法三:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.example;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.LinkedList;
import java.util.List;

public class Main {
public static void main(String[] args) throws IOException {
Thread t1 = new Thread(()->{
System.out.println("hello world");
});
t1.start();
}
}

执行顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example;
import java.io.*;
import java.nio.file.*;


public class Main {
public static void main(String[] args) throws IOException {
System.out.println("main run...");
Thread t1 = new Thread(){
public void run(){
System.out.println("thread run...");
System.out.println("thread end...");
}
};
t1.start();
System.out.println("main end...");
}
}

main线程执行的代码有4行,首先会打印main run...,然后创建Thread对象,接着调用start()启动新线程。所以mian end...与新线程是同时执行的,程序本身无法确定线程的调度顺序。

可以使用Thread.sleep()强制当前线程暂停来控制哪个线程线执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.example;
import java.io.*;
import java.nio.file.*;


public class Main {
public static void main(String[] args) throws IOException {
System.out.println("main run...");
Thread t1 = new Thread(){
public void run(){
System.out.println("thread run...");
try{Thread.sleep(10);}catch(Exception e){}
System.out.println("thread end...");
}
};
t1.start();
try{Thread.sleep(20);}catch(Exception e){}
System.out.println("main end...");
}
}

注意:

  • 在mian线程内直接调用run()方法不会新启动线程,必须调用Thread实例的start()方法才能启动新线程。
  • 一个线程对象只能调用一次start()方法。

也可以通过Thread.setPriority(int n)来设置线程的优先级(1~10,默认值是5),操作系统对高优先级线程可能调度更频繁,但不能保证一定会先执行。

线程状态

  • New:新创建的线程,尚未执行;
  • Runnable:运行中的线程,正在执行run()方法的Java代码;
  • Blocked:运行中的线程,因为某些操作被阻塞而挂起;
  • Waiting:运行中的线程,因为某些操作在等待中;
  • Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待;
  • Terminated:线程已终止,因为run()方法执行完毕。

线程终止的原因:

  • 线程正常终止:run()方法执行到语句返回;
  • 线程意外终止:run()因为未捕获的异常导致线程终止;
  • 对某个线程的Thread实例调用stop()方法强制终止。

使用join()join(long)可以让主线程等待子线程结束后再运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.example;
import java.io.*;
import java.nio.file.*;


public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
Thread t=new Thread(()->{
System.out.println("hello");
});
System.out.println("start");
t.start();
t.join();
System.out.println("end");
}
}

中断线程

Thread.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
package org.example;
import java.io.*;
import java.nio.file.*;


public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
Thread t=new MyThread();
t.start();
t.sleep(10);
t.interrupt();
t.join();
System.out.println("end");
}
}
class MyThread extends Thread {
@Override
public void run() {
int i=0;
while(!isInterrupted()) {
i++;
System.out.println(i+"hello~");
}
}
}

interrupt方法只是修改线程中的中断标记位为true,并不会执行中断。

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
package org.example;
import java.io.*;
import java.nio.file.*;


public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
Thread t=new MyThread();
t.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
int i=0,x=10;
while(i<15) {
if(i==x) {
System.out.println("线程要被中断了:"+i);
this.interrupt();
}
i++;
System.out.println("当前运行数字为:"+i);
}
}
}

当一个线程因调用Object.wait()Thread.sleep()Thread.join()而进入等待/定时等待状态时,若其他线程调用了该线程的interrupt()方法,则该线程会:

  1. 立即从等待方法中抛出******InterruptedException**(这是一个受检异常,必须被捕获或声明)。
  2. 中断状态标志被自动清除isInterrupted()会返回false)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.example;
import java.io.*;
import java.nio.file.*;


public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
Thread t=new Thread(()->{
try {
Thread.sleep(5000);
System.out.println("已过去");
} catch (InterruptedException e) {
System.out.println("中断被打断");
}
});
t.start();
Thread.sleep(1000);
t.interrupt();
}
}

也可以用volatile关键字来表示线程间共享变量,确保每个线程都能读取到更新后的变量值。

在JVM中,变量的值保存在主内存中,当线程访问变量时,会先获得一个副本并保存在自己的工作内存中。如果线程修改了变量的值,JVM会在某个时刻把修改后的值回写到主内存中,但时间是不确定的。

因此,volatile关键字的作用是:

  • 每次访问变量时,总是获取主内存的最新值;
  • 每次修改变量后,立刻回写到主内存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.example;
import java.io.*;
import java.nio.file.*;


public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
MyThread t=new MyThread();
t.start();
Thread.sleep(1000);
t.running=false;
}
}
class MyThread extends Thread {
public volatile boolean running=true;
public void run() {
int i=0;
while (running) {
i++;
System.out.println(i);
}
}
}