网站首页 > java教程 正文
各位志同道合的朋友们大家好,我是一个一直在一线互联网踩坑十余年的编码爱好者,现在将我们的各种经验以及架构实战分享出来,如果大家喜欢,就关注我,一起将技术学深学透,我会每一篇分享结束都会预告下一专题
线程介绍
线程(Thread)是程序运行的执行单元,依托于进程存在。一个进程中可以包含多个线程,多线程可以共享一块内存空间和一组系统资源,因此线程之间的切换更加节省资源、更加轻量化,因而也被称为轻量级的进程。
什么是进程
进程(Processes)是程序的一次动态执行,是系统进行资源分配和调度的基本单位,是操作系统运行的基础,通常每一个进程都拥有自己独立的内存空间和系统资源。简单来说,进程可以被当做是一个正在运行的程序。
为什么需要线程
程序的运行必须依靠进程,进程的实际执行单元就是线程。
为什么需要多线程
多线程可以提高程序的执行性能。例如,有个 90 平方的房子,一个人打扫需要花费 30 分钟,三个人打扫就只需要 10 分钟,这三个人就是程序中的“多线程”。
线程使用
线程的创建,分为以下三种方式:
- 继承 Thread 类,重写 run 方法
- 实现 Runnable 接口,实现 run 方法
- 实现 Callable 接口,实现 call 方法
下面分别来看看线程创建和使用的具体代码。
1)继承 Thread 类
请参考以下代码:
class ThreadTest {
public static void main(String[] args) throws Exception {
MyThread thread = new MyThread();
thread.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread");
}
}
以上程序执行结果如下:
Thread
2)实现 Runnable 接口
请参考以下代码:
class ThreadTest {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
new Thread(runnable).start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable");
}
}
以上程序执行结果如下:
Runnable
3)实现 Callable 接口
请参考以下代码:
class ThreadTest {
public static void main(String[] args) throws Exception {
MyCallable callable = new MyCallable();
// 定义返回结果
FutureTask<String> result = new FutureTask(callable);
// 执行程序
new Thread(result).start();
// 输出返回结果
System.out.println(result.get());
}
}
class MyCallable implements Callable {
@Override
public String call() {
System.out.println("Callable");
return "Success";
}
}
以上程序执行结果如下:
Callable
Success
可以看出,Callable 的调用是可以有返回值的,它弥补了之前调用线程没有返回值的情况,它是随着 JDK 1.5 一起发布的。
4)JDK 8 创建线程
JDK 8 之后可以使用 Lambda 表达式很方便地创建线程,请参考以下代码:
new Thread(() -> System.out.println("Lambda Of Thread.")).start();
线程高级用法
线程等待
使用 wait() 方法实现线程等待,代码如下:
System.out.println(LocalDateTime.now());
Object lock = new Object();
Thread thread = new Thread(() -> {
synchronized (lock){
try {
// 1 秒钟之后自动唤醒
lock.wait(1000);
System.out.println(LocalDateTime.now());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
以上程序执行结果如下:
2019-06-22T20:53:08.776
2019-06-22T20:53:09.788
注意:当使用 wait() 方法时,必须先持有当前对象的锁,否则会抛出异常
java.lang.IllegalMonitorStateException。
线程唤醒
使用 notify()/notifyAll() 方法唤醒线程。
- notify() 方法随机唤醒对象的等待池中的一个线程;
- notifyAll() 唤醒对象的等待池中的所有线程。
使用如下:
Object lock = new Object();
lock.wait();
lock.notify();// lock.notifyAll();
线程休眠
// 休眠 1 秒
Thread.sleep(1000);
等待线程执行完成
Thread joinThread = new Thread(() -> {
try {
System.out.println("执行前");
Thread.sleep(1000);
System.out.println("执行后");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
joinThread.start();
joinThread.join();
System.out.println("主程序");
以上程序执行结果:
执行前
执行后
主程序
yield 交出 CPU 执行权
new Thread(){
@Override
public void run() {
for (int i = 1; i < 10; i++) {
if (i == 5) {
// 让同优先级的线程有执行的机会
this.yield();
}
}
}
}.start();
注意:yield 方法是让同优先级的线程有执行的机会,但不能保证自己会从正在运行的状态迅速转换到可运行的状态。
线程中断
使用 System.exit(0) 可以让整个程序退出;要中断单个线程,可配合 interrupt() 对线程进行“中断”。使用代码如下:
Thread interruptThread = new Thread() {
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
System.out.println("i:" + i);
if (this.isInterrupted()) {
break;
}
}
}
};
interruptThread.start();
Thread.sleep(10);
interruptThread.interrupt();
线程优先级
在 Java 语言中,每一个线程有一个优先级,默认情况下,一个线程继承它父类的优先级。可以使用 setPriority 方法设置(1-10)优先级,默认的优先级是 5,数字越大表示优先级越高,优先级越高的线程可能优先被执行的概率就越大。设置优先级的代码如下:
Thread thread = new Thread(() -> System.out.println("Java"));
thread.setPriority(10);
thread.start();
死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。比如,当线程 A 持有独占锁 a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 A B 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。死锁示意图如下所示:
死锁代码:
Object obj1 = new Object();
Object obj2 = new Object();
// 线程1拥有对象1,想要等待获取对象2
new Thread() {
@Override
public void run() {
synchronized (obj1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2) {
System.out.println(Thread.currentThread().getName());
}
}
}
}.start();
// 线程2拥有对象2,想要等待获取对象1
new Thread() {
@Override
public void run() {
synchronized (obj2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1) {
System.out.println(Thread.currentThread().getName());
}
}
}
}.start();
相关面试题
1.线程和进程有什么区别和联系?
答:从本质上来说,线程是进程的实际执行单元,一个程序至少有一个进程,一个进程至少有一个线程,它们的区别主要体现在以下几个方面:
- 进程间是独立的,不能共享内存空间和上下文,而线程可以;
- 进程是程序的一次执行,线程是进程中执行的一段程序片段;
- 线程占用的资源比进程少。
2.如何保证一个线程执行完再执行第二个线程?
答:使用 join() 方法,等待上一个线程的执行完之后,再执行当前线程。示例代码:
Thread joinThread = new Thread(() -> {
try {
System.out.println("执行前");
Thread.sleep(1000);
System.out.println("执行后");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
joinThread.start();
joinThread.join();
System.out.println("主程序");
3.线程有哪些常用的方法?
答:线程的常用方法如下:
- currentThread():返回当前正在执行的线程引用
- getName():返回此线程的名称
- setPriority()/getPriority():设置和返回此线程的优先级
- isAlive():检测此线程是否处于活动状态,活动状态指的是程序处于正在运行或准备运行的状态
- sleep():使线程休眠
- join():等待线程执行完成
- yield():让同优先级的线程有执行的机会,但不能保证自己会从正在运行的状态迅速转换到可运行的状态
- interrupted():是线程处于中断的状态,但不能真正中断线程
4.wait() 和 sleep() 有什么区别?
答:wait() 和 sleep() 的区别主要体现在以下三个方面。
- 存在类的不同:sleep() 来自 Thread,wait() 来自 Object。
- 释放锁:sleep() 不释放锁;wait() 释放锁。
- 用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll() 直接唤醒。
5.守护线程是什么?
答:守护线程是一种比较低级别的线程,一般用于为其他类别线程提供服务,因此当其他线程都退出时,它也就没有存在的必要了。例如,JVM(Java 虚拟机)中的垃圾回收线程。
6.线程有哪些状态?
答:在 JDK 8 中,线程的状态有以下六种。
- NEW:尚未启动
- RUNNABLE:正在执行中
- BLOCKED:阻塞(被同步锁或者 IO 锁阻塞)
- WAITING:永久等待状态
- TIMED_WAITING:等待指定的时间重新被唤醒的状态
- TERMINATED:执行完成
题目分析:JDK 8 线程状态的源码如下图所示:
7.线程中的 start() 和 run() 有那些区别?
答:start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复调用,而 start() 只能调用一次。
8.产生死锁需要具备哪些条件?
答:产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个线程使用;
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放;
- 不剥夺条件:线程已获得的资源,在末使用完之前,不能强行剥夺;
- 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系;
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
9.如何预防死锁?
答:预防死锁的方法如下:
- 尽量使用 tryLock(long timeout, TimeUnit unit) 的方法 (ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁;
- 尽量使用 Java. util. concurrent 并发类代替自己手写锁;
- 尽量降低锁的使用粒度,尽量不要几个功能用同一把锁;
- 尽量减少同步的代码块。
10.thread.wait() 和 thread.wait(0) 有什么区别?代表什么含义?
答:thread.wait() 和 thread.wait(0) 是相同的,使用 thread.wait() 内部其实是调用的 thread.wait(0),源码如下:
public final void wait() throws InterruptedException {
wait(0);
}
wait() 表示进入等待状态,释放当前的锁让出 CPU 资源,并且只能等程序执行 notify()/notifyAll() 方法才会被重写唤醒。
11.如何让两个程序依次输出 11/22/33 等数字,请写出实现代码?
答:使用思路是在每个线程输出信息之后,让当前线程等待一会再执行下一次操作,具体实现代码如下:
new Thread(() -> {
for (int i = 1; i < 4; i++) {
System.out.println("线程一:" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
for (int i = 1; i < 4; i++) {
System.out.println("线程二:" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
程序执行结果如下:
线程一:1
线程二:1
线程二:2
线程一:2
线程二:3
线程一:3
12.说一下线程的调度策略?
答:线程调度器选择优先级最高的线程运行,但是如果发生以下情况,就会终止线程的运行:
- 线程体中调用了 yield() 方法,让出了对 CPU 的占用权;
- 线程体中调用了 sleep() 方法,使线程进入睡眠状态;
- 线程由于 I/O 操作而受阻塞;
- 另一个更高优先级的线程出现;
- 在支持时间片的系统中,该线程的时间片用完。
总结
程序的运行依靠的是进程,而进程的执行依靠的是多个线程,多线程之间可以共享一块内存和一组系统资源,而多进程间通常是相互独立的。线程的创建有三种方式:继承 Thread 重写 run 方法,实现 Runnable 或 Callable 接口,其中 Callable 可以允许线程的执行有返回值,JDK 8 中也可以使用 Lambda 来更加方便的使用线程,线程是有优先级的,优先级从 1-10 ,数字越大优先级越高,也越早被执行。如果两个线程各自拥有一把锁的同时,又同时等待获取对方的锁,就会造成死锁。可以降低锁的粒度或减少同步代码块的范围或使用 Java 提供的安全类,来防止死锁的产生。
下一篇:Java 线程池
在公众号菜单中可自行获取专属架构视频资料,包括不限于 java架构、python系列、人工智能系列、架构系列,以及最新面试、小程序、大前端均无私奉献,你会感谢我的哈
往期精选(全集在wx公号【架构师修炼】)
分布式数据之缓存技术,一起来揭开其神秘面纱
分布式数据复制技术,今天就教你真正分身术
数据分布方式之哈希与一致性哈希,我就是个神算子
分布式存储系统三要素,掌握这些就离成功不远了
想要设计一个好的分布式系统,必须搞定这个理论
分布式通信技术之发布订阅,干货满满
分布式通信技术之远程调用:RPC
消息队列Broker主从架构详细设计方案,这一篇就搞定主从架构
消息中间件路由中心你会设计吗,不会就来学学
消息队列消息延迟解决方案,跟着做就行了
秒杀系统每秒上万次下单请求,我们该怎么去设计
【分布式技术】分布式系统调度架构之单体调度,非掌握不可
CDN加速技术,作为开发的我们真的不需要懂吗?
烦人的缓存穿透问题,今天教就你如何去解决
分布式缓存高可用方案,我们都是这么干的
每天百万交易的支付系统,生产环境该怎么设置JVM堆内存大小
你的成神之路我已替你铺好,没铺你来捶我
- 上一篇: java多线程编程,面试真的躲不开!
- 下一篇: Java多线程问题全解析:让面试官对你刮目相看!
猜你喜欢
- 2025-05-30 线程池的使用及ThreadPoolExecutor源码分析
- 2025-05-30 面试官:什么是虚拟线程?为什么要有虚拟线程?
- 2025-05-30 「超级详细」Java线程实现原理
- 2025-05-30 并发编程之ThreadPoolExecutor线程池原理解析
- 2025-05-30 阿里资深架构推荐学习四本实战书籍:MySQL+Redis+Kfaka+多线程
- 2025-05-30 杰哥教你面试之一百问系列:java多线程
- 2025-05-30 面试突击29:说一下线程池7个参数的含义?
- 2025-05-30 一个 tomcat 项目使用多个线程池还是一个线程池 ?
- 2025-05-30 一个注解 —— 完美实现分布式锁
- 2025-05-30 Java 线程的生命周期及各阶段状态
你 发表评论:
欢迎- 06-04C++优先级调度队列(Priority Queue)
- 06-04数据结构与算法-优先队列(优先队列 数组实现)
- 06-04什么是优先队列?(优先队列原理)
- 06-04终于有架构大牛把分布式系统概念讲明白了,竟然用了足足800页
- 06-04分布式事物如何保证接口请求顺序性?
- 06-04微服务下分布式事务模式的详细对比
- 06-04彻底掌握分布式事务2PC、3PC模型(分布式事务 三阶段)
- 06-04分布式事务最全详解(看这篇就够了)
- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)