- 一、进程
- 二、线程
- 1.线程的概念
- 2.进程和线程的关系
- 3.进程和线程之间的区别和联系
- 三、Java中的线程
- 1.线程的创建方式
- 2.start() 和 run()的区别
- 3.jconsole
- 4.多线程的好处
一、进程
关于进程更详细的介绍可以看一下上一篇博客 计算机的工作原理,这里的进程只是概括一下
一个运行起来的程序,就是一个进程,进程在操作系统中是这样进行管理:
- 描述PCB(pid,内存指针,文件描述符,进程的状态,上下文,优先级,记账信息,等…),实际上PCB是一个非常大的结构体,属性非常非常多。
- 组织:进程使用双向链表来组织
虚拟地址空间:让进程和进程之间隔离开,尽量不相互影响
引入进程的目的,就是为了能够 “并发编程”,虽然多进程已经能够解决并发的问题了,但是大佬们认为还不够理想。
- 创建进程,就需要分配资源
- 销毁进程,就需要释放资源
如果频繁创建销毁,这样的开销就比较大了
于是程序员就发明了一个 “线程(Thread)”概念,线程在有些系统上也叫做"轻量级进程"
二、线程 1.线程的概念一个线程就是一个 “执行流”. 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 “同时” 执行着多份代码.并发编程,线程比进程更轻量
1. 创建线程比创建进程更高效
2. 销毁线程比销毁进程更高效
3. 调度线程比调度进程更高效
创建线程,并没有取申请资源。
销毁线程,也不需要释放资源。
线程是产生在进程内部,共用之前的资源
进程和线程之间,是包含关系
一个进程可以包含一个线程或者多个线程
先把进程创建出来之后,这个时候相当于资源都分配好了
然后再在这个进程里面,创建线程,这样线程就和之前的进程共用一样的资源了
站在系统内核的角度,来看待进程和线程的话
其实在操作系统内核的角度,不分线程还是进程的,只认PCB。
当创建一个进程的时候,就是创建了一个PCB出来了
同时这个PCB也可以视为是当前进程中已经包含了一个线程了(一个进程中至少得有一个线程)
属于同一个进程的线程之间是可以共用同一份内存空间,同时其它的进程(PCB)使用的是独立的内存空间
- 进程是包含线程的,一个进程里面可以有一个线程,也可以有多个线程。
- 每个进程都有独立的内存空间(虚拟地址空间),同一个进程的多个线程之间,共用这个虚拟地址空间
- 进程是操作系统资源分配的基本单位,线程是操作系统调度执行的基本单位
在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语法层面的区别
我们可以通过 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方法属于同一进程,是并发执行的。
3.jconsolerun方法不会创建新的进程,相当于一个普通的方法。直接调用 run 方法还是顺序执行状态,只有当
run方法执行完后才会执行下面的代码。并没有达到多线程的目的
在 Java jdk 的 bin 目录下有 jconsole 的工具,可以查看到进程的
当线程跑起来后之后打开,找到对应的线程。
假设我们对两个个变量同时自增 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 类及常见方法



