并发编程专题-多线程基础
什么是并发编程
-
并发和并行
- 并发:指两个或多个事件在同一个时间段内发生(多线程是并发)
- 并行:指两个或多个事件在同一时刻发生(同时发生)
-
并发编程
使用Java语言中的多线程技术实现两个或多个事件在同一时间段内同时发生
什么是线程
线程是操作系统知识背景下的一个基本概念,所以在理解线程时,我们也要对与线程相关的操作系统中基本概念做个了解。
操作系统简单认识(这里单独开一篇文章)
-
程序和进程
-
程序是什么
- 程序(Program或Procedure)是一组用计算机语言编写的命令序列的集合 。
- 通俗来说,程序就是一堆没用运行的文件。
-
什么是进程
- 进程是程序在某个数据集合上的一次运行活动。
- 通俗的解释:进程就是运行在内存中的程序
- 进程是系统进行资源分配和调度的一个基本单位。
-
从程序到进程
- 在静态的时候,程序是存储在磁盘上面的,进程是把程序的逻辑加载到内存的运行过程。我们需要把程序从磁盘读取到内存,通过操作寄存器,使用CPU执行程序,这个时候程序就变成了进程(进行中的程序)
-
程序和进程之间的关系和区别
-
程序并不能单独运行,只有将程序装载到内存中,操作系统为它分配资源才能运行,而这种执行的「程序」就称之为进程 。进程是资源分配和独立运行的基本单元。
-
程序是指令的有序集合,其本身没有任何运行的含义,是一个静态的概念;
-
进程是程序在处理机上的一次执行过程,是运行中的程序,是一个动态的概念。
-
一个程序中至少有一个进程。
- 程序可作为一种软件材料长期存在;
- 进程是有一定生命周期的,是暂时存在的。
- 同一程序可以对应多个进程。也就是说同一程序同时运行于若干个数据集合(比如内存)上,它属于若干个不同的进程。
-
-
-
进程和线程
-
什么是线程
- 线程是进程中某个单一顺序的控制流,指运行中的程序的调度单位 。
-
线程和进程之间的关系
- 进程是所有线程的集合,每一个线程是进程中的一条执行路径 。
- 一个程序至少有一个进程,一个进程至少有一个线程。
-
线程和进程之间的区别
- 进程和线程的主要差别在于它们是不同的操作系统资源管理方式。
- 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。
- 线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
- 线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源(线程的运行中需要使用计算机的内存资源和CPU),但它可与同属一个进程的其它线程共享进程所拥有的全部资源。通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
-
多线程有什么用?应用场景
怎么使用线程
Java中创建线程的两种方式
-
继承Thread类方式创建线程(不推荐)
Thread是Java定义的线程类,用于描述线程。
-
使用步骤:
-
1.自定义类继承Thread
-
2.重写run方法,线程所要处理的任务逻辑就写在run方法中
-
3.new线程对象,并使用start方法,开启线程
-
/** * 继承Thread类 */ public class ThreadDemo001 extends Thread{ /** * 线程所要执行的逻辑 */ @Override public void run() { System.out.println("你是我的关键字......"); } public static void main(String[] args) { ThreadDemo001 threadDemo001 = new ThreadDemo001(); // 开启线程 threadDemo001.start(); } }
-
-
-
实现Runnable接口方式创建线程(推荐使用)
Runnable是官方提供发一个接口,用于描述线程所需执行的任务逻辑。开发者可以在自定义类实现该接口,在该接口中实现线程所需要执行的任务逻辑。
-
使用步骤
-
自定义类实现Runnable接口
-
重写Runnable中run方法,在run方法中写自己的逻辑
-
创建Thread对象,并在创建时,使用Thread(Runnable task)构造方法
-
使用Thread对象的start方法开启线程。
-
public class DemoRunnable implements Runnable { @Override public void run() { System.out.println("其实都没有......."); } public static void main(String[] args) { Thread thread = new Thread(new DemoRunnable()); thread.start(); } }
-
-
-
两种创建方式的优缺点
日常开发中推荐使用实现Runnable接口的方式。
理由:
- 在JAVA语言中类和类之间是单继承的
- 接口和接口之间是多继承的.
- 如果你继承了Thread类之后就再也不能继承其他的类了,这在实际开发中是相当不方便的.而实现Runnable接口就不会有这个麻烦了,因为接口是多继承的,你实现一个接口之后只要你需要就可以继续实现其他的接口而没有任何限制
常用的API
这里先介绍一下多线程场景中常用的API,更多的就不一一列举
方法名 | 调用方式 | 作用 |
---|---|---|
void run() | thread对象实例 | 重写线程逻辑的地方,开发中通常使用runnable接口代替 |
void start() | thread对象实例 | 线程启动,使用线程必须要使用该方法启动线程 |
Thread currentThread() | Thread类静态方法 | 获取当前对象所在的线程对象 |
String getName() | thread对象实例 | 获取线程的名称 |
void setName(String name) | thread对象实例 | 设置线程的名称。 |
long getId() | thread对象实例 | 获取线程的ID,每个线程都会拥有唯一的线程ID,不可改 |
void join() | thread对象实例 | 当在主线程当中执行到t1.join()方法时,就认为主线程应该把执行权让给t1(主要用于主线程和子线程之间) |
void sleep(long millis) | Thread类静态方法 | 让当前线程进入休眠状态(生命周期中的阻塞状态),可以指定休眠时间,单位:毫秒 |
void setDaemon(boolean on) | thread对象实例 | 将此线程标记为daemon线程或用户线程。true:将用户线程设置为守护线程。false:当前线程为用户线程。默认是false |
int getPriority() | thread对象实例 | 返回此线程的优先级。 |
void setPriority(int newPriority) | thread对象实例 | 更改此线程的优先级。Java 线程优先级使用 1 ~ 10 的整数表示:最低优先级 1:Thread.MIN_PRIORITY 最高优先级 10:Thread.MAX_PRIORITY 普通优先级 5:Thread.NORM_PRIORITY |
线程的生命周期
线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。
-
新建状态
- 当用new操作符创建一个线程时, 例如new Thread®,线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码
-
就绪状态
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的**start()**方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。
-
阻塞状态
线程运行过程中,可能由于各种原因进入阻塞状态:
- 线程通过调用sleep方法进入睡眠状态;
- 线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
- 线程试图得到一个锁,而该锁正被其他线程持有;
- 线程在等待某个触发条件;
-
运行状态
当线程获得CPU时间后,它才进入运行状态,真正开始**执行run()**方法
-
死亡状态
有两个原因会导致线程死亡:
- run方法正常退出而自然死亡,
- 一个未捕获的异常终止了run方法而使线程猝死。
- 为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.
有两个原因会导致线程死亡:
- run方法正常退出而自然死亡,
- 一个未捕获的异常终止了run方法而使线程猝死。
- 为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.