多线程的创建
1.继承Thread类,重写run方法
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start(); // start()会调用操作系统的api创建线程,以run()作为入口方法执行。
while (true) {
System.out.println("Hello World");
}
}
}
class MyThread extends Thread {
@Override
public void run() { // 该方法会在合适的时机调用
while (true) {
System.out.println("Hello Thread");
}
}
}
此时创造了多线程,Hello Thread和Hello World会同时循环打印。
每个线程都是独立的线程流,都能执行独立的逻辑。
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.run(); // 将start()改成了run()
while (true) {
System.out.println("Hello World");
}
}
}
class MyThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("Hello Thread");
}
}
}
将start()改成run()实际上只有一个main线程。此时由于run()先被调用,因此只会循环打印Hello Thread而不会打印Hello World。
2.实现runnable接口,重写run方法
public class Test {
public static void main(String[] args) throws InterruptedException {
MyRunnable myRunnable = new MyRunnable(); // Runnable没用start()方法,只能与Thread配合使用
Thread t = new Thread(myRunnable);
t.start();
while (true) {
System.out.println("Hello Thread");
Thread.sleep(1000);
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("Hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
- 第一种写法,把线程和线程要执行的任务耦合在一起了
- 第二种写法,Runnable表示要执行的任务,Thread表示线程。将两者解耦合了
3.使用匿名内部类,重写run方法
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread() { // 此处的t是Thread子类的实例
// 此处定义新的属性和方法
@Override
public void run() {
while (true) {
System.out.println("Hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
t.start();
while (true) {
System.out.println("Hello main");
Thread.sleep(1000);
}
}
}
- 只需要使用一次
- 代码逻辑简单
4.使用匿名内部类,基于Runnable
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("Hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
t.start();
while (true) {
System.out.println("Hello main");
Thread.sleep(1000);
}
}
}
5.基于lambda表达式(匿名内部类)
public class Test {
public static void main(String[] args) throws InterruptedException throws InterruptedException {
Thread t = new Thread(() -> {
while (true) {
System.out.println("Hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
while (true) {
System.out.println("Hello main");
Thread.sleep(1000);
}
}
}
Thread的构造方法
| 方法 | 说明 |
|---|---|
| Thread() | 创建线程对象 |
| Thread(Runnable target) | 使用Runnable对象创建线程对象 |
| Thread(String name) | 创建线程对象,并命名 |
| Thread(Runnable target,String name) | 使用Runnable对象创建线程对象,并命名 |
Thread的几个常见属性
此处的ID是Jvm分配的,和系统分配的ID是不同内容。Java中无法看到操作系统分配的线程ID。
| 属性 | 获取方法 |
|---|---|
| ID | getId() |
| 名称 | getName() |
| 状态 | getState() |
| 优先级 | getPriority() |
| 是否后台进程 | isDaemon() |
| 是否存活 | isAlive() |
| 是否被中断 | isInterrupted() |
1.isDaemon()
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread( () -> {
while (true) {
System.out.println("Hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "这是一个线程");
t.setDaemon(true); // true -> 成为后台线程
// 此时只有一个前台线程,main线程结束了,进程就结束了
t.start();
}
}
2.isAlive()
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread( () -> {
for (int i = 0; i < 3; i++) {
System.out.println("Hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
System.out.println(t.isAlive()); // false
t.start();
Thread.sleep(1000);
System.out.println(t.isAlive()); // true
Thread.sleep(3000);
System.out.println(t.isAlive()); // false
}
}
线程的启动
针对一个Thread对象,只能调用一次start!
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread( () -> {
System.out.println("Hello Thread");
});
t.start();
System.out.println("线程第一次启动");
t.start();
System.out.println("线程第二次启动");
}
}
// 线程第一次启动
// 线程第二次启动
// Hello Thread
// Exception in thread "main" java.lang.IllegalThreadStateException
// at java.base/java.lang.Thread.start(Thread.java:802)
// at Test.main(Test.java:13)
线程的终止
Java中没有“强制终止”操作。让一个线程结束的核心方式,是让线程的入口方法能够执行完。
1.使用标志位
import java.util.Scanner;
public class Test {
private static boolean running = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread( () -> {
while (running) {
System.out.println("Hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("end");
});
t.start();
Scanner scanner = new Scanner(System.in); // 当用户输入后,线程终止
System.out.println("输入任意内容,让t线程终止");
scanner.next();
running = false;
}
}
- 将变量写为成员变量,本质是lambda作为匿名内部类访问外部类。如果将成员变量改为局部变量,要保证变量
running不被修改。 - 如果设置
Thread.sleep(10000);,此时结束操作不能及时处理。
2.使用interrupt方法
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Thread t = new Thread( () -> {
while (Thread.currentThread().isInterrupted()) {
System.out.println("Hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("线程 t 终止");
});
t.start();
Scanner sc = new Scanner(System.in);
sc.next();
t.interrupt();
}
}
// Hello Thread
// Hello Thread
// ...
// 1
// Exception in thread "Thread-0" java.lang.RuntimeException: // java.lang.InterruptedException: sleep interrupted
// at Test.lambda$main$0(Test.java:11)
// at java.base/java.lang.Thread.run(Thread.java:842)
// Caused by: java.lang.InterruptedException: sleep interrupted
// at java.base/java.lang.Thread.sleep(Native Method)
// at Test.lambda$main$0(Test.java:9)
// ... 1 more
//
// Process finished with exit code 0
t.interrupt()代表终止线程操作,内置了标志位Thread.currentThread().isInterrupted()中,isInterrupted()方法可以获取到当前线程是否被终止,Thread.currentThread()指代当前线程的引用- try-catch中的
throw new RuntimeException(e);是指当t线程处于阻塞状态时,有interrupt方法终止线程,会导致t线程触发InterruptedException异常 - 由于
Thread.sleep()占据大部分时间,因此几乎不会输出"线程 t 终止"这个结果 - 实际开发中,catch中可以进行以下操作:进行重试、记录错误日志、触发监控报警
线程的等待
| 方法 | 说明 |
|---|---|
| public void join() | 等待线程结束 |
| public void join(long millis) | 等待线程结束,最多millis毫秒 |
| public void join(long millis, int nanos) | 同上,提高精度 |
如果需要A线程工作完后的结果交回主线程进行结果计算,需要主线程等待A线程的结束。因此需要主动控制线程的结束,即主线程等待A线程的结束。
public class Test {
private static int result = 0;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread( () -> {
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += i;
}
result = sum;
});
t.start();
t.join(); // t线程是被等待的对象
System.out.println(result);
}
}
例如计算1+2+3+...+1000的结果返回给主线程,由于System.out.println(result);这段代码执行的更快,如果不控制线程结束时间,结果会打印0。因此,调用t.join();方法,当t线程已经结束,则t.join()方法不会执行;如果t线程还在执行,则t.join()会阻塞主线程等待,直到t线程结束。不过,任何一个线程都可以被等待:
public class Test {
private static int result = 0;
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
Thread t = new Thread( () -> {
try {
mainThread.join();
System.out.println(result);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t.start();
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += i;
}
result = sum;
}
}
线程的状态
通过t.getState()方法获取
NEW:Thread对象创建了,还未启动
RUNNABLE:就绪状态,正在被cpu执行或随时可以调度到cpu执行
TERMINSTED:线程的入口方法执行完了,系统中的线程已经销毁
WAITING:由join()引起的无固定期限的阻塞
TIMED_WAITING:由于sleep()或join()引起的有固定期限阻塞
BLOCKED:由于锁竞争引起的阻塞
线程安全
代码在单线程或多线程上都没有问题,叫做线程安全。
public class Demo1 {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread( () -> {
for (int i = 0; i < 100000; i++) {
count++;
}
});
Thread t2 = new Thread( () -> {
for (int i = 0; i < 100000; i++) {
count++;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}
// 154448 (不符合预期,且每次结果都不同)
原因:
- 线程调度是随机的,一个线程执行任何一个指令都可能被cpu调度走
- 多个线程同时操作一个变量
- 针对变量的修改操作不是“原子”的
- 内存可见性引起的线程安全问题
- 指令重排序引起的线程安全问题
解决方法:通过锁,让一个变量只能被一个线程修改。
synchronized (锁对象) { (-> 加锁)
// 需要操作的变量
} (-> 解锁)
- 锁对象可以是任意对象,可以是Object, String, 甚至是ArrayList
- 一般初始化锁对象的方法
Object locker = new Object(); - 当一个线程对一个锁对象加锁时,另一个线程继续对同一个锁对象加锁时,会发生锁竞争,此时另一个线程会产生阻塞等待,即
BLOCKED状态,直到锁对象被解锁。
public class Demo1 {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread( () -> {
for (int i = 0; i < 10000; i++) {
synchronized (locker) {
count++;
}
}
});
Thread t2 = new Thread( () -> {
for (int i = 0; i < 10000; i++) {
synchronized (locker) {
count++;
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}

参与讨论
(Participate in the discussion)
参与讨论