1.多线程介绍
学习多线程前,我们先了解几个关于多线程的概念。
1.1、进程:当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且进程之间是相互独立存在的。
如打开电脑的任务管理器,点击进程栏,qq,IDEA,谷歌浏览器,网易云音乐就是四个进程。
操作系统进行资源分配的最小单元是进程
1.2、线程: 线程是进程中的一个最小执行单位,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。如下图的IDEA,就同时运行了四个线程。
操作系统进行运算调度的最小单元是线程
总的来说:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
1.3、串行:顾名思义,“串” — 单个线程把多个任务像串一样串起来,执行的时候只能一个一个依次执行,不会在时间上同时执行多个任务
1.4、并行:一样的,从名字来看,齐头并进。比如同时下载多个文件,这是在同一时刻同时发生的,存在时间重叠。
了解到以上基本概念之后,我们来看多线程是什么。
举个例子,打开网易云音乐,就是开启了一个进程。而在这个进程里有很多功能,听歌,搜歌,看mv等。
如果是单线程,我们听歌的时候想搜歌,只能等歌曲结束,才能进行搜歌操作,这是有执行顺序,不能同时进行的。
而如果是多线程,我们可以一边听歌,一边搜索歌曲,还可以一边看评论区。 这几个操作可以同时进行,互不影响,没有执行顺序。
这就是一个进程运行时产生了多个线程。
2.程序运行原理
现代操作系统比如UNIX,Linux,Windows等,都是支持“多任务”的操作系统。所谓的多任务,就是操作系统可以同时运行多个任务。
操作系统(如Windows、Linux)的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。
任务执行的一小段时间叫做时间片,任务正在执行时的状态叫运行状态,任务执行一段时间后强制暂停去执行下一个任务,被暂停的任务就处于就绪状态等待下一个属于它的时间片的到来。
这样每个任务都能得到执行,由于CPU的执行效率非常高,时间片非常短,在各个任务之间快速地切换,给人的感觉就是多个任务在“同时进行”,这也就是我们所说的并发。
并发与并行
现在,多核CPU已经非常普及了,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。对于
并发:多个进程在一个CPU下采用时间片轮转的方式,在一段时间之内,让多个进程都得以推进,称之为并发(假同时)
并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行(真同时)
计算机操作系统中把并行性和并发性明显区分开,主要是从微观的角度来说的,具体是指进程的并行性(多处理机的情况下,多个进程同时运行)和并发性(单处理机的情况下,多个进程在同一时间间隔运行的)。
并发与并行类似于工厂中的流水线,要扩大产量,1是考虑建造多个工厂,这就是并行。2是考虑每个工厂中新增流水线,这就类似并发
3.用java实现一个进程
方式一、通过继承 Thread
来创建一个线程类,该方法的好处是 this 代表的就是当前线程,不需要通过Thread.currentThread()
来获取当前线程的引用。
package MyThread;
public class MyThread {
public static void main(String[] args) {
Thread2 t = new Thread2();
t.start();
}
static class Thread2 extends Thread {
@Override
public void run() {
//打印当前进程名字(如自己没设置名字,系统默认生成)
System.out.println(currentThread().getName());
}
}
}
Thread-0
方式二、通过实现 Runnable
接口,并且调用 Thread
的构造方法时将 Runnable
对象作为 target
参数传入来创建线程对象
该方法的好处是可以规避类的单继承的限制;但需要通过 Thread.currentThread()
来获取当前线程的引用。
package MyThread;
public class MyThread {
public static void main(String[] args) {
Thread t = new Thread(new myRunnable());
t.start();
}
static class myRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"在运行");
}
}
}
Thread-0在运行
方式三、使用Callable创建线程(Future/FutureTask + Callable)
使用Callable 创建线程,有返回值,可以获取到线程的执行结果
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("callable对象");
return "OK";
}
};
//FutureTask任务类,实现了Runnable, Future接口。传入callable
FutureTask<String> task = new FutureTask<>(callable);
//通过线程启动
new Thread(task).start();
//main线程阻塞等待,需要等上边线程执行完毕并获取到返回值
String str = task.get(); //task.get()获取返回值
System.out.println(str);
System.out.println("====================");
//线程池可以传入callable对象,返回值为Future类型
ExecutorService pool = Executors.newFixedThreadPool(4);
Future future = pool.submit(callable);
System.out.println(future.get()); //main阻塞等待
}
callable对象
OK
====================
callable对象
OK
最后,再用一个小例子体会一下多线程的优点 - 增加运行效率
package ThreadAdvantage;
public class ThreadAdvantage {
//线程数目
private static final int NUM = 5;
//计算次数
private static final int COUNT = 10_0000_0000;
//测试:计算 a 的值
private static void increment(){
int a = 0;
for(long i = 0; i < COUNT; i++){
a++;
}
}
//串行(依次执行)
private static void serial(){
long start = System.nanoTime();
for(int i = 0;i < NUM;i++){
increment();
}
long end = System.nanoTime();
System.out.printf("串行运行时间:%s毫秒",(end-start)/1000/1000);
}
//并发(有时候并发既表达并发,也表达并行)
private static void parallel(){
long start = System.nanoTime();
for(int i = 0;i < NUM;i++) {
//开启五个新线程,分别运行increment
new Thread(new Runnable() {
@Override
public void run() {
increment();
}
}).start();
}
//若活跃线程大于1,即并发没结束,当前线程让步,继续执行并发线程
while (Thread.activeCount() > 1){
Thread.yield();
}
long end = System.nanoTime();
System.out.printf("并发运行时间:%s毫秒",(end-start)/1000/1000);
}
public static void main(String[] args) {
serial();
System.out.println();
parallel();
System.out.println();
}
}
运行结果:
Connected to the target VM, address: '127.0.0.1:52532', transport: 'socket'
串行运行时间:3156毫秒
并发运行时间:1180毫秒
Disconnected from the target VM, address: '127.0.0.1:52532', transport: 'socket'