目录
·前言
在前面文章我介绍了线程是什么,线程存在的意义还有线程与进程的区别,在本篇文章将会带大家在Java中进一步认识线程,会用Java语言写第一个多线程的程序,介绍在Java中如何创建线程,还要结束多线程编程的优势。
一、第一个多线程程序
1.程序编写
在下面的程序中,我会让大家感受到多线程程序和普通程序的区别:
- 每个线程是独立的执行流;
- 多个线程之间是“并发”执行的。
程序代码如下所示:
// 1. 创建自己的类,继承自 Thread
class MyThread extends Thread {
// run 方法是该线程的入口方法
@Override
public void run() {
while (true) {
// currentThread.getName()这个方法是获取当前线程的名字
System.out.println("hello " + currentThread().getName());
try {
// sleep()这个方法会让当前线程进入休眠,()中参数是ms,为了减慢循环频率
// 这个方法可能抛出异常,所以需要用 try-catch包住
sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 2. 根据上面创建的类,创建出实例
Thread t1 = new MyThread();
// 3. start(),这个方法用来启动线程,这才是创建出线程
// 调用这个方法,才会真正调用系统 api ,在系统内核中创建出线程
t1.start();
// 在main里启动另一个死循环,直观感受两个线程在"并发"执行
while (true) {
System.out.println("hello " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
程序运行结果如下图所示:
由上图两个线程中的循环不断打印里面的内容,可以看出这两个线程是“并发”执行。
2.介绍jconsole
jconsole是一个很方便的工具,可以利用这个工具来查看我们创建线程的相关信息,他的位置在我们的jdk的bin目录下,如下图所示:
下面介绍一下jconsole的使用过程:
关于使用jconsole需要补充介绍几点:
- jconsole 只能列出Java的进程,其他不是Java的程序无法分析;
- 打开 jconsole 可能不显示任何进程,可以使用管理员的方式来启动 jconsole(鼠标右键选择jconsole,选择“以管理员的身份运行”);
- 上面图中可以看见,我们创建的线程名叫 Thread-0 ,但是代码中,实例化的对象名叫t,其实答案就在刚才说的话里,我们创建的是一个对象,t是对象的名,不是我们所创建的线程名,我们创建的线程名默认叫 Thread-0、Thread-1……;
- 上图中,我们会发现,除了我们手动创建的线程,和main线程外,还有很多别的线程,这些线程都是JVM自带的线程,他们需要完成一些其他工作,比如垃圾回收,监控统计各种指标(如果代码出问题,可以通过这些指标给我们提供一些线索)等……;
- 线程是不断执行的,jconsole 是在我们选择线程的一瞬间,对线程来个“快照”,就把这一瞬间的状态展开显示出来。
其实不光是 jconsole 可以看到线程的情况,在 idea 调试器中,也可以看道线程的情况,只不过我个人感觉 jconsole 比较方便,所以就不再介绍 idea 中的调试器了。
二、创建线程
下面我来介绍几种Java中创建线程的方式:
1.继承Thread类
①重写run方法
// 继承 Thread 重写 run 方法
class MyThread extends Thread {
@Override
public void run() {
System.out.println("hello thread!!!");
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
// 实例化一个线程对象
Thread t = new MyThread();
// 真正的去申请系统线程,参与 CPU 调度
t.start();
}
}
②重写run方法,使用匿名内部类
// 继承 Thread ,重写 run 方法,使用匿名内部类
public class ThreadDemo3 {
public static void main(String[] args) {
Thread t = new Thread(){
// 指定线程任务
@Override
public void run() {
System.out.println("hello thread");
}
};
// 真正的去申请系统线程,参与 CPU 调度
t.start();
}
}
2.实现Runnable接口
①重写run方法
// 创建一个 Runnable 的实现类,并实现 run 方法
// Runnable 主要描述的是线程的任务
class MyThread2 implements Runnable {
@Override
public void run() {
System.out.println("hello thread!!!");
}
}
public class ThreadDemo4 {
public static void main(String[] args) {
// 实例化 Runnable 对象
Runnable runnable = new MyThread2();
// 实例化线程对象,并绑定任务
Thread t = new Thread(runnable);
// 真正的去申请系统线程,参与 CPU 调度
t.start();
}
}
②重写run方法,使用匿名内部类
// 通过 Runnable 匿名内部类创建一个线程
public class ThreadDemo5 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
// 指定线程任务
@Override
public void run() {
System.out.println("hello thread!!!");
}
});
// 申请系统线程参与CPU调度
t.start();
}
}
③使用 lambda 表达式
lambda表达式可以简化函数式接口的使用。什么是函数式接口呢?就是一个有且只有一个抽象方法的普通接口,Runnable接口就是一个函数式接口,所以可以使用 lambda 表达式来创建 Runnable 的子类对象,进而创建一个线程,代码如下所示:
// 通过 lambda 表达式的方式创建一个线程
public class ThreadDemo6 {
public static void main(String[] args) {
Thread t = new Thread(()->{
// 指定任务: 打印 hello thread!!
System.out.println("hello thread!!");
});
// 申请系统线程参与CPU调度
t.start();
}
}
上述的五种创建线程的方式,我本人比较喜欢用 lambda 表达式的方式进行创建线程,因为这种方式比较简便。
三、多线程的优势
使用多线程的方式可以加快运行速度,可以举一个例子,如下图所示:
在一个房间中,有100只烤鸡,此时需要房间中的滑稽老哥把这些鸡给吃完,可想而知,这样吃下去的速度会很慢,为了加快吃鸡效率,我们可以再邀请一个个滑稽老哥一起吃鸡,如下图所示:
这样,一个滑稽老哥吃50只烤鸡,效率明显就快了很多,以此类推,我们还可以再多邀请几个滑稽老哥,如下图所示: 现在,邀请了更多的滑稽老哥,吃鸡的速度变得更快了,但是,不是说引入的滑稽老哥越多效率就会越快,如下图所示:
上图我们可以看出,邀请的滑稽老哥到达一定数量之后,再邀请更多的滑稽老哥,吃鸡的效率也没有办法继续提升了,这是因为桌子坐不下了。
上面列举的例子中,滑稽老哥就是我们的线程,烤鸡就是我们要执行的任务,多线程的优势就是通过引入多个线程可以加快执行任务的效率,但是也不能引入太多的线程,就像上图所示那样,线程数量太多,各个线程之间就会互相竞争CPU的资源(CPU核心数有限),这样非但不会提高效率,反而还会增加调度的开销。
·结尾
文章到这就快结束了,本篇文章是在Java中利用多线程编程的一个初始篇章,这里介绍了创建线程的几种方式,编写的第一个多线程程序,介绍了多线的优势,在下一个篇文章中我会对java中Thread类再进行进一步的讲解,如果本篇文章对你有帮助的话,希望能三连支持一下咯,您的支持就是我最大的动力,我们下一篇文章再见吧,┏(^0^)┛~~~