简介
说到线程就不得不提与之相关的另一概念:进程,那么什么是进程?与线程有什么关系呢?简单来说一个运行着的应用程序就是一个进程,比如:我启动了自己手机上的酷猫音乐播放器,这就是一个进程,然后我随意点了一首歌曲进行播放,此时酷猫启动了一条线程进行音乐播放,听了一部分,我感觉歌曲还不错,于是我按下了下载按钮,此时酷猫又启动了一条线程进行音乐下载,现在酷猫同时进行着音乐播放和音乐下载,此时就出现了多线程,音乐播放线程与音乐下载线程并行运行,说到并行,你一定想到了并发吧,那并行与并发有什么区别呢?并行强调的是同一时刻,并发强调的是一段时间内。线程是进程的一个执行单元,一个进程中至少有一条线程,进程是资源分配的最小单位,线程是 CPU 调度的最小单位。
程序是静态的,进程是动态的。多进程是操作系统中多个程序同时执行。
如果程序是采用多线程技术编写的,那么运行在单核单线程上运行的话,是并发执行;那么运行在多核多线程上运行的话,是并行执行的。
进程
狭义:进程是正在运行的程序的实例(一个程序)。
广义:进程是一个具有一定独立功能的程序,关于某个集合的一次运行活动。
进程(程序段 数据段 进程控制块PCB 进程标识符PID)
- 资源分配的基本单位
- 独立运行的基本单位
目的:改善资源利用率以及提高系统吞吐量
线程
线程是操作系统能够进行运算调试的最小单位,它被包含在进程中,是进程中实际动作单位。一个线程指的是进程中的一个单一顺序的控制流,一个进程中可以并发多个线程,每个线程执行不同的任务。
- 独立调度的基本单位
目的:减少程序并发执行是所付出的时空开销
线程是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
Java默认有2个线程:main线程、GC线程。
代码语言:javascript复制并发(单核):多个线程操作同一个资源。
并行(多核):多个线程操作多个资源。
并发编程的本质:充分利用CPU的资源
多线程就是分时利用CPU,宏观上让所有线程一起执行 ,也叫并发。
生命周期
代码语言:javascript复制public enum State {
NEW, //新生
RUNNABLE, //运行
BLOCKED, //阻塞
WAITING, //等待 死死等
TIMED_WAITING, //超时等
TERMINATED; //终止
}
- 使用new创建线程对象后,系统并没有提供运行所需的资源
- 使用start()方法启动线程后(RUNNING、RUNABLE)系统就分配资源(具备执行的资格),但是具体执行还得听后CPU调配
- RUNABLE状态的线程只能进入RUNNING状态或者意外终止(不可进入BLOCKED状态)
- BLOCKED状态可以进入RUNABLE状态(不可以进入RUNNING)
public class ThreadTest34 {
public static void main(String[] args) throws Exception{
Thread thread = new ThreadUser34();
System.out.println("线程在Main方法中的状态1:" thread.getState());
Thread.sleep(1000);
thread.start();
Thread.sleep(1000);
System.out.println("线程在Main方法中的状态2:" thread.getState());
}
}
class ThreadUser34 extends Thread{
public ThreadUser34() {
System.out.println("构造方法的状态:" Thread.currentThread().getState());
}
@Override
public void run() {
System.out.println("run方法中的状态:" Thread.currentThread().getState());
}
}
执行结果:
构造方法的状态:RUNNABLE
线程在Main方法中的状态1:NEW
run方法中的状态:RUNNABLE
线程在Main方法中的状态2:TERMINATED
创建线程
创建线程只有一种方式那就是构造Thread类,而实现线程的执行单元有两种方式:
- 第一种是重新Thread的run方法
- 第二种是实现Runnable接口的run方法,并将Runnable实例用作构造Thread的参数
- 第三种使用FutureTask,在创建Thread对象的时候传进去
Thread负责线程本身相关的职责和控制,而Runnable则负责逻辑执行单元的部分。
代码语言:javascript复制Thread的run源码:
@Override
public void run() {
//如果构造Thread时传入了Runnable,则会执行Runnable的run方法。
if (target != null) {
target.run();
}
//负责需要重新run方法
}
备注:线程的执行单元指的是run方法
继承Thread类创建
- 线程的启动顺序并不是按照start的顺序启动的,而是随机启动的
@Slf4j
public class ThreadTest {
public static void main(String[] args){
log.info("主线程ID:" Thread.currentThread().getId() ", 线程名字:" Thread.currentThread().getName());
Thread thread = new MyThread();
thread.start();
}
}
/**
* 通过继承Thread来实现
*/
@Slf4j
class MyThread extends Thread{
@Override
public void run() {
log.info("子线程ID: " Thread.currentThread().getId() ", 子线程名字: " Thread.currentThread().getName());
}
}
执行结果:
20:06:38.901 [main] INFO com.java.master.线程.ThreadTest - 主线程ID:1, 线程名字:main
20:06:38.904 [Thread-0] INFO com.java.master.线程.MyThread - 子线程ID: 11, 子线程名字: Thread-0
实现Runnable接口创建
代码语言:javascript复制@Slf4j
public class ThreadTest2 {
public static void main(String[] args){
log.info("主线程ID:" Thread.currentThread().getId() ", 线程名字:" Thread.currentThread().getName());
Thread thread = new Thread(new MyThread2());
thread.start();
}
}
@Slf4j
class MyThread2 implements Runnable{
@Override
public void run() {
log.info("子线程ID: " Thread.currentThread().getId() ", 子线程名字: " Thread.currentThread().getName());
}
}
执行结果:
20:13:48.236 [main] INFO com.java.master.线程.ThreadTest2 - 主线程ID:1, 线程名字:main
20:13:48.240 [Thread-0] INFO com.java.master.线程.MyThread2 - 子线程ID: 11, 子线程名字: Thread-0
注意:这种方式必须将Runnable作为Thread类的参数,然后通过Thread的start方法来创建一个新线程来执行该子任务
在Java中,这2种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,但是由于Java只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。
事实上,查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。
FutureTask方式
代码语言:javascript复制public class CallableTest {
public static void main(String[] args) throws Exception{
Callable<Integer> callable = new CallableService();
FutureTask<Integer> task = new FutureTask<>(callable);
Thread thread = new Thread(task);
thread.start();
System.out.println("线程返回值: " task.get());
}
}
class CallableService implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() "调用了callable方法");
return (int)(Math.random() * 10);
}
}
匿名内部类创建
代码语言:javascript复制public class ThreadTest3 {
public static void main(String[] args) {
//匿名内部类方式创建Thread
new Thread() {
@Override
public void run() {
enjoyMusic();
}
}.start();
//Lambda方式1,等价于上边的匿名内部类创建方式
new Thread(ThreadTest3::enjoyMusic).start();
//Lambda方式2,等价于上边的匿名内部类创建方式
new Thread(() -> {
enjoyMusic();
}).start();
}
public static void enjoyMusic() {
for (; ;) {
System.out.println("music music");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果:
music music
music music
music music
成员变量与线程安全
1. 不共享数据
每个线程都有各自的count变量,自己减少自己的count变量的值,这种情况就是不共享。
代码语言:javascript复制public class ThreadTest4 {
public static void main(String[] args) {
Thread thread1 = new MyThread4();
Thread thread2 = new MyThread4();
Thread thread3 = new MyThread4();
thread1.start();
thread2.start();
thread3.start();
}
}
class MyThread4 extends Thread {
private int count = 5;
@Override
public void run() {
while (count > 0) {
count --;
System.out.println(Thread.currentThread().getName() " - " count);
}
}
}
执行结果:
Thread-1 - 4
Thread-1 - 3
Thread-0 - 4
Thread-0 - 3
Thread-1 - 2
Thread-0 - 2
Thread-2 - 4
Thread-0 - 1
Thread-0 - 0
Thread-1 - 1
Thread-1 - 0
Thread-2 - 3
Thread-2 - 2
Thread-2 - 1
Thread-2 - 0
2. 共享变量
共享变量有概率出现不同线程使用相同的count的值,产生了“非线程安全”问题。
代码语言:javascript复制public class ThreadTest5 {
public static void main(String[] args) {
Thread thread = new MyThread5();
Thread thread1 = new Thread(thread);
Thread thread2 = new Thread(thread);
Thread thread3 = new Thread(thread);
thread1.start();
thread2.start();
thread3.start();
}
}
class MyThread5 extends Thread {
private int count = 5;
@Override
public void run() {
count --;
System.out.println(Thread.currentThread().getName() " - " count);
}
}
执行结果:
Thread-2 - 3
Thread-1 - 3
Thread-3 - 2
线程分类
线程分为两种,用户线程和守护线程。其实守护线程和用户线程区别不大,可以理解为特殊的用户线程。特殊就特殊在如果程序中所有的用户线程都退出了,那么所有的守护线程就都会被杀死,很好理解,没有被守护的对象了,也不需要守护线程了。
在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆。
所谓守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
用户线程
默认创建出来的线程都是用户线程。
守护线程
线程是否为守护线程和他的父类有很大关系,如果父类是正常线程,则子类线程也是正常线程,反之亦然。
代码语言:javascript复制thread.setDaemon(true);
备注:必须在线程启动start()方法之前设置。
代码语言:javascript复制代码分析:
public static void main(String[] args){
System.out.println("start");
Thread thread = new Thread() {
@Override
public void run() {
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
}catch (Exception e) {
e.printStackTrace();
}
}
}
};
// thread.setDaemon(true);
thread.start();
System.out.println("finish");
}
这段代码里边存在两个线程(main,thread),当main线程即使生命周期结束,JVM进程还是不能退出,因为还有一个非守护线程thread在执行。
如果将thread.setDaemon(true)注释放开,则当main线程执行完毕之后,JVM也随之退出,当然thread也就退出了。
备注:守护线程具备自动结束生命周期得特性,而非守护线程则不具备这样的特点。