Java多线程

前言

在我们学习过程中,对于多线程编程时刻都存在。而要掌握多线程的原理、注意事项,以及它的API才能更好更游刃有余的控制或者说编写更高质量的代码。
本文通过学习高洪岩-《Java多线程编程核心技术》来学习和理解Java多线程技术。

进程和线程

进程和线程是什么关系,他们有什么区别?相信这个问题大多数人曾经面试被问及到。那他们到底是什么关系,有什么区别呢?

进程:进程是操作系统中运行的每一个应用程序。

线程:线程是运行在进程中的,它是CUP调度的最小单位。

多线程:多线程是指在同一进程中有多个线程执行任务。

在单任务运行环境下,任务执行是串行执行,一个任务执行完了才能执行另一个任务。所以他的执行时间是累加的。

在多任务运行环境下,任务执行是并行执行,CPU可以在任务之间进行随机切换去执行,使得运行效率大大提升。所以多线程就是在使用异步。

多线程使用
  • 使用多线程
  1. 继承Thread类,重写run()方法
  2. 实现Runnable接口,重写run()方法
  • 停止线程
  1. 使用退出标志,使线程正常退出,即当run()方法执行完后线程终止。
  2. 使用Thread.stop()方法,但是不推荐,该方法已经废弃,使用后可能产生不可预料的结果。
  3. 使用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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class T1 extends Thread{
@Override
public void run() {
super.run();
try {
for (int i = 0; i < 500000; i++) {
if (this.isInterrupted()) {
System.out.println("This thead is interrupted, Process will exit...");
// 通过抛出异常来进行线程的终止
throw new InterruptedException();
}
System.out.println("i-" + (i + 1));
}
System.out.println("This thread is not interrupted...");
} catch (InterruptedException e) {
System.out.println("InterruptedException running()...");
}
}
}
public class Test1{
public static void main(String[] args){
try{
T1 t1 = new T1();
t1.start();
Thread.sleep(2000);
t1.interrupt();
}catch(InterruptedException e){
System.out.prinlin("main catch");
}
System.out.println("end");
}
}
控制台输出:
...
i-31722
i-31723
i-31724
i-31725
i-31726
end
This thead is interrupted, Process will exit...
InterruptedException running()...

可以看到通过throw new InterruptedException(),解决了线程终止的问题,”This thread is not 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
26
27
28
29
30
31
32
33
34
35
36
37
public class T2 extends Thread {
private int i;
@Override
public void run() {
super.run();
try {
for (;;) {
i++;
System.out.println("i=" + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test2{
public static void main(String[] args){
try{
T2 t2 = new T2();
t2.start();
Thread.sleep(5000);
t2.stop();
}catach(InterruptedException e){
e.printStackTrace();
}
}
}
控制台输出:
i=1
i=2
i=3
i=4
i=5

虽然由控制台看起来stop()方法处理线程的终止很完美,但是它作为废弃的方法,如果强制使线程停止,会造成一些清理工作得不到处理。对锁定的对象进行了锁的释放,导致数据不能同步处理而出现数据不一致问题。

  • 判断线程是否停止
  1. Thread.interrupted():该方法用于测试当前线程是否中断(线程中断状态会由该方法清除)
  2. thread.isInterrupted():该方法用于测试当前线程是否中断

所以当对同一个线程进行了如下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test{
public static void main(String[] args){
MyThread thread = new MyThread();
thread.setName("thread1");
thread.start();
// 中断线程
thread.interrupt();
// 判断是否中断
boolean i1 = thread.interrupted();
// boolean i1 = thread.isInterrupted();
System.out.println("the current thread:" + thread.getName() + "is interrupted: " + i1);
// 判断是否中断
boolean i2 = thread.interrupted();
// boolean i2 = thread.isInterrupted();
System.out.println("the current thread:" + thread.getName() + "is interrupted: " + i2);
}
}
执行结果:
the current thread:thread1 is interrupted: false;
the current thread:thread1 is interrupted: false;

JDK文档对Thread.interrupted()方法有很明确的解释

测试当前线程是否已经中断。线程的 中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false 。
线程中断被忽略,因为在中断时不处于活动状态的线程将由此返回 false 的方法反映出来。
如果当前线程已经中断,则返回 true;否则返回 false。

1
2
3
4
5
public static boolean interrupted() {
// 使用当前线程的isInterrupted(true)中断并清除中断状态
return currentThread().isInterrupted(true);
}
private native boolean isInterrupted(boolean ClearInterrupted);
  • 暂停线程(suspend()&resume())

暂停意味着此线程可以恢复运行。在Java多线程中,可以使用suspend()方法暂停线程,使用rsume()方法恢复线程的执行。

1
2
  • 线程的优先级(setPriority())

线程的优先级通过thread.setPriority()来指定,它的范围是[1-10],若不在该范围内则抛出IllegalArgumentException()异常。

  1. 线程优先级具有继承性(线程A启动了线程B,则B的优先级与A相同)

  2. 线程优先级具有规则性(高优先级的线程总是大部分先执行完,并非全部执行完)

  3. 线程优先级具有随机性(高优先级的线程并不一定都会先执行完)

  • 守护线程(setDaemon)

Java中有两种线程,分别是:用户线程,另一种是守护线程。

守护线程是一种特殊的线程,它的特性有陪伴的含义,当进程中不存在”非守护线程”,则守护线程自动销毁,典型的守护线程就是垃圾回收线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ThreadDaemonDemo extends Thread {
private int i = 0;
@Override
public void run() {
while (true) {
i++;
System.out.println("i = " + i);
}
}
}
public static void main(String[] args) {
try {
ThreadDaemonDemo thread = new ThreadDaemonDemo();
// 将线程设置为守护线程
thread.setDaemon(true);
thread.start();
// 主线程睡眠2s后守护线程发现除了主线程,再无非守护线程允许,则退出...
Thread.sleep(2000);
System.out.println("我离开Thread,也不再打印了,停止了...");
} catch (InterruptedException e) {
}
对象及变量的并发访问

“非线程安全”问题在于“实例变量”中。如果是方法内部的私有变量则不存在“非线程安全“问题。

多个线程共同访问1个对象中的实例变量,运转结果可能出现交叉情况。

  • synchronized同步方法

synchronized可修饰方法起到同步方法的作用。而使用关键字synchronized一定是排队运行的。

  • synchronized同步语句块

  • volatile关键字

线程间通信
  • 等待/通知机制

  • 方法join()的使用

  • 类ThreadLocal的使用

  • 类InheritableThreadLocal的使用

Lock的使用
  • 使用ReentrantLock类

  • 使用ReentrantReadWriteLock类

定时器Timer
  • 定时器Timer的使用
单例模式与多线程
如果帮到了你,想打赏支持,喏~