autojs多线程_多线程、线程池、内置锁⾯试题(⼀⽹打净,持
续更新)-疯狂创客圈
基础知识
为什么要使⽤多线程(并发编程的优点)
充分利⽤多核CPU的计算能⼒:通过并发编程的形式可以将多核CPU的计算能⼒发挥到极致,性能得到提升
⽅便进⾏业务拆分,提升系统并发能⼒和性能:在特殊的业务场景下,先天的就适合于并发编程。现在的系统动不动就要求百万级甚⾄千万级的并发量,⽽多线程并发编程正是开发⾼并发系统的基础,利⽤好多线程机制可以⼤⼤提⾼系统整体的并发能⼒以及性能。⾯对复杂业务模型,并⾏程序会⽐串⾏程序更适应业务需求,⽽并发编程更能吻合这种业务拆分 。
多线程有什么缺点(并发编程的缺点)
并发编程的⽬的就是为了能提⾼程序的执⾏效率,提⾼程序运⾏速度,但是并发编程并不总是能提⾼程序运⾏速度的,⽽且并发编程可能会遇到很多问题,⽐如:内存泄漏、上下⽂切换、线程安全、死锁等问题。
并发编程三要素是什么?在 Java 程序中怎么保证多线程的运⾏安全?
并发编程三要素(线程的安全性问题体现在):
原⼦性:原⼦,即⼀个不可再被分割的颗粒。原⼦性指的是⼀个或多个操作要么全部执⾏成功要么全部执⾏失败。
可见性:⼀个线程对共享变量的修改,另⼀个线程能够⽴刻看到。(synchronized,volatile)
有序性:程序执⾏的顺序按照代码的先后顺序执⾏。(处理器可能会对指令进⾏重排序)
出现线程安全问题的原因:
线程切换带来的原⼦性问题
缓存导致的可见性问题
编译优化带来的有序性问题
解决办法:
JDK Atomic开头的原⼦类、synchronized、LOCK,可以解决原⼦性问题
synchronized、volatile、LOCK,可以解决可见性问题
Happens-Before 规则可以解决有序性问题
并⾏和并发有什么区别?
并发:多个任务在同⼀个 CPU 核上,按细分的时间⽚轮流(交替)执⾏,从逻辑上来看那些任务是同时执⾏。
并⾏:单位时间内,多个处理器或多核处理器同时处理多个任务,是真正意义上的“同时进⾏”。
串⾏:有n个任务,由⼀个线程按顺序执⾏。由于任务、⽅法都在⼀个线程执⾏所以不存在线程不安全情况,也就不存在临界区的问题。
做⼀个形象的⽐喻:
并发 = 两个队列和⼀台咖啡机。
并⾏ = 两个队列和两台咖啡机。
串⾏ = ⼀个队列和⼀台咖啡机。
一个线程可以包含多个进程
线程和进程区别
什么是多线程,多线程的优劣?
多线程:多线程是指程序中包含多个执⾏流,即在⼀个程序中可以同时运⾏多个不同的线程来执⾏不同的任务。
多线程的好处:
可以提⾼ CPU 的利⽤率。在多线程程序中,⼀个线程必须等待的时候,CPU 可以运⾏其它的线程⽽不是等待,这样就⼤⼤提⾼了程序的效率。也就是说允许单个程序创建多个并⾏执⾏的线程来完成各⾃的任务。
多线程的劣势:
线程也是程序,所以线程需要占⽤内存,线程越多占⽤内存也越多;
多线程需要协调和管理,所以需要 CPU 时间跟踪线程;
线程之间对共享资源的访问会相互影响,必须解决竞⽤共享资源的问题。
什么是线程和进程?
进程
⼀个在内存中运⾏的应⽤程序。每个进程都有⾃⼰独⽴的⼀块内存空间,⼀个进程可以有多个线程,⽐如在Windows系统中,⼀个运⾏的就是⼀个进程。
线程
进程中的⼀个执⾏任务(控制单元),负责当前进程中程序的执⾏。⼀个进程⾄少有⼀个线程,⼀个进程可以运⾏多个线程,多个线程可共享数据。
进程与线程的区别
线程具有许多传统进程所具有的特征,故⼜称为轻型进程(Light—Weight Process)或进程元;⽽把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有⼀个线程的任务。在引⼊了线程的操作系统中,通常⼀个进程都有若⼲个线程,⾄少包含⼀个线程。
根本区别:进程是操作系统资源分配的基本单位,⽽线程是处理器任务调度和执⾏的基本单位
资源开销:每个进程都有独⽴的代码和数据空间(程序上下⽂),程序之间的切换会有较⼤的开销;线程可以看做轻量级的进程,同⼀类线程共享代码和数据空间,每个线程都有⾃⼰独⽴的运⾏栈和程序计数器(PC),线程之间切换的开销⼩。
包含关系:如果⼀个进程内有多个线程,则执⾏过程不是⼀条线的,⽽是多条线(线程)共同完成的;线程是进程的⼀部分,所以线程也被称为轻权进程或者轻量级进程。
内存分配:同⼀进程的线程共享本进程的地址空间和资源,⽽进程之间的地址空间和资源是相互独⽴的
影响关系:⼀个进程崩溃后,在保护模式下不会对其他进程产⽣影响,但是⼀个线程崩溃整个进程都死掉。所以多进程要⽐多线程健壮。
执⾏过程:每个独⽴的进程有程序运⾏的⼊⼝、顺序执⾏序列和程序出⼝。但是线程不能独⽴执⾏,必须依存在应⽤程序中,由应⽤程序提供多个线程执⾏控制,两者均可并发执⾏
什么是上下⽂切换?
多线程编程中⼀般线程的个数都⼤于 CPU 核⼼的个数,⽽⼀个 CPU 核⼼在任意时刻只能被⼀个线程使⽤,为了让这些线程都能得到有效执⾏,CPU 采取的策略是为每个线程分配时间⽚并轮转的形式。
当⼀个线程的时间⽚⽤完的时候就会重新处于就绪状态让给其他线程使⽤,这个过程就属于⼀次上下⽂切换。
概括来说就是:当前任务在执⾏完 CPU 时间⽚切换到另⼀个任务之前会先保存⾃⼰的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是⼀次上下⽂切换。
上下⽂切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒⼏⼗上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下⽂切换对系统来说意味着消耗⼤量的 CPU 时间,事实上,可能是操作系统中时间消耗最⼤的操作。
Linux 相⽐与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有⼀项就是,其上下⽂切换和模式切换的时间消耗⾮常少。
守护线程和⽤户线程有什么区别呢?
守护线程和⽤户线程
⽤户 (User) 线程:运⾏在前台,执⾏具体的任务,如程序的主线程、连接⽹络的⼦线程等都是⽤户线程
守护 (Daemon) 线程:运⾏在后台,为其他前台线程服务。也可以说守护线程是 JVM 中⾮守护线程的 “佣⼈”。⼀旦所有⽤户线程都结束运⾏,守护线程会随 JVM ⼀起结束⼯作
main 函数所在的线程就是⼀个⽤户线程啊,main 函数启动的同时在 JVM 内部同时还启动了好多守护线程,⽐如垃圾回收线程。
⽐较明显的区别之⼀是⽤户线程结束,JVM 退出,不管这个时候有没有守护线程运⾏。⽽守护线程不会影响 JVM 的退出。
注意事项:
1. setDaemon(true)必须在start()⽅法前执⾏,否则会抛出 IllegalThreadStateException 异常
2. 在守护线程中产⽣的新线程也是守护线程
3. 不是所有的任务都可以分配给守护线程来执⾏,⽐如读写操作或者计算逻辑
4. 守护 (Daemon) 线程中不能依靠 finally 块的内容来确保执⾏关闭或清理资源的逻辑。因为我们上⾯也说过了⼀旦所有⽤户线程都结
束运⾏,守护线程会随 JVM ⼀起结束⼯作,所以守护 (Daemon) 线程中的 finally 语句块可能⽆法被执⾏。
如何在 Windows 和 Linux 上查哪个线程cpu利⽤率最⾼?
windows上⾯⽤任务管理器看,linux下可以⽤ top 这个⼯具看。
1. 出cpu耗⽤厉害的进程pid, 终端执⾏top命令,然后按下shift+p 查出cpu利⽤最厉害的pid号
2. 根据上⾯第⼀步拿到的pid号,top -H -p pid 。然后按下shift+p,查出cpu利⽤率最厉害的线程号,⽐如top -H -p 1328
3. 将获取到的线程号转换成16进制,去百度转换⼀下就⾏
4. 使⽤jstack⼯具将进程信息打印输出,jstack pid号 > /tmp/,⽐如jstack 31365 > /tmp/
5. 编辑/tmp/⽂件,查线程号对应的信息
创建线程的四种⽅式
创建线程有哪⼏种⽅式?
创建线程有四种⽅式:
继承 Thread 类;
实现 Runnable 接⼝;
实现 Callable 接⼝;
使⽤ Executors ⼯具类创建线程池
1继承 Thread 类
步骤
1. 定义⼀个Thread类的⼦类,重写run⽅法,将相关逻辑实现,run()⽅法就是线程要执⾏的业务逻辑⽅法
2. 创建⾃定义的线程⼦类对象
3. 调⽤⼦类实例的star()⽅法来启动线程
public class MyThread extends Thread {
@Override
public void run() {
(().getName() + " run()⽅法正在执⾏...");
}
}
12345678
public class TheadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
();
(().getName() + " main()⽅法执⾏结束");
}
}
12345678910
运⾏结果
main main()⽅法执⾏结束
Thread-0 run()⽅法正在执⾏...
12
2实现 Runnable 接⼝
步骤
1. 定义Runnable接⼝实现类MyRunnable,并重写run()⽅法
2. 创建MyRunnable实例myRunnable,以myRunnable作为target创建Thead对象,该Thread对象才是真正的线程对象
3. 调⽤线程对象的start()⽅法
public class MyRunnable implements Runnable {
@Override
public void run() {
(().getName() + " run()⽅法执⾏中...");
}
}
12345678
public class RunnableTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
();
(().getName() + " main()⽅法执⾏完成");
}
}
12345678910
执⾏结果
main main()⽅法执⾏完成
Thread-0 run()⽅法执⾏中...
12
3实现 Callable 接⼝
步骤
1. 创建实现Callable接⼝的类myCallable
2. 以myCallable为参数创建FutureTask对象
3. 将FutureTask作为参数创建Thread对象
4. 调⽤线程对象的start()⽅法
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() {
(().getName() + " call()⽅法执⾏中...");
return 1;
}
}
123456789
public class CallableTest {
public static void main(String[] args) {
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable());
Thread thread = new Thread(futureTask);
();
try {
(1000);
("返回结果 " + ());
} catch (InterruptedException e) {
();
} catch (ExecutionException e) {
();
}
(().getName() + " main()⽅法执⾏完成");
}
}
12345678910111213141516171819
执⾏结果
Thread-0 call()⽅法执⾏中...
返回结果 1
main main()⽅法执⾏完成
123
4使⽤ Executors ⼯具类创建线程池
Executors提供了⼀系列⼯⼚⽅法⽤于创先线程池,返回的线程池都实现了ExecutorService接⼝。
主要有newFixedThreadPool,newCachedThreadPool,newSingleThreadExecutor,newScheduledThreadPool,后续详细介绍这四种线程池
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论