多线程
对于大数据量的操作,建议使用开启线程的方式进行完成,避免了程序占用时间,多线程会提高执行效率但也伴随相应的线程安全的风险。微服务同一代码部署到多个服务上也会出现线程安全问题,此篇仅介绍非分布式的线程管理。
引入线程
- 并行:指两个或多个事件在同一时刻发生(同时发生)。
- 并发:指两个或多个事件在同一个时间段内发生。
注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。
- 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
- 线程:进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
进程与线程的区别
- 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
- 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。
注意:下面内容为了解知识点
1:因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于 CPU 的调度,程序员是干涉不了的。而这也就造成的多线程的随机性。
2:Java 程序的进程里面至少包含两个线程,主进程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个线程,java 本身具备了垃圾的收集机制,所以在 Java 运行时至少会启动两个线程。
3:由于创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是创建多进程。
线程调度:
计算机通常只有一个CPU时,在任意时刻只能执行一条计算机指令,每一个进程只有获得CPU的使用权才能执行指令。所谓多进程并发运行,从宏观上看,其实是各个进程轮流获得CPU的使用权,分别执行各自的任务。那么,在可运行池中,会有多个线程处于就绪状态等到CPU,JVM就负责了线程的调度。JVM采用的是抢占式调度,没有采用分时调度,因此可以能造成多线程执行结果的的随机性。
多线程原理
程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。
多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。
Thread类
简介
Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。
所属包
java.lang
Class Thread
java.lang.Object
java.lang.Thread
构造方法
public Thread():分配一个新的线程对象。public Thread(String name):分配一个指定名字的新的线程对象。public Thread(Runnable target):分配一个带有指定目标新的线程对象。public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。
常用方法:
public String getName():获取当前线程名称。
public void setName(String name) 将此线程的名称更改为等于参数 name 。
public void start():导致此线程开始执行; Java虚拟机调用此线程的run方法。
public void run():此线程要执行的任务在此处定义代码。
public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
public static Thread currentThread() :返回对当前正在执行的线程对象的引用。
public int getPriority() 返回此线程的优先级
public void setPriority(int newPriority) 更改此线程的优先级void setDaemon(boolean on) 将此线程标记为 daemon线程或用户线程 当其他线程执行完毕后守护线程将结束 无论是否执行完毕
run方法和start方法的区别:
run:封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程
star:启动线程,然后有Jvm调用此线程的run方法
Runnable
所属包
java.lang
Interface Runnable
简介:
通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。
在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。
tips:Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。
创建线程
通过Thread类 创建线程
方式一 通过继承Thread 重写run 方法 实现线程
Java中通过继承Thread类来创建并启动多线程的步骤如下:
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程
方式二 通过创建Runnable对象实现 实现run方法 实现线程
1.定义Runnable对象 (1.匿名内部类,2.继承接口 ,3.lambda表达式 实现接口)
2.实现run方法
3.定义Tread类 构造方法传入Runnable对象
4.调用start()启用该线程
例子:
public static void testThread(){
// 方式1
Thread thread = new ThreadOne();
thread.start();
new Thread(()->System.out.println("lambda 创建线程")).run();
// 方式2
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~");
System.out.println("测试runnable");
}
};
Thread thread1 = new Thread(runnable);
thread1.start();
// 方式3
Runnable runnable1 =()->{
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~");
System.out.println("测试runnable lambda");
};
Thread thread2 = new Thread(runnable1);
thread2.start();
// 方式4
Thread thread3 = new Thread(new RunnableOne());
thread3.start();
}
static class ThreadOne extends Thread{
@Override
public void run() {
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~");
for (int i = 0; i < 1; i++) {
System.out.println(i);
}
}
}
static class RunnableOne implements Runnable{
@Override
public void run() {
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~");
System.out.println("测试runnable 继承接口");
}
}
守护线程
void setDaemon(boolean on) 将此线程标记为 daemon线程或用户线程 当其他线程执行完毕后守护线程将结束 无论是否执行完毕
变量副本
当共享数据改变的时候 是先改变当前线程的变量副本 然后在去改变共享数据, 可能存在改变了共享数据 其他线程仍使用变量副本中变量没有更新变量的问题 此时需要在共享数据上 添加 volatile 关键字 (不能保证数据的原子性) 也可以在需要使用共享数据的地方加入同步代码块 同步代码块在运行时会清空变量副本


乐观锁 悲观锁

并发工具类
多个线程执行完毕后 再执行之后代码
CountDownLatch:
所属包 方法:
包
java.util.concurrent Class CountDownLatch
构造
public CountDownLatch(int count)构造一个以给定计数 CountDownLatch CountDownLatch。
oid await() 导致当前线程等到锁存器计数到零,除非线程是 interrupted 。
boolean await(long timeout, TimeUnit unit) 使当前线程等待直到锁存器计数到零为止,除非线程为 interrupted或指定的等待时间过去。
void countDown() 减少锁存器的计数,如果计数达到零,释放所有等待的线程。
long getCount() 返回当前计数。
String toString() 返回一个标识此锁存器的字符串及其状态。
使用:
1.创建计数器对象 ; 2.将计数器对象传入run方法 执行 等待或计数 3.线程执行完指定次数后执行await后的代码
semaphore
控制访问特定资源场景
所属包 方法:
包
java.lang.Object java.util.concurrent.Semaphore
构造:
Semaphore(int permits)
创建一个 Semaphore与给定数量的许可证和非公平公平设置。
Semaphore(int permits, boolean fair)
创建一个 Semaphore与给定数量的许可证和给定的公平设置。
方法
void acquire() 从该信号量获取许可证,阻止直到可用,或线程为 interrupted 。
void acquire(int permits) 从该信号量获取给定数量的许可证,阻止直到所有可用,否则线程为 interrupted 。
void release() 释放许可证,将其返回到信号量。
void release(int permits) 释放给定数量的许可证,将其返回到信号量。
1. 获取Semaphore 对象 2.获取通行证 3.执行代码4.归还通行证
线程池创建线程
概述
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
由于线程池中有很多操作都是与优化资源相关的,我们在这里就不多赘述。我们通过一张图来了解线程池的工作原理:
合理利用线程池能够带来三个好处:
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
所属包
java.util.concurrent
Class Executors
启动线程池
Executors类中有个创建线程池的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)
获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:
Runnable
-
public Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行Future接口:用来记录线程任务执行完毕后产生的结果。
使用线程池中线程对象的步骤:
Callable:
-
<T> Future<T> submit(Callable<T> task): 获取线程池中的某一个线程对象,并执行.Future : 表示计算的结果.
-
V get(): 获取计算完成的结果。

常用线程池创建
newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
这种类型的线程池特点是:
(1)工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程(2)将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
(3)在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
newFixedThreadPool
创建一个指定工作线程数量的线程池(初始为0,最大为指定数)。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
ThreadPoolExecutor
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
创建一个新 ThreadPoolExecutor给定的初始参数。
参数:
核心线程数量,最大线程数量,空闲线程最大存活时间,
时间单位:java.util.concurrent Enum TimeUnit ;
任务队列:java.util.concurrent Class ArrayBlockingQueue<E>;
创建线程工厂: Executors :static ThreadFactory defaultThreadFactory() 返回用于创建新线程的默认线程工厂。
任务的拒绝策略:java.util.concurrent Class ThreadPoolExecutor.AbortPolicy 丢弃任务抛出异常
java.util.concurrent Class ThreadPoolExecutor.CallerRunsPolicy 绕过池子直接执行run犯法
java.util.concurrent Class ThreadPoolExecutor.DiscardOldestPolicy 丢弃任务不抛异常
java.util.concurrent Class ThreadPoolExecutor.DiscardPolicy 抛弃队列中等待最久的 任务加入队列
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,10,60,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());
newSingleThreadExecutor
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。
线程池使用案例
public static void practiceExcutor(){
// 无限大小的线程池 使用 runnable 接口
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// usePool(cachedThreadPool);
// 创建一个指定大小的线程池 // 使用callable 接口接收future 的返回值
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
// usePool(fixedThreadPool);
//创建一个单线程的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
usePool(singleThreadExecutor);
// 创建一个延迟执行的线程池
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
// usePool(scheduledThreadPool);
}
/**
* 使用线程池
*/
private static void usePool(ExecutorService executor) {
ArrayList<Future<String>> futures = new ArrayList<>();
for(int i = 0; i < 3; i++){
final int index = i;
if (executor instanceof ScheduledExecutorService ){
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
((ScheduledExecutorService) executor).schedule(new Runnable(){
public void run(){
System.out.println(index+Thread.currentThread().getName());
System.out.println("延迟3秒");
}
}, 3, TimeUnit.SECONDS);
} else {
executor.execute(()->System.out.println(Thread.currentThread().getName()+" execute 方式启动"));
executor.submit(()->System.out.println(Thread.currentThread().getName()+" submit 方式启动"));
futures.add(executor.submit(new Callable<String>(){
@Override
public String call() throws Exception {
return index+Thread.currentThread().getName()+" callable方式启动";
}
}));
}
}
futures.forEach(a->{
try {
System.out.println(a.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
}
线程安全问题
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
Java中提供了同步机制(synchronized)来解决。
线程同步
有三种方式完成同步操作:
- 同步代码块。(锁是共享数据)
- 同步方法。(锁是this ,同步静态方法锁是 类名.class)
- 锁机制。
同步代码块
- 同步代码块:
synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
-
锁对象 可以是任意类型。
-
多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
同步方法
- 同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
格式:
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
Lock锁
java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
ReentrantLock
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock():加同步锁。public void unlock():释放同步锁。
示例代码
public static void practiceLock() {
Student lock = new Student(1, "同步");
// 同步代码块 内部锁
RunnableZero runnableOne = new RunnableZero();
new Thread(runnableOne, "同步代码块1").start();
new Thread(runnableOne, "同步代码块2").start();
// 测试 外部锁 联系两个Runnable
RunnableOne runnableOne1 = new RunnableOne(lock);
RunnableOne runnableOne2 = new RunnableOne(lock);
new Thread(runnableOne1, "同步代码块3").start();
new Thread(runnableOne2, "同步代码块4").start();
// 同步方法
RunnableTwo runnableTwo = new RunnableTwo();
new Thread(runnableTwo, "同步方法1").start();
new Thread(runnableTwo, "同步方法2").start();
// lock锁
RunnableThree runnableThree = new RunnableThree();
new Thread(runnableThree, "lock锁 锁1").start();
new Thread(runnableThree, "lock锁 锁2").start();
}
/**
* 使用类中变量作为同步代码块的锁
*/
static class RunnableZero implements Runnable {
int[] intArray = {1, 2, 3};
@Override
public void run() {
synchronized (intArray) {
for (int i : intArray) {
System.out.println(Thread.currentThread().getName());
System.out.println(i);
/* try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
}
}
}
/**
* 使用 外部变量作为同步代码块的锁 多个Runnable 共用同一变量
* @param <T>
*/
static class RunnableOne<T> implements Runnable {
int[] intArray = {1, 2, 3};
Student student;
public RunnableOne(Student student) {
this.student = student;
}
@Override
public void run() {
synchronized (student /*intArray*/) {
for (int i : intArray) {
System.out.println(Thread.currentThread().getName());
System.out.println(i);
/* try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
}
}
}
/**
* 同步方法的使用
* @param <T>
*/
static class RunnableTwo<T> implements Runnable {
int[] intArray = {1, 2, 3};
@Override
public void run() {
printArray(intArray);
}
/**
* 同步方法 使用 synchronized修饰方法
* @param intArray
*/
public synchronized static void printArray(int[] intArray) {
for (int i : intArray) {
System.out.println(Thread.currentThread().getName());
System.out.println(i);
/* try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
}
}
/**
* 使用lock锁 注意try catch finally使用 , 注意lock的实现子类
* @param <T>
*/
static class RunnableThree<T> implements Runnable {
int[] intArray = {1, 2, 3};
Lock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
try {
for (int i : intArray) {
System.out.println(Thread.currentThread().getName());
System.out.println(i);
/* try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
线程状态

线程状态概述
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State这个枚举中给出了六种线程状态:
这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析
| 线程状态 | 导致状态发生条件 |
|---|---|
| NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
| Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
| Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
| Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
| Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
| Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
我们不需要去研究这几种状态的实现原理,我们只需知道在做线程操作中存在这样的状态。那我们怎么去理解这几个状态呢,新建与被终止还是很容易理解的,我们就研究一下线程从Runnable(可运行)状态与非运行状态之间的转换问题。
线程的等待唤醒
睡眠sleep方法
我们看到状态中有一个状态叫做计时等待,可以通过Thread类的方法来进行演示.
public static void sleep(long time) 让当前线程进入到睡眠状态,到毫秒后自动醒来继续执行
public class Test{
public static void main(String[] args){
for(int i = 1;i<=5;i++){
Thread.sleep(1000);
System.out.println(i)
}
}
}
这时我们发现主线程执行到sleep方法会休眠1秒后再继续执行。
3.3 等待和唤醒
Object类的方法
public void wait() : 让当前线程进入到等待状态 此方法必须锁对象调用.
public class Demo1_wait {
public static void main(String[] args) throws InterruptedException {
// 步骤1 : 子线程开启,进入无限等待状态, 没有被唤醒,无法继续运行.
new Thread(() -> {
try {
System.out.println("begin wait ....");
synchronized ("") {
"".wait();
}
System.out.println("over");
} catch (Exception e) {
}
}).start();
}
public void notify() : 唤醒当前锁对象上等待状态的线程 此方法必须锁对象调用.
public class Demo2_notify {
public static void main(String[] args) throws InterruptedException {
// 步骤1 : 子线程开启,进入无限等待状态, 没有被唤醒,无法继续运行.
new Thread(() -> {
try {
System.out.println("begin wait ....");
synchronized ("") {
"".wait();
}
System.out.println("over");
} catch (Exception e) {
}
}).start();
//步骤2: 加入如下代码后, 3秒后,会执行notify方法, 唤醒wait中线程.
Thread.sleep(3000);
new Thread(() -> {
try {
synchronized ("") {
System.out.println("唤醒");
"".notify();
}
} catch (Exception e) {
}
}).start();
}
}
阻塞队列:

BlockingQueue:
所属包 与常用方法
java.util.concurrent
Interface BlockingQueue<E>
void put(E e) 将指定的元素插入到此队列中,等待空格可用。
E take() 检索并删除此队列的头,如有必要,等待元素可用。
已知实现类:
ArrayBlockingQueue , DelayQueue , LinkedBlockingDeque , LinkedBlockingQueue , LinkedTransferQueue , PriorityBlockingQueue , SynchronousQueue
1 为容器数量
ArrayBlockingQueue<String> abq = new ArrayBlockingQueue<>(1);
本文深入探讨Java中的多线程概念,包括线程创建、线程安全、线程状态以及线程池的使用。介绍了Thread类、Runnable接口、线程同步方法,并通过实例展示了线程池的优势和常见实现,帮助开发者理解并掌握Java多线程编程。

5313

被折叠的 条评论
为什么被折叠?



