Java多线程——线程的概念和创建
Posted 爱敲代码的三毛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程——线程的概念和创建相关的知识,希望对你有一定的参考价值。
文章目录
一、进程
关于进程更详细的介绍可以看一下上一篇博客 计算机的工作原理,这里的进程只是概括一下
一个运行起来的程序,就是一个进程,进程在操作系统中是这样进行管理:
- 描述PCB(pid,内存指针,文件描述符,进程的状态,上下文,优先级,记账信息,等…),实际上PCB是一个非常大的结构体,属性非常非常多。
- 组织:进程使用双向链表来组织
虚拟地址空间:让进程和进程之间隔离开,尽量不相互影响
引入进程的目的,就是为了能够 “并发编程”,虽然多进程已经能够解决并发的问题了,但是大佬们认为还不够理想。
- 创建进程,就需要分配资源
- 销毁进程,就需要释放资源
如果频繁创建销毁,这样的开销就比较大了
于是程序员就发明了一个 “线程(Thread)”概念,线程在有些系统上也叫做"轻量级进程"
二、线程
1.线程的概念
一个线程就是一个 “执行流”. 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 “同时” 执行着多份代码.并发编程,线程比进程更轻量
1. 创建线程比创建进程更高效
2. 销毁线程比销毁进程更高效
3. 调度线程比调度进程更高效
创建线程,并没有取申请资源。
销毁线程,也不需要释放资源。
线程是产生在进程内部,共用之前的资源
2.进程和线程的关系
进程和线程之间,是包含关系
一个进程可以包含一个线程或者多个线程
先把进程创建出来之后,这个时候相当于资源都分配好了
然后再在这个进程里面,创建线程,这样线程就和之前的进程共用一样的资源了
站在系统内核的角度,来看待进程和线程的话
其实在操作系统内核的角度,不分线程还是进程的,只认PCB。
当创建一个进程的时候,就是创建了一个PCB出来了
同时这个PCB也可以视为是当前进程中已经包含了一个线程了(一个进程中至少得有一个线程)
属于同一个进程的线程之间是可以共用同一份内存空间,同时其它的进程(PCB)使用的是独立的内存空间
3.进程和线程之间的区别和联系
- 进程是包含线程的,一个进程里面可以有一个线程,也可以有多个线程。
- 每个进程都有独立的内存空间(虚拟地址空间),同一个进程的多个线程之间,共用这个虚拟地址空间
- 进程是操作系统资源分配的基本单位,线程是操作系统调度执行的基本单位
三、Java中的线程
1.线程的创建方式
在Java中使用 Thread 这个类来创建线程,通过这个类有很多方法来创建一个线程
- 继承 Thread 重写 run 方法
class MyTread1 extends Thread {
@Override
public void run() {
System.out.println("继承Thread,重写run");
}
}
- 实现 Runnable 接口,重写run
class MyThread2 implements Runnable {
@Override
public void run() {
System.out.println("实现 Runnable 接口,重写run");
}
}
public class TestThread2 {
public static void main(String[] args) {
Thread t = new MyThread();
}
}
- 继承 Thread ,重写run方法,使用匿名内部类
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
super.run();
}
};
}
- 实现 Runnable,重写 run,使用匿名内部类
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("实现Runnable 接口,使用匿名内部类创建");
}
});
}
- 使用 lambda 表达式
public static void main(String[] args) {
//Thread t = new Thread(()-> System.out.println("使用 lambda 表达式来创建 Runnable 子类对象"));
Thread t = new Thread(()->{
System.out.println("使用 lambda 表达式来创建 Runnable 子类对象");
});
}
以上这些创建线程的方式,本质都相同。
都是借助 Thread 类,在内核中创建新的PCB,加入到内核的双向链表中
只不过区别是,指定线程要执行任务的方式不一样,
此处的区别,其实都只是单纯的Java语法层面的区别
2.start() 和 run()的区别
我们可以通过 start()方法来启动线程
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("启动线程");
}
});
thread.start();
}
我们直接调用 run 方法
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("启动线程");
}
});
thread.run();
}
这两者的运行结果都是有相同的
那么它们到底有什么区别呢?
start() 方法
当点击运行时,首先系统会创建一个进程,这个进程里面包含了一个线程,就是main方法,接着运行 start方法,会创建一个新的PCB也就是线程,挂在链表上。
接着这个新的PCB会执行相应代码
run() 方法,直接运行并没创建新的线程
总结:start会创建一个新的线程执行run方法,同时main方法同时执行。run方法和main方法属于同一进程,是并发执行的。
run方法不会创建新的进程,相当于一个普通的方法。直接调用 run 方法还是顺序执行状态,只有当
run方法执行完后才会执行下面的代码。并没有达到多线程的目的
3.jconsole
在 Java jdk 的 bin 目录下有 jconsole 的工具,可以查看到进程的
当线程跑起来后之后打开,找到对应的线程。
4.多线程的好处
假设我们对两个个变量同时自增 20 亿次
采用串行执行
public static void main(String[] args) {
long a = 0;
long b = 0;
long start = System.currentTimeMillis();
for (long i = 0; i < count; i++) {
a++;
}
for (long i = 0; i < count; i++) {
b++;
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
运行结果
采用并发执行
private static final long count = 10_0000_0000L;
public static void main(String[] args) {
long start = System.currentTimeMillis();
Thread t1 = new Thread(new Runnable() {
long a = 0;
@Override
public void run() {
for (int i = 0; i < count; i++) {
a++;
}
}
});
Thread t2 = new Thread(new Runnable() {
long b = 0;
@Override
public void run() {
for (long i = 0; i < count; i++) {
b++;
}
}
});
t1.start();
t2.start();
//阻塞,只有当 t1和t2执行完才执行后面的代码
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(end-start);
}
运行结果
我们发现多线程的确快了不少,
串行执行:700mm左右
并发执行:400mm左右
那么是正好300的差距吗?不一定的,
线程调度,自身也是有开销的。
一个线程执行 40 亿次和两个线程各执行 20 亿次,中间都可能会调度诺干次,也就是都会花一定时间,这个时间是不确定的。
下一篇线程的Thread 类及常见方法
以上是关于Java多线程——线程的概念和创建的主要内容,如果未能解决你的问题,请参考以下文章