第一章:Java多线程技能

74 阅读7分钟

第一章 Java多线程技能

1.1 进程和线程的定义以及多线程的优缺点

  • 进程:可以理解为一个运行的java程序就是一个进程。
  • 线程:进程中独立运行的一个子任务
线程和进程的总结
  1. 进程虽然是相互独立的,但是他们可以相互通信,较为通用的方法就是socket或者Http协议
  2. 进程拥有共享的系统资源,比如内存、网络端口,供其内部线程使用
  3. 进程比较中,因为创建进程需要操作系统分配资源,会占用内存
  4. 线程存在于进程,是进程的一个子集,先有进程,后有线程
  5. 虽然线程更轻,但是线程上下文切换时间成本非常高
使用多线程的场景
  • 阻塞:一旦系统中出现乐阻塞的现象,可以根据实际情况来使用多线程来提高效率
  • 依赖:业务分为两个执行过程,分别是A和B,当A业务有阻塞的情况发生,B业务不依赖A业务的执行结果这时就可以使用多线程来提高运行效率;如果B业务依赖A业务的执行结果,则不需要使用多线程技术,按照顺序串行即可
tips:

多线程是异步的,所以千万不要把IDE里代码的顺序当作线程执行的顺序,线程被调用的时机是随机的

1.2 使用多线程

1.2.1 继承Thread类
  • 继承Thread类
    • Thread类实现了Runnable接口
    • Java语言中只允许单根继承,所以实现Runnable是更好的一种方式
  • 实现Runnable
Thread类继承

image.png

/**
 * @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("执行结束");
    }
}

image.png

  • 方法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方法)。 一个线程启动一次以上是不合法的。特别是,线程一旦完成执行就不能重新启动。

image.png

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); 设置线程为守护线程。
  • 当进程中不存在非守护线程时,守护线程自动销毁。典型的守护线程就是垃圾回收线程。