多线程
并发和并行
并行:指两个或多个事件在同一时刻发生(同时执行)。
并发:指两个或多个事件在同一个时间段内发生(交替执行)。
注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。
进程和线程
进程
进程(Process
)是指正在运行的程序,如QQ
,是程序一次动态执行过程。它对应了从代码加载、执行并执行完毕的一个完整过程,这个过程也是进程本身从产生、发展到消亡的过程。操作系统同时管理一个计算机系统中的多个进程,让计算机系统中的多个进程轮流使用CPU资源,或者共享资源。
特点:
- 进程是系统运行的基本单位
- 每一个进程都有自己独立的空间、一组系统资源
- 每一个进程内部数据和状态都是完全独立的
- 每一个应用程序运行时都会产生一个进程
线程
线程是进程中执行运算的最小单位,一个进程在其执行过程中可以产生多个线程,而线程必须在某个进程内执行。
线程是进程内部的一个执行单元,是可以完成一个独立任务的顺序控制流程。一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
进程和线程的区别
进程与线程的区别
- 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
- 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。
多线程的由来
摩尔定律是由英特尔(Intel)创始人之一戈登·摩尔(Gordon Moore)提出来的。其内容为:当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,性能也将提升一倍。换言之,每一美元所能买到的电脑性能,将每隔18-24个月翻一倍以上。这一定律揭示了信息技术进步的速度。然而,“摩尔定律”的时代将会退出,因为研究和实验室的成本需求十分高昂,而有财力投资在创建和维护芯片工厂的企业很少。而且制程也越来越接近半导体的物理极限,将会难以再缩小下去。这时,我们不再追求更快的处理器了,而是着眼于更多的处理器(多核处理器),而且让他们一致保持工作。
Java
在当时很超前,它是第一个支持并发程序设计的主流语言。它的出发点是,当时多核处理器还很神秘,而且Web
编程才刚刚起步,处理器要花很长的时间等待服务器响应,需要并发程序设计来确保界面不会冻住
。
我们已经很熟悉计算机操作系统中的多任务:在同一刻运行多个程序的能力。如:边打游戏边听歌。现在,我们的电脑都是拥有多个CPU的,但是,并发执行的进程数目并不是由CPU数目制约的。操作系统将CPU的时间片段分配给每个进程,给人并行的感觉。
多线程程序是扩展了多任务的概念:一个程序同时执行多个任务。通常,每一个任务称为一个线程(Thread
),它是线程控制的简称。可以同时运行一个以上线程的程序称为多线程程序(Multithreaded
)。
1:因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于CPU 的调度,程序员是干涉不了的。而这也就造成的多线程的随机性。
2:Java 程序的进程里面至少包含两个线程,主进程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个线程,java 本身具备了垃圾的收集机制,所以在 Java 运行时至少会启动两个线程。
3:由于创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是创建多进程。
多线程和多进程的区别
本质的区别在于每个进程拥有自己的一套变量,而线程则是共享数据。线程之间共享数据是有风险的(后边分析),然而共享变量使线程之间的通信比进程之间通信更有效、更容易。此外,在有的操作系统中,与进程相比,线程更"轻量级",创建、撤销一个线程比启动新的进程开销要小的多。
线程调度
- 分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。 - 抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java
使用的为抢占式调度。
创建线程类
每个程序至少自动拥有一个线程,称为主线程。当程序加载到内存时启动主线程。java
程序中public static void main(String[] args)
是主线程的入口,运行java
程序时会执行这个方法。
- 将一个类声明为
Thread
的子类。 这个子类应该重写Thread
类的方法run
。 然后可以分配并启动子类的实例。
使用Thread类
java.lang.Thread
类代表线程。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java
使用线程执行体来代表这段程序流。
public class Thread implements Runnable
创建线程
继承Thread
类,并重写run()
方法。
package com.itlaobing.demo.thread;
public class MyThread extends Thread{
//定义指定线程名称的构造方法
public MyThread(String name) {
//调用父类的String参数的构造方法,指定线程的名称
super(name);
}
/**
* 重写run方法,完成该线程执行的逻辑
*/
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(getName()+":正在执行!"+i);
}
}
}
@Test
public void testMyThread(){
//创建自定义线程对象
MyThread thread = new MyThread("myThread");
//开启线程
thread.start();
//在主方法中执行for循环
for (int i = 0; i < 200; i++){
System.out.println("main()线程" + i);
}
}
实现Runnable接口的方式创建
java.lang.Runnable
也是非常常见的一种创建线程的方式,我们只需要重写run
方法即可。任何实现Runnable
接口的类,其实例都能被线程执行。
public interface Runnable
创建线程任务
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
@Test
public void testMyRunnable() throws InterruptedException {
//创建线程任务对象
MyRunnable runnable = new MyRunnable();
//创建线程对象
Thread thread = new Thread(runnable, "皇家一号线程");
//启动线程
thread.start();
//线程休眠5S
Thread.sleep(5000);
for (int i = 0; i < 50; i++) {
System.out.println("main线程 " + i);
}
}
上面代码中MyRunnable
可以是一个内部类。具体实现自己补充。
Runnable
对象仅仅作为Thread
对象的target
,Runnable
实现类里包含的
run()
方法仅作为线程执行体。而实际的线程对象依然是Thread
实例,只是该
Thread
线程负责执行其target
的run()
方法。
Thread
和Runnable
区别
实现Runnable
接口比继承Thread
类所具有的优势:
- 可以避免
java
中的单继承的局限性。 - 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
- 实现了
Runable
接口的话,很容易的实现资源共享(一个实例多个线程运行)。(适合多个相同的程序代码的线程去共享同一个资源。) - 线程池只能放入实现
Runnable
或Callable
类线程,不能直接放入继承Thread
的类。
实现Callable接口
java.util.concurrent.Callable
接口类似于Runnable
,因为两者的实例都是为另一个线程执行的类设计的。
但是, Runnable
不返回结果,也不能抛出检查异常。
public interface Callable<V> {
// 计算结果,如果无法计算结果,则抛出一个异常
V call() throws Exception;
}
java.util.concurrent.FutureTask
可用于包装一个Callable
或 Runnable
对象。具有启动和取消计算、查询计算是否完成以及检索计算结果的方法。
构造函数:
public FutureTask(Runnable runnable, V result)
public FutureTask(Callable<V> callable)
常用方法:
// 如有必要,等待计算完成,然后检索其结果。get 如果计算尚未完成,这些方法将阻塞
public V get()
// 如有必要,最多等待给定时间以完成计算,然后检索其结果(如果可用)
public V get(long timeout, TimeUnit unit)
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
}
public static void main(String[] args) {
// 线程开启之后需要执行里面的call方法
MyCallable call = new MyCallable();
// //可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
FutureTask<Integer> future = new FutureTask<Integer>(call);
Thread thread = new Thread(future);
thread.start();
try {
// main 线程阻塞, 等待 callable 线程执行完毕, 返回结果
int a = future.get();
System.out.println(a);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
三种实现方式的对比:
- 实现Runnable、Callable接口
- 好处: 扩展性强,实现该接口的同时还可以继承其他的类。很容易的实现资源共享(一个实例多个线程运行)
- 缺点: 编程相对复杂,不能直接使用Thread类中的方法
- 继承Thread类
- 好处: 编程比较简单,可以直接使用Thread类中的方法。资源共享麻烦
- 缺点: 可以扩展性较差,不能再继承其他的类
线程池
线程池的优势:
- 降低系统资源的消耗
- 提高系统的响应速度
- 方便线程的并发数管控,统一分配,调优,提供资源使用率
为什么使用线程池: - 重用线程池的线程,避免因为线程的创建和销毁所带来的性能开销。
- 有效控制线程池的最大并发数,避免大量的线程之间因抢占系统资源而阻塞。
- 能够对线程进行简单的管理,并提供一下特定的操作如:可以提供定时、定期、单线
程、并发数控制等功能。
Executor和ExecutorService
Executor:一个接口,其定义了一个接收Runnable
对象的方法executor
,其方法签名为executor(Runnable command)
,该方法接收一个Runable
实例,它用来执行一个任务,任务即一个实现了Runnable
接口的类,一般来说,Runnable
任务开辟在新线程中的使用方法为:new Thread(new RunnableTask()).start()
,但在Executor
中,可以使用Executor
而不用显示地创建线程:executor.execute(new RunnableTask()) ;
Executor
接口并不严格要求执行是异步的。
ExecutorService: 是一个比Executor
使用更广泛的子类接口,其提供了生命周期管理的方法,返回Future
对象,以及可跟踪一个或多个异步任务执行状况返回Future
的方法;可以调用ExecutorService
的shutdown()
方法来平滑地关闭ExecutorService
,调用该方法后,将导致ExecutorService
停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService
。因此我们一般用该接口来实现和管理多线程。
Thread类
构造方法
Thread
中常用的构造方法有:
public Thread()
:分配一个新的线程对象。public Thread(String name)
:分配一个指定名字的新的线程对象。public Thread(Runnable target)
:分配一个带有指定目标新的线程对象。public Thread(Runnable target,String name)
:分配一个带有指定目标新的线程对象并指定名字。
@Test
public void newInstance(){
Thread thread = new Thread();
Thread thread1 = new Thread("thread - 1a");
}
(Fields
)
//线程可以拥有的最小优先级
public final static int MIN_PRIORITY = 1;
//分配给线程的默认优先级
public final static int NORM_PRIORITY = 5;
//线程可以拥有的最大优先级
public final static int MAX_PRIORITY = 10;
这几个属性用于表示线程的优先级(后续会介绍)
常用方法
Thread
类提供了大量控制和操作线程的方法,其中常用的有:
-
public static Thread currentThread()
:返回对当前正在执行的线程对象的引用。Thread.currentThread(); // 执行这句代码的线程
-
public void run()
: 表示线程的任务。- 如果这个线程是使用单独的
Runnable
运行对象构造的,则Runnable
对象的run
方法; 否则,此方法不执行任何操作并返回。所有Thread
的子类应该覆盖(重写)此方法
- 如果这个线程是使用单独的
-
public synchronized void start()
: 线程开始执行;- Java虚拟机调用此线程的
run
方法。结果是两个线程同时运行:当前线程(从调用返回到start
方法)和另一个线程(执行其run
方法), 多次调用此方法是不合法的。
- Java虚拟机调用此线程的
-
public static native void sleep(long millis)
:使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。 线程不会丢失任何CPU的所有权。//线程休眠5S Thread.sleep(5000);
-
public static void sleep(long millis, int nanos)
:使当前正在执行的线程以指定的毫秒数加上指定的纳秒数暂停(暂时停止执行) -
public final String getName()
:返回此线程的名称Thread.currentThread().getName();//获取当前线程名称
-
public final synchronized void setName(String name)
:将此线程的名称更改为等于参数name -
public final int getPriority()
:返回此线程的优先级 -
public final void setPriority(int newPriority)
:更改此线程的优先级,1~10之间 -
public final native boolean isAlive()
:测试这个线程是否活着。 如果一个线程已经启动并且尚未死亡,那么线程是活着的 -
public final void join()
:等待线程死亡,等同于join(0)
-
public final synchronized void join(long millis)
:等待这个线程死亡的时间最多为millis毫秒。 0的超时意味着永远等待-
join
可以理解为当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。(让父线程等待子线程结束之后才能继续运行)public static void main(String[] args){ MyThread thread = new MyThread(); thread.start(); try { // main 执行, main 等待 thread 执行完 thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } }
-
-
public void interrupt()
:中断这个线程,线程的中断状态标记为true
@Deprecated(since="1.2") public final void stop()
立即终止线程(废弃的)
-
public static boolean interrupted()
:测试当前线程是否中断。 该方法可以清除线程的中断状态(设置中断状态为False
) 。 换句话说,如果这个方法被连续调用两次,那么第二个调用将返回false(除非当前线程再次中断,在第一个调用已经清除其中断状态之后,在第二个调用之前已经检查过)。 -
public boolean isInterrupted()
:测试这个线程是否被中断。 线程的中断状态不受此方法的影响。 -
public static native void yield()
:导致当前执行线程处于让步状态。如果有其他可运行线程具有至少与此线程同样高的优先级,那么这些线程接下来会被调度。并不一定会让出去。 -
public State getState()
:返回此线程的状态,返回值是Thread
的一个内部类,枚举类State
。线程状态可以是:NEW
尚未启动的线程处于此状态。RUNNABLE
在Java虚拟机中执行的线程(可以运行的线程)处于此状态。BLOCKED
被阻塞等待监视器锁定的线程处于此状态。WAITING
正在等待另一个线程执行特定动作的线程处于此状态。TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。 (sleep(1000)
,join(1000)
)TERMINATED
已退出的线程处于此状态。
线程状态
线程状态 | 具体含义 |
---|---|
NEW | 一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程象,没有线程特征。 |
RUNNABLE | 当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的度。 |
BLOCKED | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
WAITING | 一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。 |
TIMED_WAITING | 一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。 |
TERMINATED | 一个完全运行完成的线程的状态。也称之为终止状态、结束状态 |
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有六种状态。在API中 java.lang.Thread.State
这个枚举中给出了六种线程状态:
我们不需要去研究这几种状态的实现原理,我们只需知道在做线程操作中存在这样的状态。那我们怎么去理解这几个状态呢,新建与被终止还是很容易理解的,我们就研究一下线程从Runnable
(可运行)状态与非运行状态之间的转换问题。
| TERMINATED | 一个完全运行完成的线程的状态。也称之为终止状态、结束状态 |
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有六种状态。在API中 java.lang.Thread.State
这个枚举中给出了六种线程状态:
我们不需要去研究这几种状态的实现原理,我们只需知道在做线程操作中存在这样的状态。那我们怎么去理解这几个状态呢,新建与被终止还是很容易理解的,我们就研究一下线程从Runnable
(可运行)状态与非运行状态之间的转换问题。