文章目录
概述
线程与进程
进程是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行。
一个进程最少有一个线程。当某个进程中正在运行的用户线程数为0时,该进程终止。
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程
线程调度
分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间
抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性)
Java使用的为抢占式调度。.
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核心而言,某个时刻只能执行一个线程,而CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
同步与异步
同步: 排队执行 , 效率低,但是数据安全
异步 :同时执行 , 效率高,但是数据不安全
java中的三种线程实现方式
继承 Thread 类
public class Thread
extends Object
implements Runnable
可以通过继承Thread类并重写run方法实现多线程,调用的时候调用start方法执行。
示例代码
创建子线程输出“Hello Thread”示例如下:
class MyThread extends Thread {
@Override
public void run() { // 重写run方法
System.out.println("Hello Thread"); // 输出
}
}
public class Main {
public static void Main(String[] args) {
MyThread t = new MyThread(); // 创建新的MyThread对象
t.start(); // 调用start方法执行。start方法来自父类Thread
}
}
输出结果为
Hello Thread
常用方法
构造方法:
构造器 | 描述 |
---|---|
Thread() | 分配新的 Thread 对象。 |
Thread(Runnable target) | 分配新的 Thread 对象。 |
Thread(Runnable target, String name) | 分配新的 Thread 对象。 |
Thread(String name) | 分配新的 Thread 对象。 |
参数名称 | 参数类型 | 参数含义 |
---|---|---|
target | Runnable | 要执行的运行对象 |
name | String | 线程名称 |
普通方法:
变量和类型 | 方法 | 描述 |
---|---|---|
void | start() | 导致此线程开始执行; Java虚拟机调用此线程的run方法 |
void | run() | 如果此线程是使用单独的Runnable运行对象构造的,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回 |
void | interrupt() | 中断此线程。调用此方法时线程内部的部分语句会抛出InterruptException通知线程需要中止 |
static void | sleep(long millis) | 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性 |
static void | sleep(long millis, int nanos) | 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性 |
static Thread | currentThread() | 返回对当前正在执行的线程对象的引用 |
String | getName() | 返回此线程的名称 |
void | setName(String name) | 将此线程的名称更改为等于参数 name |
Thread.State | getState() | 返回此线程的状态 |
void | setDaemon(boolean on) | 将此线程标记为守护线程或用户线程,on为true表示设置为守护线程 |
int | getPriority() | 返回此线程的优先级 |
void | setPriority(int newPriority) | 更改此线程的优先级 |
实现 Runnable 接口
public interface Runnable
可以新建类,重写Runnable,并创建Thread传入实现了Runnable的类实现多线程。
用Runnable的代码创建可分为如下三步:
- 创建任务对象
- 创建线程并为其分配任务
- 执行这个线程
示例代码
创建子线程输出“Hello Thread”示例如下:
class MyClass implements Runnable {
@Override
public void run() { // 实现run方法
System.out.println("Hello Thread"); // 输出
}
}
public class Main {
public static void main(String[] args) {
MyClass myClass = new MyClass(); // 1.创建任务对象
Thread t = new Thread(myClass); // 2.创建线程并为其分配任务
t.start(); // 3.执行这个线程
}
}
输出结果为
Hello Thread
相比于继承Thread,实现Runnable有如下好处:
- 通过创建任务,然后给线程分配的方式实现多线程,更适合多个线程同时执行相同任务的情况
- 可以避免单继承带来的局限性
- 任务与线程本身分离,提高了程序的健壮性
- 线程池技术只接受Runnable类型的任务,不接受Thread类型的线程
方法摘要
变量和类型 | 方法 | 描述 |
---|---|---|
void | run() | 当使用实现接口 Runnable 的对象来创建线程时 启动该线程会导致在该单独执行的线程中调用该对象的 run 方法 |
实现 Callable 接口
实现Callable除了需要使用Thread类之外,还需要涉及到Callable接口和FutureTask类。它们的定义如下:
public interface Callable
public class FutureTask extends Object implements RunnableFuture
示例代码
创建子线程输出“Hello Thread”示例如下:
class MyClass<V> implements Callable<V> {
public V v;
public <V> MyClass(V v) {
this.v = v;
}
@Override
public <V> call() {
return v; // 把上面的对象返回去
}
}
public class Main {
public static void main(String[] args) {
MyClass<String> myClass = new MyClass<String>();
FutureTask<String> task = new FutureTask<>(myClass);
new Thread(task).start();
System.out.println(task.get());
}
}
输出结果为
Hello Thread
常用方法
Callable
变量和类型 | 方法 | 描述 |
---|---|---|
V | call() | 计算结果,如果无法执行,则抛出Exception。 其中V为泛型 |
FutureTask
下表中列出来的常用方法均属于接口java.util.concurrent.Future。
变量和类型 | 方法 | 描述 |
---|---|---|
boolean | cancel(boolean mayInterruptIfRunning) | 尝试取消执行此任务。 |
V | get() | 如果需要等待计算完成,然后检索其结果。 其中V为泛型。 |
V | get(long timeout, TimeUnit unit) | 如果需要,最多等待计算完成的给定时间,然后检索其结果(如果可用)。其中V为泛型。 |
boolean | isCancelled() | 如果此任务在正常完成之前取消,则返回 true 。 |
boolean | isDone() | 如果此任务完成,则返回 true 。 |
线程安全
在多个线程“同时”操作一个数据时,可能会出现一个线程的操作尚未完成、其它线程就操作修改了这个线程需要操作的数据导致结果的不正确,因此在某些地方可以通过上锁的形式保证其它线程在需要对相关数据进行连续操作时不进行插足。
比较常用的上锁方法有3种:同步代码块、同步方法、显式锁。
同步代码块
语法格式
synchronized(obj) {
// TODO:自己的代码块
}
当某个线程执行到这一个代码块时,会检查obj对象是否已被上锁。如果没有,上锁并执行,执行完毕后解锁;如果有,等待对象解锁。
这种情况的锁对象是obj对象。
同步方法
用法:在类中需要同步的方法前用 synchronized 修饰。例如:
public synchronized void start() {}
同步方法的锁定对象为this,即该方法所属的对象
static同步方法锁定的是 类名.class
显式锁
上文的两种锁称为隐式锁。显式锁顾名思义,就是直接创建锁对象完成上锁解锁的操作。
说明如下:
- 创建锁使用的语句:Lock l = new ReentrantLock();
- 需要上锁的地方调用 l.lock() 。当其它线程运行至此发现未上锁则上锁,否则等待锁被解开;
- 需要解锁的地方调用 l.unlock() 。
在此也需要提及“公平锁”与“不公平锁”的概念
对于后到的线程:
公平锁:排队执行
不公平锁:抢占执行(java默认)
可以使用一参构造方法ReentrantLock(fair)指定锁的类型,fair传入true表示公平锁。
线程死锁
本部分内容参考自https://blog.csdn.net/weixin_43213517/article/details/90314004
概述
线程死锁是指两个或两个以上的线程互相持有对方所需要的资源,由于 synchronized 的特性,一个线程持有一个资源,或者说获得一个锁,在该线程释放这个锁之前,其它线程是获取不到这个锁的,而且会一直死等下去,因此这便造成了死锁。
产生条件
互斥条件:一个资源,或者说一个锁只能被一个线程所占用,当一个线程首先获取到这个锁之后,在该线程释放这个锁之前,其它线程均是无法获取到这个锁的。
占有且等待:一个线程已经获取到一个锁,再获取另一个锁的过程中,即使获取不到也不会释放已经获得的锁。
不可剥夺条件:任何一个线程都无法强制获取别的线程已经占有的锁
循环等待条件:线程A拿着线程B的锁,线程B拿着线程A的锁。
如何避免
加锁顺序:线程按照相同的顺序加锁。
加锁时限,线程获取锁的过程中限制一定的时间,如果给定时间内获取不到,就算了,别勉强自己。这需要用到Lock的一些API。
线程状态
线程状态描述于枚举类 Thread.State。共有如下六种:
六种状态:
枚举条目 | 描述 |
---|---|
BLOCKED | 线程的线程状态被阻塞等待监视器锁定。 |
NEW | 尚未启动的线程的线程状态。 |
RUNNABLE | 可运行线程的线程状态。 |
TERMINATED | 终止线程的线程状态。 |
TIMED_WAITING | 具有指定等待时间的等待线程的线程状态。 |
WAITING | 等待线程的线程状态。 |
等待与唤醒
在java.lang.Object对象上,定义了如下三个方法:
返回类型 | 方法 | 描述 |
---|---|---|
void | wait() | 导致当前线程等待它被唤醒,通常是通知或中断。 |
void | wait(long timeoutMillis) | 导致当前线程等待它被唤醒,通常是通知或中断,或者直到经过一定量的实时。 |
void | wait(long timeoutMillis, int nanos) | 导致当前线程等待它被唤醒,通常是通知或中断 ,或者直到经过一定量的实时。 |
void | notify() | 唤醒正在此对象监视器上等待的单个线程。 |
void | notifyAll() | 唤醒等待此对象监视器的所有线程。 |
当一个线程执行某个对象的 wait 方法时,这个线程会进入等待状态,等待其它线程调用 notify 唤醒在这个对象上等待着的、这个线程。
线程池
线程池即为存放线程的容器。
普通线程的执行流程:
创建线程 → 创建任务 → 执行任务 → 关闭线程
其中当任务很简单时,创建/关闭线程会浪费大量时间
可以构造出一个存放线程的“池子”,即线程池。线程池中的线程可以实现重复使用,从而节省了创建关闭线程的时间。
四种常见线程池
缓存线程池:不定长线程池
定长线程池:池未满则创建新线程
单线程线程池:只有一个线程的线程池,用于排队执行
周期性任务定长线程池:某个时机触发时自动执行
ExecutorService:前三个线程池共用的接口
service.execute(Runnable); // 指挥线程池执行Runnable的任务
缓存线程池 cached
线程池长度没有限制
ExecutorService service = Executors.newCachedThreadPool(); // 创建
定长线程池 fixed
线程池定长
ExecutorService service = Executors.newFixedThreadPool(nThreads); // 其中nThreads的数值表示线程池长度
单线程线程池 single
ExecutorService service = Executors.newSingleThreadExecutor();
周期定长线程池 scheduled
ScheduledExecutorService 是 ExecutorService 的子接口
ScheduledExecutorService service = Executors.newScheduledThreadExecutor(corePoolSize); // 参数表示线程池数
执行方法也不是execute,而是schedule
schedule的方法参数为:
参数1,定时执行的Runnable任务
参数2,时长数字 delay
参数3,时长数字的时间单位,TimeUnit常量决定
另一种方法:scheduleAtFixedRate
参数1,Runnable
参数2,延迟时长数字initialDelay
参数3,周期period
参数4,时长数字的单位
含义:初期等待initialDelay后第一次执行,每隔period执行一次