概念
Java多线程是指在Java程序中同时运行多个线程,从而使得程序能够更加高效地利用CPU资源。
线程是表示程序的执行流程,是CPU调度执行的基本单位;线程有自己的程序计数器、寄存器、堆栈和帧。同一进程中的线程共用相同的地址空间,同时共享进进程锁拥有的内存和其他资源
多线程的实现方式
1、继承Thread类
2、实现Runnable接口
3、创建Callable接口的实现类 ,实现它的Call方法
示例代码
package com.site.blog.my.core;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyCallable implements Callable {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
@Override
public Object call() throws Exception {
//执行多线程任务,并返回结果
return null;
}
}
class MyFutureTask{
public static void main(String[] args) {
MyCallable mc = new MyCallable();
FutureTask ft = new FutureTask<>(mc);
new Thread(ft).start();
try {
// 获取结果
Object result = ft.get();
System.out.printf("输出单个结果"+result);
if(ft.isDone()){
// 处理最终结果
System.out.printf("输出汇总结果"+result);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
4、通过线程池实现多线程
newFixedThreadPool(int nThreads)
创建一个固定大小的线程池,线程池中的线程数量始终为 nThreads。特点是核心线程和最大线程数量都是一个固定的值 如果任务比较多工作线程处理不过来,就会加入到阻塞队列里面等待。
示例代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(new Runnable() {
public void run() {
System.out.println("Thread 1 is running...");
}
});
executorService.execute(new Runnable() {
public void run() {
System.out.println("Thread 2 is running...");
}
});
executorService.shutdown();
}
}
newCachedThreadPool()
创建一个可缓存的线程池,线程池中的线程数量根据任务的数量动态调整,如果有空闲线程则重用,否则创建新线程。是一种可以缓存的线程池,它可以用来处理大量短期 的突发流量。
它的特点有三个,最大线程数是 Integer.MaxValue,线程存活时间是 60 秒,阻 塞队列用的是 SynchronousQueue,这是一种不存才任何元素的阻塞队列,也就 是每提交一个任务给到线程池,都会分配一个工作线程来处理,由于最大线程数 没有限制。
示例代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
public void run() {
System.out.println("Thread 1 is running...");
}
});
executorService.execute(new Runnable() {
public void run() {
System.out.println("Thread 2 is running...");
}
});
executorService.shutdown();
}
}
newSingleThreadExecutor()
创建一个单线程的线程池,只有一个工作线程的线程池。 并且线程数量无法动态更改,因此可以保证所有的任务都按照 FIFO(顺序) 的方式顺序 执行。
newScheduledThreadPool
具有延迟执行功能的线程池 可以用它来实现定时调度
线程同步
在多线程编程中,由于线程之间的竞争条件,可能会导致一些不确定的结果。为了解决这些问题,就需要使用线程同步机制。
线程同步指的是多个线程之间的协作,确保它们能够有序地执行,避免竞争条件和数据冲突的发生。Java 中提供了多种方式来实现线程同步,主要有以下几种:
synchronized 关键字:通过加锁的方式来实现线程同步,确保同一时间只有一个线程可以访问共享资源。
ReentrantLock 类:与 synchronized 类似,也是通过加锁来实现线程同步。但是,相比 synchronized,它提供了更多的灵活性和功能,比如支持公平锁、可重入锁等。
volatile 关键字:用于修饰变量,保证其在多个线程之间的可见性,确保每个线程都能够看到其他线程所做的修改。
wait() 和 notify() 方法:用于实现线程之间的协作,让线程进入等待状态或者唤醒等待的线程。
CountDownLatch 类:用于协调多个线程之间的同步,等待所有的线程都执行完毕之后再执行其他操作。
CyclicBarrier 类:与 CountDownLatch 类似,也用于协调多个线程之间的同步。不同的是,CyclicBarrier 可以重复使用,即当所有线程都执行完毕后,它会自动重置,可以重新开始。
除了以上的机制,Java 还提供了一些其他的同步工具和类,比如 Semaphore、ReadWriteLock、Condition 等。
在实现线程同步时,需要注意以下几点:
确保同步范围正确,即只对需要同步的资源进行加锁,而不是整个方法或类。
避免死锁的发生,即当多个线程之间互相等待对方释放锁时,可能会陷入死锁状态。
考虑性能问题,尽量减少锁的竞争,避免线程之间的频繁切换,提高程序的效率
线程池以及优化
线程池是一种用于管理和调度线程的机制,可以有效地避免线程的频繁创建和销毁,提高应用程序的性能和稳定性。在使用线程池时,需要设置合适的参数来优化线程池的性能。
常见的线程池参数有以下几个:
corePoolSize:核心线程数,线程池创建时就会创建这些线程,即使它们没有任务可执行。
maximumPoolSize:线程池最大线程数,用于限制线程池中线程的数量。
keepAliveTime:线程的空闲时间,当线程处于空闲状态时,超过该时间则销毁线程,直到线程数达到corePoolSize。
unit:keepAliveTime的单位。
workQueue:工作队列,用于保存待执行的任务,常用的有ArrayBlockingQueue、LinkedBlockingQueue等。
threadFactory:线程工厂,用于创建线程,可自定义线程的名称和属性。
handler:拒绝策略,用于处理达到最大线程数或队列已满的任务。线程池拒绝策略是指当线程池中的线程数已经达到最大值,无法再创建新的线程时,对新提交的任务的处理策略。
Java中的线程池一共有4种拒绝策略:
AbortPolicy(默认):当线程池队列已满并且线程池中的线程数达到最大值时,新提交的任务会抛出 RejectedExecutionException 异常。
CallerRunsPolicy:当线程池队列已满并且线程池中的线程数达到最大值时,新提交的任务由提交该任务的线程来执行。
DiscardOldestPolicy:当线程池队列已满并且线程池中的线程数达到最大值时,会丢弃队列中最老的一个任务,然后将新提交的任务加入队列中。
DiscardPolicy:当线程池队列已满并且线程池中的线程数达到最大值时,直接丢弃新提交的任务,不做任何处理。
可以通过 ThreadPoolExecutor 类的构造函数或者 setRejectedExecutionHandler() 方法来设置线程池的拒绝策略。通常来说,可以根据业务场景的不同选择合适的拒绝策略。例如,如果任务处理不是很重要,可以选择 DiscardPolicy 策略来丢弃新任务,避免队列积压;如果任务处理比较重要,可以选择 CallerRunsPolicy 策略来等待队列中的任务得到执行,避免任务丢失。
在优化线程池时,可以考虑以下几个方面:
调整线程池的大小:根据应用程序的性能需求,调整线程池的核心线程数和最大线程数,避免线程池过大或过小,导致性能下降。
调整工作队列的大小:根据应用程序的任务类型和数量,选择合适的工作队列,避免任务被拒绝或线程池阻塞。
使用线程池监控工具:通过监控线程池的状态和执行情况,及时发现和解决问题,优化线程池的性能。
使用合适的拒绝策略:当线程池已满或队列已满时,通过选择合适的拒绝