目录
前言:
1.什么是线程
2.为什么要有线程
3.进程与线程的区别与联系
4.Java的线程和操作系统线程的关系
5.多线程编程示例
6.创建线程
6.1继承Thread类
6.2实现Runnable接口
6.3继承Thread,使用匿名内部类
6.4实现Runnable接口,使用匿名内部类
6.5lambda表达式创建Runnable子类对象
7.Thread类及常见的方法
7.1Thread中常见的构造方法
7.2Thread的几个常见属性
结束语:
前言:
这节中小编就来和大家聊一聊多线程是什么以及需要我们掌握多线程程序的编写、多线程的状态、什么是线程不安全及解决思路以及掌握synchronized、volatile关键字。在上节博客中给大家讲到了进程和进程管理,我们讲解了为什么要使用调度,CPU的按照并发的方式来执行进程的,在PCB中也提供了一些属性,里面有进程的优先级、进程的状态、进程的上下文、进程的记账信息......也给大家讲解了引入进程的目的就是为了能够实现多个任务并发执行的效果。接下来小编就给大家讲解一下什么是线程,线程与进程之间又有什么联系。
1.什么是线程
一个线程就是一个“执行流”,每个线程之间都可以按照顺序执行自己的代码,多个线程之间“同时”执行着多份代码。
2.为什么要有线程
首先“并发编程”称为“刚需”。
- 单核CPU的发展遇到了瓶颈,要想提高算力,就需要多核CPU,而并发编程能够更充分利用多核CPU资源。
- 有些任务场景需要“等待IO”,为了让等待IO的时间能够去做一些其他的工作,也需要用到并发编程。
其次,虽然多进程也能实现并发编程,但是是线程比进程更轻量。
- 创建线程比创建进程更快。
- 销毁线程比销毁进程更快。
- 调度线程比调度进程更快。
最后,线程虽然比进程轻量,但是人们还不满足,于是又有了“线程池”和“协程”。
- 有关于线程池的话题我们后面在介绍,关于协程的话题这里我们不做过多的讨论。
3.进程与线程的区别与联系
进程有一个重大的问题就是比较重量,如频繁的创建/销毁进程,成本会比较高。
所以我们又引出了线程的概念。那么线程与进程之间到底有什么联系和区别呢?我们接着往下看。
进程是包含线程的。每一个进程至少有一个线程存在,即主线程。一个进程里可以有一个线程也可以有多个线程,每个线程之间都是一个独立存在的执行流,多个线程之间也是并发执行的。这里注意多个线程可能是在多个CPU核心上同时运行,也可能是在一个CPU核心上,通过快速调度,并发运行。操作系统真正的调度是在调度线程而不是在调度进程。
注意:线程是操作系统调度运行的基本单位!!!进程是操作系统资源分配的基本单位!!!
一个进程中的多个线程之间,共用一份系统资源。
- 内存空间。
- 文件描述符表。
注意:只有在进程启动的时候,创建第一个线程的时候需要花成本去申请系统资源。一旦进程(第一个线程)创建完毕,此时后续再创建线程就不必在申请资源了,这样创建和销毁的效率就会大大提高。
下面小编给大家举个例子方便大家更好的理解进程与线程。
所以通过上述的例子再给大家总结一下进程与线程之间的区别:
- 进程包含线程。
- 进程有自己独立的内存空间和文件描述符表,同一个进程中的多个线程之间共享一份地址空间和文件描述符表。
- 进程是操作系统资源分配的基本单位,线程是操作系统调度执行的基本单位。
- 进程之间具有独立性,一个进程挂了不会影响其他进程;同一个进程里的多个线程之间,一个线程挂了,可能会把整个进程带走,影响其他进程。
4.Java的线程和操作系统线程的关系
线程是操作系统中的概念,操作系统内核实现了线程这样的机制,并且对用户层提供了一些API供用户使用。
Java标准库中Thread类可以视为是对操作系统提供的API进行了进一步的抽象和封装。
5.多线程编程示例
上面说了这么多的概念,接下来还是带着大家一起在代码中切实的感受一下究竟什么是多线程。
代码展示:
package Thread;
class MyThread extends Thread{
@Override
public void run() {
while (true) {
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Thread t = new MyThread();
//start会创建新的线程。
t.start();
//run不会创建新的线程。run是在main线程中执行的。
//t.run();
while (true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果展示:
代码分析:
在上述代码中我们看到我们写了一个类MyThread并继承了Thread类,在java中标准库提供了一个类Thread,它能够表示一个线程。
在继承Thread的时候我们需要对父类中的run()方法进行重写。
- 在主函数中我们先创建出来了一个MyThread的实例。
Thread t = new MyThread();
- 紧接着我们就要启动线程。
t.strat();
在上述代码中我们涉及到了两个线程。
- main方法所对应的线程(一个进程里面至少有一个线程)也可以称为主线程。
- 通过t.strat创建一个新的线程。
结果分析:
我们执行代码之后看到的效果是“hello t”和“hello main”是交替打印的,但又不是严格意义上的交替打印。按照我之前的单线程的想法,如果我们执行main线程中的进入while循环之后由于是死循环,那么按照之前的想法应该是出不来的,应该是要一值打印“hello main”的,但是这里并不是我们想象中的那样,而是和另一个线程中的一起交替打印,那就说明这里是启动了两个线程,两个线程分别独立运行。多线程在CPU上的调度是不确定的是随机的。所以我们看到的就是不规律的交替打印。这也就是我们上述中提到的一个进程中的多个线程并发执行的过程。
同时我们可以借助jak里面的工具jconsole来分析java里的线程。
在下面的路径下找到安装路径,然后双击打开运行。
点击线程,在下面可以看到很多线程这里你会发现有很多线程,这里不只有咱们创建出来的两个。除了标记出来的两个线程之外其他都是JVM自己创建出来的。
描述出了当前这两线程执行到哪里了。
6.创建线程
6.1继承Thread类
我们可以使用Thread,重写run方式来创建线程。
代码展示:
package Thread;
//使用继承Thread,重写run方法来创建线程
class MyThread extends Thread{
@Override
public void run() {
while (true) {
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Thread t = new MyThread();
//start会创建新的线程。
t.start();
//run不会创建新的线程。run是在main线程中执行的。
//t.run();
while (true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果展示:
6.2实现Runnable接口
使用实现Runable,重写run。
代码展示:
package Thread;
//使用Runnable接口来实现线程创建
//1.实现Runnable接口
class MyRunnable implements Runnable{
@Override
public void run() {
while (true) {
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
//2.创建爱你Thread类实例,调用Thread的构造方法是将Runnable对象作为target参数。
MyRunnable runnable = new MyRunnable();
Thread t = new Thread(runnable);
//3.调用start方法。
t.start();
while (true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果展示:
对比上述两种方法:
- 继承Thread类,直接使用this就表示当前线程对象的引用。
- 实现Runnable接口,this表示的是MyRunnable的引用,需要使用Thread.currentThread()
6.3继承Thread,使用匿名内部类
代码展示:
package Thread;
//继承Thread,使用匿名内部类来创建线程
public class ThreadDemo3 {
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
while (true) {
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//启动线程
t.start();
//主线程
while (true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果展示:
6.4实现Runnable接口,使用匿名内部类
代码展示:
package Thread;
public class ThreadDemo4 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//启动线程
t.start();
//主线程
while (true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果展示:
6.5lambda表达式创建Runnable子类对象
这个也是创建线程最推荐的写法,使用lambda表达式也是最直观的简单的写法!!!
前面也给大家讲解过有关于lambda表达式的用法,如果还有不会的同学请点击下面的链接先去看看lambda表达式的使用吧!!!(http://t.csdn.cn/zEPFB)
代码展示:
package Thread;
//使用lambda表达式来创建线程
public class ThreadDemo5 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true) {
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//启动线程
t.start();
//主线程
while (true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果展示:
7.Thread类及常见的方法
Thread类是JVM用来管理线程的一个类,换句话说,每个线程都由一个唯一的Thread对象与之关联。Java代码中的Thread对象和操作系统中的线程是一一对应的。
用我们上述的例子来看,每个执行流,也需要有一个对象来描述,类似下图所示,而Thread类的对象就是用来描述一个线程执行流的,JVM会将这些Thread对象组织起来,用于线程调度,线程管理。
7.1Thread中常见的构造方法
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用Runnable对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target,String name) | 使用Runnable对象创建线程对象,并命名 |
Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组,这个目前我们了解即可 |
代码演示案例:
package Thread;
public class ThreadDemo6 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true) {
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"我的线程");
t.start();
while (true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果展示:
我们可以看到在上述运行的线程中就会出现自己命名的线程名字。
7.2Thread的几个常见属性
属性 | 获取 |
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
代码展示:
package Thread;
public class ThreadDemo7 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
// System.out.println("hello t");
},"我的线程");
t.start();
System.out.println("线程ID是:" + t.getId());
System.out.println("线程名称是:" + t.getName());
System.out.println("线程状态是:" + t.getState());
System.out.println("线程优先级是:" + t.getPriority());
System.out.println("线程是否是后台线程:" + t.isDaemon());
System.out.println("线程是否存活:" + t.isAlive());
System.out.println("线程是否被中断:" + t.isInterrupted());
}
}
结果展示:
解释getDaemon:
- true表示是后台线程。后台线程不会阻止java进程结束,哪怕后台线程还没有执行完,java进程该结束就会结束。
- false表示是前台线程。前台线程会阻止java进程结束,必须得java进程中的所有前台线程都执行完java进程才会结束。
注意:创建的线程默认是前台的,也可以通过setDaemon修改成后台线程。
解释isAlive:
是描述系统内核里的那个线程是否还存活。线程的入口方法执行完毕,此时系统中的对应的线程就没有了,此时调用该线程isAlive就是false。
结束语:
这节中小编主要是给大家分享了线程了概念、线程与进程之间的区别和联系以及如何创建线程。希望这节对大家学习JavaEE有一定的帮助,想要学习的同学记得关注小编和小编一起学习吧!如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津(在此小编先谢过各位大佬啦!)