多线程的爱之初体验:
多线程,就是在同一程序中同时执行多个任务的能力,就像是苦逼程序猿凯叔从单身到恋爱生活的转变。在单线程的世界里,程序就像一个孤独的程序猿,只能按部就班地逐一完成任务,好比单身时的生活,一个人做饭、洗碗、工作,一切都得自己来,顺序进行,效率受限。
而一旦引入多线程,情况就大不相同了。这就像程序猿突然找到了另一半,生活开始有了搭档,两个人可以同时处理不同的事情:一个做饭,另一个洗碗;一个忙着敲代码,另一个则处理文档,彼此协作,任务完成得又快又好。在程序中,多线程允许不同的线程并行处理不同的任务或同一任务的不同部分,大大提升了效率和响应速度,就好比恋爱中的两人互相支持,共同进步,生活变得更加高效和谐。
总结来说,多线程的引入如同单身到恋爱的转变,从独自承担所有责任到有了伴侣的协助与分担,不仅让程序执行变得更加灵活高效,也象征着生活中的合作与成长。
PS:但是这只是比喻,真正的凯叔的生活是一个人做饭、洗碗、工作,还得带吞金兽,还得上交"保护费"狗日的多线程。啊呸!
多线程的引入对比单线程来说给程序猿们更多无限遐想的空间,现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正式开发高并发系统的基础,利用好多线程机制可以大大提高系统的并发能力以及性能。多线程正象是一个完美的时间管理大师。
开启多线程:
敲黑板,划重点,面试必备有考点
Java多线程的实现方式主要有以下四种,每种方式有其特定的应用场景和优缺点:
- 继承Thread类
-
实现方式 :创建一个新的类继承
Thread
类,并重写其run()
方法,在run()
方法中定义需要并行执行的代码逻辑。然后创建该类的实例并调用start()
方法启动新线程。 -
区别与特点 :这种方式简单直接,但因为Java不支持多重继承,如果你的类已经继承了其他类,则无法再继承
Thread
类来创建线程。
-
java
public class ThreadDemo1 extends Thread {
@Override
public void run() {
System.out.println("继承Thread实现多线程,名 称:"+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
ThreadDemo1 threadDemo1 = new ThreadDemo1();
threadDemo1.setName("demo1");
threadDemo1.start();
System.out.println("主线程名称:"+Thread.currentThread().getName());
}
- 实现Runnable接口
- 实现方式 :定义一个类实现
Runnable
接口,并实现其run()
方法。然后将此Runnable
实例作为参数传递给Thread
类的构造函数,创建Thread
对象,最后通过Thread
对象的start()
方法启动线程。 - 区别与特点:由于Java支持接口的多重实现,这种方式更加灵活,可以在同一个类中实现多个接口,更适合复杂的类继承结构。
java
public class ThreadDemo2 implements Runnable {
@Override
public void run() {
System.out.println("通过Runnable实现多线程,名称:"+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
ThreadDemo2 threadDemo2 = new ThreadDemo2();
Thread thread = new Thread(threadDemo2);
thread.setName("demo2");
thread.start();
System.out.println("主线程名称:"+Thread.currentThread().getName());
}
//JDK8之后采⽤lambda表达式
public static void main(String[] args) {
Thread thread = new Thread(()->{
System.out.println("通过Runnable实现多线程,名称:"+Thread.currentThread().getName());
});
thread.setName("demo2");
thread.start();
System.out.println("主线程名称:"+Thread.currentThread().getName());
}
- 实现Callable接口 + FutureTask包装器
-
实现方式 :创建一个类实现
Callable
接口,该接口有一个call()
方法可以返回结果并抛出异常。然后使用FutureTask
包装器将Callable
对象转换为Runnable
,再将FutureTask
交给Thread
去执行,或者提交给ExecutorService
管理。 -
区别与特点 :与前两种方式相比,
Callable
可以返回结果并声明抛出异常,提供了更强的灵活性和错误处理能力,适用于需要获取线程执行结果的场景。
-
java
//缺点:jdk5以后才⽀持,需要重写call⽅法,结合多个类⽐如FutureTask和Thread类
public class MyTask implements Callable<Object> {
@Override
public Object call() throws Exception {
System.out.println("通过Callable实现多线程,名 称:"+Thread.currentThread().getName());
return "这是返回值";
}
}
public static void main(String[] args) {
FutureTask<Object> futureTask = new FutureTask<>(()->{
System.out.println("通过Callable实现多线程,名称:"+Thread.currentThread().getName());
return "这是返回值";
});
//FutureTask继承了Runnable,可以放在Thread中启动执⾏
Thread thread = new Thread(futureTask);
thread.setName("demo3");
thread.start();
System.out.println("主线程名 称:"+Thread.currentThread().getName());
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
//阻塞等待中被中断,则抛出
e.printStackTrace();
} catch (ExecutionException e) {
//执⾏过程发送异常被抛出
e.printStackTrace();
}
}
4.使用Executor框架(如ThreadPoolExecutor)
-
实现方式:通过Executors工厂类创建线程池(如固定大小线程池、缓存线程池、定时线程池等),然后提交Runnable或Callable任务给线程池执行。
-
实现方式:这种方式提供了线程管理和调度的高级功能,如线程复用、任务调度、线程池管理等,能有效控制线程数量,提高系统资源利用率,降低系统开销,是生产环境中推荐使用的多线程编程模式。
java
//优点:安全⾼性能,复⽤线程
//缺点: jdk5后才⽀持,需要结合Runnable进⾏使⽤
public class ThreadDemo4 implements Runnable {
@Override
public void run() {
System.out.println("通过线程池+runnable实现多线程,名称:"+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for(int i=0;i<10;i++){
executorService.execute(new ThreadDemo4());
}
System.out.println("主线程名称:"+Thread.currentThread().getName());
//关闭线程池
executorService.shutdown();
}
总结区别:
- 继承Thread类 和实现Runnable接口是最基本的实现方式,后者因为支持接口多重继承而更灵活。
- 实现Callable接口相比前两者,增加了返回结果和异常处理的能力,适合需要线程执行结果的场景。
- Executor框架引入了线程池的概念,提供了更高层次的抽象,简化了线程管理,提高了程序性能和可维护性,是现代多线程编程的首选方案。
同步与异步的恋爱哲学:
在Java多线程编程中,同步和异步是处理线程间交互和资源共享时两种基本的方法,它们在设计理念和实现机制上有着本质的区别,但又常常在实际应用中相辅相成,共同保证了程序的正确性和高效性。
就好比同步就是我给你发消息,你秒回,这就是同步;异步就是我给你发消息,你这时候正在跟别的哥哥聊的正嗨,晚上了看到消息,回了一个"哦,我要去洗澡了,下次聊",而你还贴心想"每次女神为了我都要专门去洗澡,这就是爱情"。舔狗不得House.
同步(Synchronization)
在Java中,同步主要用于保护共享资源不被多个线程同时访问而造成数据不一致或逻辑错误。主要有以下几种同步机制:
- synchronized关键字:这是Java中最常用的同步机制,它可以用来修饰方法或代码块。当一个线程访问某个对象的synchronized方法或代码块时,其他线程必须等待,直到当前线程完成操作。synchronized基于监视器锁(monitor lock)实现,确保了同一时刻只有一个线程可以执行受保护的代码。
- Lock接口及其实现类(如ReentrantLock):提供比synchronized更细粒度的锁控制,例如tryLock()方法尝试获取锁,可以设定超时时间,以及支持公平锁和非公平锁的选择。
- 原子类(AtomicInteger, AtomicBoolean等):通过CAS(Compare and Swap)操作实现无锁同步,适用于轻量级的同步需求。
异步(Asynchronous)
异步编程模型关注的是非阻塞操作,即当一个操作可能需要较长时间完成时(如I/O操作、网络通信等),程序不等待其完成,而是继续执行其他任务,待操作完成后通过回调、事件、Future/Promise等方式通知主线程或处理结果。
- 回调:异步操作完成后执行的函数,通常作为参数传递给异步方法。例如,在网络请求中,可以指定一个回调函数来处理响应数据。
- Future和CompletableFuture:Java提供的用于表示异步计算结果的类。Future可以查看计算是否完成,获取结果(可能阻塞),而CompletableFuture在此基础上增加了链式调用、组合异步操作的能力,支持更复杂的异步编程模式。
- Reactive Programming(反应式编程):通过Observable、Flowable等模型,以声明式的方式处理异步数据流,提供了强大的背压(backpressure)机制来应对生产者消费者速度不匹配问题。
同步与异步的区别和联系
区别:同步操作会导致线程等待,保证了操作的顺序执行和数据的一致性,但可能引起性能瓶颈;异步操作则不阻塞线程,提高了程序的并发能力,但编程复杂度相对较高,需要处理并发控制和结果回调。
联系:在实际应用中,同步和异步经常结合起来使用。例如,可以在异步处理完成后,通过同步代码更新共享状态,确保数据一致性。两者都是多线程编程中处理并发问题的有效工具,选择使用哪种方式取决于具体场景的需求和权衡。
线程的几种状态转换关系:
未完待续