目录
前言:
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中也提供了一些属性,里面有进程的优先级、进程的状态、进程的上下文、进程的记账信息......也给大家讲解了引入进程的目的就是为了能够实现多个任务并发执行的效果。接下来小编就给大家讲解一下什么是线程,线程与进程之间又有什么联系。
一个线程就是一个“执行流”,每个线程之间都可以按照顺序执行自己的代码,多个线程之间“同时”执行着多份代码。
首先“并发编程”称为“刚需”。
其次,虽然多进程也能实现并发编程,但是是线程比进程更轻量。
最后,线程虽然比进程轻量,但是人们还不满足,于是又有了“线程池”和“协程”。
进程有一个重大的问题就是比较重量,如频繁的创建/销毁进程,成本会比较高。
所以我们又引出了线程的概念。那么线程与进程之间到底有什么联系和区别呢?我们接着往下看。
进程是包含线程的。每一个进程至少有一个线程存在,即主线程。一个进程里可以有一个线程也可以有多个线程,每个线程之间都是一个独立存在的执行流,多个线程之间也是并发执行的。这里注意多个线程可能是在多个CPU核心上同时运行,也可能是在一个CPU核心上,通过快速调度,并发运行。操作系统真正的调度是在调度线程而不是在调度进程。
注意:线程是操作系统调度运行的基本单位!!!进程是操作系统资源分配的基本单位!!!
一个进程中的多个线程之间,共用一份系统资源。
注意:只有在进程启动的时候,创建第一个线程的时候需要花成本去申请系统资源。一旦进程(第一个线程)创建完毕,此时后续再创建线程就不必在申请资源了,这样创建和销毁的效率就会大大提高。
下面小编给大家举个例子方便大家更好的理解进程与线程。
所以通过上述的例子再给大家总结一下进程与线程之间的区别:
线程是操作系统中的概念,操作系统内核实现了线程这样的机制,并且对用户层提供了一些API供用户使用。
Java标准库中Thread类可以视为是对操作系统提供的API进行了进一步的抽象和封装。
上面说了这么多的概念,接下来还是带着大家一起在代码中切实的感受一下究竟什么是多线程。
代码展示:
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()方法进行重写。
Thread t = new MyThread();
t.strat();
在上述代码中我们涉及到了两个线程。
结果分析:
我们执行代码之后看到的效果是“hello t”和“hello main”是交替打印的,但又不是严格意义上的交替打印。按照我之前的单线程的想法,如果我们执行main线程中的进入while循环之后由于是死循环,那么按照之前的想法应该是出不来的,应该是要一值打印“hello main”的,但是这里并不是我们想象中的那样,而是和另一个线程中的一起交替打印,那就说明这里是启动了两个线程,两个线程分别独立运行。多线程在CPU上的调度是不确定的是随机的。所以我们看到的就是不规律的交替打印。这也就是我们上述中提到的一个进程中的多个线程并发执行的过程。
同时我们可以借助jak里面的工具jconsole来分析java里的线程。
在下面的路径下找到安装路径,然后双击打开运行。
点击线程,在下面可以看到很多线程这里你会发现有很多线程,这里不只有咱们创建出来的两个。除了标记出来的两个线程之外其他都是JVM自己创建出来的。
描述出了当前这两线程执行到哪里了。
我们可以使用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();
}
}
}
}
结果展示:
使用实现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();
}
}
}
}
结果展示:
对比上述两种方法:
代码展示:
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();
}
}
}
}
代码展示:
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();
}
}
}
}
这个也是创建线程最推荐的写法,使用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();
}
}
}
}
Thread类是JVM用来管理线程的一个类,换句话说,每个线程都由一个唯一的Thread对象与之关联。Java代码中的Thread对象和操作系统中的线程是一一对应的。
用我们上述的例子来看,每个执行流,也需要有一个对象来描述,类似下图所示,而Thread类的对象就是用来描述一个线程执行流的,JVM会将这些Thread对象组织起来,用于线程调度,线程管理。
方法 | 说明 |
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();
}
}
}
}
结果展示:
我们可以看到在上述运行的线程中就会出现自己命名的线程名字。
属性 | 获取 |
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:
注意:创建的线程默认是前台的,也可以通过setDaemon修改成后台线程。
解释isAlive:
是描述系统内核里的那个线程是否还存活。线程的入口方法执行完毕,此时系统中的对应的线程就没有了,此时调用该线程isAlive就是false。
这节中小编主要是给大家分享了线程了概念、线程与进程之间的区别和联系以及如何创建线程。希望这节对大家学习JavaEE有一定的帮助,想要学习的同学记得关注小编和小编一起学习吧!如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津(在此小编先谢过各位大佬啦!)