第一章 Java多线程技能
1.1 进程和线程的定义以及多线程的优缺点
- 进程:可以理解为一个运行的java程序就是一个进程。
- 线程:进程中独立运行的一个子任务
线程和进程的总结
- 进程虽然是相互独立的,但是他们可以相互通信,较为通用的方法就是socket或者Http协议
- 进程拥有共享的系统资源,比如内存、网络端口,供其内部线程使用
- 进程比较中,因为创建进程需要操作系统分配资源,会占用内存
- 线程存在于进程,是进程的一个子集,先有进程,后有线程
- 虽然线程更轻,但是线程上下文切换时间成本非常高
使用多线程的场景
- 阻塞:一旦系统中出现乐阻塞的现象,可以根据实际情况来使用多线程来提高效率
- 依赖:业务分为两个执行过程,分别是A和B,当A业务有阻塞的情况发生,B业务不依赖A业务的执行结果这时就可以使用多线程来提高运行效率;如果B业务依赖A业务的执行结果,则不需要使用多线程技术,按照顺序串行即可
tips:
多线程是异步的,所以千万不要把IDE里代码的顺序当作线程执行的顺序,线程被调用的时机是随机的
1.2 使用多线程
1.2.1 继承Thread类
- 继承Thread类
- Thread类实现了Runnable接口
- Java语言中只允许单根继承,所以实现Runnable是更好的一种方式
- 实现Runnable
Thread类继承
/**
* @author linyuan
* @since 2022/9/5
*/
public class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("myThread");
}
}
/**
* @author linyuan
* @since 2022/9/5
*/
public class MyThreadMain {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
System.out.println("执行结束");
}
}
- 方法start耗时时间长的原因:
- 通过JVM告诉操作系统创建Thread
- 操作系统开辟内存并使用Windows SDK中的createThread()函数创建Thread线程对线
- 操作系统队Thread对象进行调度,以确定执行时机
- Thread在操作系统中被成功的执行
1.2.2 使用常见的三个工具类分析线程的信息,存在于jdk/bin文件夹中
- jsp+jstack.exe
- jmc.exe
- jvisualvm.exe
1.2.3 执行start方法的先后并不是线程run执行的顺序
/**
* @author linyuan
* @since 2022/9/5
*/
public class MyThreadMain {
public static void main(String[] args) {
MyThread thread = new MyThread();
MyThread1 thread1 = new MyThread1();
MyThread2 thread2 = new MyThread2();
thread.start();
thread1.start();
thread2.start();
System.out.println("执行结束");
}
}
执行结束
myThread2
myThread1
myThread
1.2.4 实现Runnable接口
/**
* @author linyuan
* @since 2022/9/5
*/
public class MyThread3 implements Runnable {
@Override
public void run() {
System.out.println("myThread implements Runnable");
}
}
/**
* @author linyuan
* @since 2022/9/5
*/
public class MyThreadMain {
public static void main(String[] args) {
Runnable runnable = new MyThread3();
Thread thread = new Thread(runnable);
thread.start();
System.out.println("执行结束");
}
}
1.2.5 实例变量导致的非线程安全问题
- 主要的导致原因就是多线程的共享变量导致。
/**
* @author linyuan
* @since 2022/9/5
*/
public class MyThread4 extends Thread {
private int count = 5;
@Override
public void run() {
count--;
System.out.println("由" + currentThread().getName() + "计算。 count = " + count);
}
}
/**
* @author linyuan
* @since 2022/9/5
*/
public class MyThreadMain {
public static void main(String[] args) {
MyThread4 myThread4 = new MyThread4();
Thread thread1 = new Thread(myThread4, "1");
Thread thread2 = new Thread(myThread4, "2");
Thread thread3 = new Thread(myThread4, "3");
thread1.start();
thread2.start();
thread3.start();
System.out.println("执行结束");
}
}
执行结束
由3计算。 count = 2
由2计算。 count = 3
由1计算。 count = 3
1.2.6 方法run()被jvm所调用
- start方法的注释:
使线程开始执行;Java虚拟机调用这个线程的run方法。 结果是两个线程并行运行:当前线程(从调用start方法返回)和另一个线程(执行其run方法)。 一个线程启动一次以上是不合法的。特别是,线程一旦完成执行就不能重新启动。
1.3 currentThread方法
- 可以返回方法正在被哪个线程调用
1.4 isAlive方法
- 判断线程对象是否存活
/**
* @author linyuan
* @since 2022/9/5
*/
public class MyThreadMain {
public static void main(String[] args) {
MyThread4 myThread4 = new MyThread4();
System.out.println("begin:" + myThread4.isAlive());
myThread4.start();
System.out.println("end:" + myThread4.isAlive());
System.out.println("执行结束");
}
}
begin:false
end:true
执行结束
- 方法isAlive是测试线程是否处于活动状态,线程启动且未停止算为活动状态。
1.5 sleep方法
- sleep()方法的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)
- 如果调用sleep()方法所在的类是Thread.java,是可以使用this.sleep(3000)的。如果不是在Thread.java这个类中,则必须使用Thread.sleep(3000)。因为类中没有提供sleep()方法。
1.6 sleep(long millis, int nanos)方法
- 让当前正在执行的线程在指定的毫秒数加指定的纳秒数(暂停执行),此方法收到系统计时器及调度程序精度及准确性的影响
1.7 方法StackTraceElement[] getStackTrace()
- 返回一个表示该线程的堆栈跟踪元素数组,如果该线程尚未启动或者已经终止,则该方法将返回一个零长度的数组。如果返回的数组不是零长度的,则第一个元素代表堆栈顶,他是数组中最新的方法调用,最后一个元素代表堆栈底,是该数组中最旧的方法调用
/**
* @author linyuan
* @since 2022/9/6
*/
public class Test1 {
public void a() {
b();
}
public void b() {
c();
}
public void c() {
d();
}
public void d() {
e();
}
public void e() {
StackTraceElement[] stackTraceElement = Thread.currentThread().getStackTrace();
Arrays.stream(stackTraceElement).forEach(s -> {
System.out.println(s.getClassName() + ":" + s.getMethodName() + ":" + s.getFileName() + ":" + s.getLineNumber());
});
}
public static void main(String[] args) {
new Test1().a();
}
}
java.lang.Thread:getStackTrace:Thread.java:1559
com.example.multithread.lab1.Test1:e:Test1.java:28
com.example.multithread.lab1.Test1:d:Test1.java:24
com.example.multithread.lab1.Test1:c:Test1.java:20
com.example.multithread.lab1.Test1:b:Test1.java:16
com.example.multithread.lab1.Test1:a:Test1.java:12
com.example.multithread.lab1.Test1:main:Test1.java:35
1.8 方法static void dumpStack()
- 将当前线程的堆栈信息输出值标准错误流,仅用于调试
/**
* @author linyuan
* @since 2022/9/6
*/
public class Test1 {
public void a() {
b();
}
public void b() {
c();
}
public void c() {
d();
}
public void d() {
e();
}
public void e() {
Thread.dumpStack();
}
public static void main(String[] args) {
new Test1().a();
}
}
java.lang.Exception: Stack trace
at java.lang.Thread.dumpStack(Thread.java:1336)
at com.example.multithread.lab1.Test1.e(Test1.java:28)
at com.example.multithread.lab1.Test1.d(Test1.java:24)
at com.example.multithread.lab1.Test1.c(Test1.java:20)
at com.example.multithread.lab1.Test1.b(Test1.java:16)
at com.example.multithread.lab1.Test1.a(Test1.java:12)
at com.example.multithread.lab1.Test1.main(Test1.java:32)
1.9 方法Map<Thread, StackTranElement[]> getAllStackTraces()
- 返回所有活动线程的堆栈信息的一个映射,Map的key是线程对象。
- getLineNumber方法返回负数,比如-2代表没有具体的行号信息,大多数是应为调用了native方法才会返回-2。
1.10 getId() 方法
- 获取线程的唯一标识
1.11 停止线程
- 使用线程标志位使线程正常退出
- 使用stop方法强行终止线程,但是不推荐,因为stop和suspend和resume方法一样,都是作废过期的方法
- 使用interrupt方法中断线程
interrupt并不是马上停止线程,而是仅仅在当前线程中打了一个停止的标记,并不是真正的停止线程。interrupted()和isInterrupted()方法用来判断线程是否已经中断。其中interrupted()方法检测并且清除线程的中断状态,也就是对于中断状态的线程,第一次调用时为true,第二次调用则是false。isInterrupted()方法的,不会清除线程的中断状态。
/**
* @author linyuan
* @since 2022/9/6
*/
public class Test2 {
public static void main(String[] args) throws InterruptedException {
MyThread1 myThread1 = new MyThread1();
myThread1.interrupt();
System.out.println(myThread1.isInterrupted());
myThread1.start();
TimeUnit.SECONDS.sleep(1);
}
}
/**
* @author linyuan
* @since 2022/9/5
*/
public class MyThread1 extends Thread {
@Override
public void run() {
super.run();
this.interrupt();
System.out.println(Thread.interrupted());
System.out.println(Thread.interrupted());
}
}
false
true
false
第一次输出为false,线程还未启动
第二次输出为true,线程已经中断状态了
第三次输出为false,interrputed将中断状态清除了。
1.11.2 停止线程
- 判断线程的中断状态,抛出异常来终止线程
- 使用return来停止线程
1.11.3 sleep状态下停止线程
- 在线程sleep状态下停止线程,会抛出异常
Exception in thread "Thread-0" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted
at com.example.multithread.lab1.MyThread1.run(MyThread1.java:17)
Caused by: java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.example.multithread.lab1.MyThread1.run(MyThread1.java:15)
1.12 yield()
- 放弃当前的CPU资源,让其他方法占用CPU执行时间,放弃时间不确定。
1.13 线程的优先级
- 线程优先级具有继承性
- 优先级规律
- 高优先级的线程总是大部嗯先执行完,但并不代表高优先级的线程全部先执行完。
- 线程优先级的随机性
1.14 守护线程
- setDeamon(true); 设置线程为守护线程。
- 当进程中不存在非守护线程时,守护线程自动销毁。典型的守护线程就是垃圾回收线程。