Java多线程编程核心技术-多线程基础使用

本文详细介绍了Java多线程的基础知识和技术要点,包括线程的创建、启动过程及执行顺序的随机性。并通过实例演示了如何通过继承Thread类创建线程,以及分析了线程状态和常见线程分析命令。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

导语
  想要学习一个新的技术就必须要无限的接近它,深入的了解它,了解一个东西的步骤就是由浅入深的去深入的了解它。下面这个专题博主会带着大家共同学习Java多线程的核心编程技术,从入门到深入,也欢迎大家能够加入面试群聊,来分享自己的面试学习心得。
Java面试问题交流群

使用多线程

  在一个进程运行的过程中至少存在一个线程,这些线程在后台默默的执行,支撑着整个进程的运行。例如在Java 中调用public static void main方法就可以创建一个主线程,这线程是由JVM创建。

public class Test{
	public static void main(String[] args){
		System.out.println(Thread.currentThread().getName());
	}
}

在这里插入图片描述
  会看到在控制台输出了一个main,其实这个main就是我们获取到的当前执行线程的线程名。而这个线程名与main方法并没有直接关系。

通过继承Thread类创建线程

  Java提供了很多的对于多线程技术的支持包,通过这些包提供的方法可以快速的创建线程。但是在实际开发中我们常用的两种方式:一种是继承Thread类,一种是实现Runnable接口。当然还有其他两种方式一种是继承Callable接口,一种是使用线程池进行创建。在一般的开发中都很少用到后两种方式,所以这里,先来简单的介绍一下前两种方式,后续的分享中也会讲到后两种实现方式,有兴趣的的读者可以尝试学习一下后两种方式。

public class Thread implements Runnable

  首先呢,可以看到Thread类也是继承了Runnable接口,而他们之间就是为了维持一个多态的关系,这也是Java语言的三大特性之一。通过继承Thread类的方式来创建新的线程的时候,在很多程度上是限制了多继承的。我们都知道,Java语言是单继承的,也就是说一个类只能由一个父类。但是可以继承多个接口。所以从实质上来讲,无论是Thread的方式还是Runnable的方式,本质上是没有太大的区别的。

  下面看一个小例子,我们来创建一个MyTestThread 的类,继承Thread类实现其中的run()方法。

public class MyTestThread extends Thread {
    @Override
    public void run() {
        super.run();
        System.out.println("MyTestThread");
    }
}

  测试运行类

public class RunMain {
    public static void main(String[] args) {
        MyTestThread myTestThread = new MyTestThread();
        myTestThread.start();
        System.out.println("运行结束");
    }
}

在这里插入图片描述
  从上面的运行结果可以看到,线程通过start()启动之后,会自动的调用了run()方法中的内容。也就是说run()方法中的内容就是我们在开发中实际需要操作的业务逻辑对象。从控制台输出的结果来看,在MyTestThread类中run()方法中的内容实际要比主线程中的内容要输出的晚。这也就说明了一个问题,其实在调用了start()方法之后,执行的过程是一个非常耗时的操作。从线程启动到线程执行到run()方法之后,其实远比主线程直接执行完要耗时。所以就出现了“运行结束”,在MyThreadThread打印之前的结果。

  虽然机会渺茫但是笔者通过几次的运行还是得到了如下的结果,从下面结果可以看到,在线程执行的过程中,并没有先后顺序,而是完全随机的。说明线程的执行时随机的。虽然输入这种结果的机会很小,但是多执行几次之后还是会出现。

在这里插入图片描述

  如果非要要这样一个结果的话可以在主线程中加入耗时操作。让主线程睡眠 2秒钟。

public class RunMain {
    public static void main(String[] args) throws InterruptedException {
        MyTestThread myTestThread = new MyTestThread();
        myTestThread.start();
        TimeUnit.SECONDS.sleep(2);
        System.out.println("运行结束");
    }
}

  这里需要注意的一点就是再使用多线程的时候,代码的运行结果与代码的执行顺序或者是调用顺序是无关的。线程是作为一个子任务来执行的,它对于CPU的使用是与主线程同一等级的。因为CPU调用每个线程是随机的,这也就解释了上面的结果。

  来看一下下面这种情况,如果我们重复调用了start()方法就会出现如下的异常。
在这里插入图片描述

常见的线程分析命令

  在平时的开发中,我们可以对运行中的线程进行状态分析,常见的三种线程状态分析的方法如下

  • jps+jstack.exe
  • jmc.exe
  • jvisualvm.exe

  创建了五个匿名线程对象,默认应该会是Thread-1~5的线程名。下面就通过上述的三种方式来分析一下线程

public class RunMain2 {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(){
                @Override
                public void run() {
                    try {
                        TimeUnit.SECONDS.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
            
        }
    }
}

jps + jstack的方式

  运行jps命令之后,会看到RunMain2线程ID为6312 ,然后通过jstack -l 6312 查看对应信息

C:\Users\Administrator>jps
13408
5248 Jps
10116 Launcher
6596 RuoYiApplication
11720 Launcher
12776 KotlinCompileDaemon
6312 RunMain2
11692 VideoCallApplication
18812

C:\Users\Administrator>jstask -l 6312
'jstask' 不是内部或外部命令,也不是可运行的程序
或批处理文件。

C:\Users\Administrator>jstack -l 6312
2022-03-25 17:26:26
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.291-b10 mixed mode):

"DestroyJavaVM" #19 prio=5 os_prio=0 tid=0x000002024835e800 nid=0x3784 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Thread-4" #18 prio=5 os_prio=0 tid=0x000002024835d000 nid=0x3f58 waiting on condition [0x000000ac7ffff000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        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.netty.charp01.demo.RunMain2$1.run(RunMain2.java:19)

   Locked ownable synchronizers:
        - None

"Thread-3" #17 prio=5 os_prio=0 tid=0x0000020248359800 nid=0x25f8 waiting on condition [0x000000ac7fefe000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        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.netty.charp01.demo.RunMain2$1.run(RunMain2.java:19)

   Locked ownable synchronizers:
        - None

"Thread-2" #16 prio=5 os_prio=0 tid=0x000002024835c000 nid=0x2454 waiting on condition [0x000000ac7fdff000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        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.netty.charp01.demo.RunMain2$1.run(RunMain2.java:19)

   Locked ownable synchronizers:
        - None

"Thread-1" #15 prio=5 os_prio=0 tid=0x0000020248357000 nid=0x403c waiting on condition [0x000000ac7fcfe000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        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.netty.charp01.demo.RunMain2$1.run(RunMain2.java:19)

   Locked ownable synchronizers:
        - None

"Thread-0" #14 prio=5 os_prio=0 tid=0x0000020248356800 nid=0x1ebc waiting on condition [0x000000ac7fbff000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        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.netty.charp01.demo.RunMain2$1.run(RunMain2.java:19)

   Locked ownable synchronizers:
        - None

"Service Thread" #13 daemon prio=9 os_prio=0 tid=0x000002024828f000 nid=0x305c runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C1 CompilerThread3" #12 daemon prio=9 os_prio=2 tid=0x00000202481df800 nid=0x3390 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C2 CompilerThread2" #11 daemon prio=9 os_prio=2 tid=0x00000202481de000 nid=0x44b4 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C2 CompilerThread1" #10 daemon prio=9 os_prio=2 tid=0x00000202481da800 nid=0x3094 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C2 CompilerThread0" #9 daemon prio=9 os_prio=2 tid=0x00000202481da000 nid=0x1e34 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"JDWP Command Reader" #8 daemon prio=10 os_prio=0 tid=0x000002024815c000 nid=0x2ff0 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"JDWP Event Helper Thread" #7 daemon prio=10 os_prio=0 tid=0x0000020248155000 nid=0x3870 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"JDWP Transport Listener: dt_socket" #6 daemon prio=10 os_prio=0 tid=0x0000020245d6c000 nid=0x3700 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000020245d43800 nid=0x25fc waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000020245d43000 nid=0x1588 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000020245d12800 nid=0x2e8c in Object.wait() [0x000000ac7effe000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b008ee0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
        - locked <0x000000076b008ee0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

   Locked ownable synchronizers:
        - None

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000020245d0b000 nid=0x3cfc in Object.wait() [0x000000ac7eeff000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b006c00> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x000000076b006c00> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

   Locked ownable synchronizers:
        - None

"VM Thread" os_prio=2 tid=0x0000020245ce2800 nid=0x3aec runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x000002022b094800 nid=0xa3c runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x000002022b096000 nid=0x2bec runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000002022b097000 nid=0x414 runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000002022b098800 nid=0x3f04 runnable

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000002022b09a800 nid=0x3d68 runnable

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x000002022b09b800 nid=0x1db4 runnable

"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x000002022b09e800 nid=0x3880 runnable

"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x000002022b09f000 nid=0x21a8 runnable

"GC task thread#8 (ParallelGC)" os_prio=0 tid=0x000002022b0a1000 nid=0x2f4c runnable

"GC task thread#9 (ParallelGC)" os_prio=0 tid=0x000002022b0a2000 nid=0x33dc runnable

"VM Periodic Task Thread" os_prio=2 tid=0x00000202482ec000 nid=0x2230 waiting on condition

JNI global references: 1520


C:\Users\Administrator>

jmc.exe

  笔者使用的时候需要从 http://jdk.java.net/jmc/8/ 连接上进行下载才可以使用
在这里插入图片描述
  

在这里插入图片描述
jvisualvm.exe

在这里插入图片描述

测试线程的随机性

  在前面我们提到线程的调用是随机的,在上面代码中,对这一点并没有太多的展现,在这里我们编写一个测试数据来测试线程的随机性。

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println("run="+Thread.currentThread().getName());
        }
    }
}

  启动测试线程

public class Test {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.setName("myThread");
        thread.start();

        for (int i = 0; i < 10000; i++) {
            System.out.println("Main="+Thread.currentThread().getName());
        }
    }
}

  测试结果
在这里插入图片描述
  可以看到,在Thread类中的start方法启动线程之后,线程进入就绪状态,准备去调用run方法中的逻辑代码。在这整个的过程就需要CPU安排时间片来执行对应的逻辑代码。而这个时间片的获取是与其他线程的执行以及CPU的性能等有关系的。所以这就导致线程的执行是随机的,导致最终输出到控制台的内容是随机的。

Start方法的执行顺序并不代表就是run方法的执行顺序

  一般情况下,我们都知道程序是从上到下按照顺序执行的,但在多线程中并不是这样。先来看个小例子。

public class MyThread extends Thread {
    private int i;
    public MyThread(int i){
        super();
        this.i = i;
    }

    @Override
    public void run() {
        System.out.println(i);
    }
}
public class Main {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread(1);
        MyThread myThread2 = new MyThread(2);
        MyThread myThread3 = new MyThread(3);
        MyThread myThread4 = new MyThread(4);
        MyThread myThread5 = new MyThread(5);
        MyThread myThread6 = new MyThread(6);
        MyThread myThread7 = new MyThread(7);
        MyThread myThread8 = new MyThread(8);
        MyThread myThread9 = new MyThread(9);
        MyThread myThread10 = new MyThread(10);
        MyThread myThread11 = new MyThread(11);
        MyThread myThread12 = new MyThread(12);

        myThread1.start();
        myThread2.start();
        myThread3.start();
        myThread4.start();
        myThread5.start();
        myThread6.start();
        myThread7.start();
        myThread8.start();
        myThread9.start();
        myThread10.start();
        myThread11.start();
        myThread12.start();
    }
}

在这里插入图片描述
  从运行结果来看,我们调用start方法是按照顺序进行调用的,但实际执行run方法的时候却是另一种情况。这也从侧面反映出了,start方法的顺序并不会影响run方法的顺序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nihui123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值