只写一些Java特有的.
使用线程的方法¶
第一种:继承Thread¶
官方文档示例¶

示例¶
demo¶
- 注意,t1调用的不是run方法,而是start方法.
- run相当于注册了方法,start是启动线程
setName¶
用于给线程设置名字.当然也可以getName
由于Java只能单继承,因此继承了Thread就不能继承其他的了.这是它的弊端.
第二种:实现Runnable接口¶
官方文档示例¶
注意,我们要把我们的对象传给一个Thread对象.
示例¶
demo¶

setName¶
注意,setName/getName都是Thread的方法,因此我们不能在run()里面直接调用. 我们可以使用Thread.currentTread这个静态方法,获取当前线程的对象,然后得到对应的线程.
第三种:利用Callable和Future接口¶
是对前两种方式的补充.我们可以看到前两种的run都没有返回值.而第三种可以.
过程¶
开始实现:
这个的泛型V就表示返回值的类型.
FutureTask是Future接口的实现类.表示用来托管mc的结果.如果我们想要运行多个线程获得多个结果,那么就需要创建多个FutureTask,传入到不同的Thread.
常见的成员方法¶

线程默认名字¶
如果线程没有起名字,那么getName会返回默认名字.默认名字是Tread-X,X表示序号,从0开始.
构造方法¶
构造方法也能起名字:

main线程¶

sleep¶
哪个线程调用Thread.sleep,哪个线程睡眠.
抢占式调度¶
java采用抢占式调度.优先级为1-10,默认为5(不论自己写的线程还是主线程都是这样). 优先级越高,越可能被选择.
守护线程¶
把一部分线程设成守护线程之后,当所有非守护线程结束时,守护线程会立刻开始陆续结束,不论是否执行完成. 注意,不是立刻结束,只是陆续结束.
出让线程/礼让线程¶
调用Thread.yield()后,会出让执行权.
插入线程/插队线程¶
在某个线程1当中调用线程对象2的.join后,那么线程1会阻塞住,等待线程2执行完,才会往后执行. 这样就解释了为什么我们经常在main函数join子线程. 上面对于线程2来说,就相对于总是在插线程1的队,让它自己总是比线程1先执行.
线程安全¶
同步代码块synchronized¶
写法:¶
锁对象可以是任意的(不仅仅是像下面的代码这样用Object),但是必须保证唯一.因此我们可以设成static.
如果锁对象不是唯一的,那么就相当于不同的锁.(比如在这里我把obj换成this,由于三个线程的this不一样,因此就有三把锁)
我们一般为了保证唯一,其实一般是写MyThread.class(字节码文件对象),这一般是唯一的.
同步方法¶
特点
这相对于把同步代码块中的代码抽出来单独成方法了.
这里实际上是提出了一种写共享代码的方法:
1.循环(死循环)
2. 同步代码块
3. 判断共享数据是否到了末尾(到了末尾)
4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
runnable的误区¶
如图,我们虽然有三个线程,但是实际上只有一个runnable对象.因此,ticket这个变量其实是三个线程共享的,不需要加static.
这里相当于三个线程同时调用同一个runnable对象的run方法.
这上面也好理解:这个mr对象是在堆上的,那么三个线程都是可以看到同一个堆的.
改成同步方法:

注意如果我们使用那种继承Thread的写法,就不能用同步方法代替同步代码块了.因为那三个线程有三个不同的this.- 因此我们可以看出,java这里的runnable比较符合原有的印象.
StringBuffer¶
StringBuffer和StringBuilder的方法一模一样.但是,前者是线程安全的,后者是不安全的.
实际上,只是StringBuffer上各个方法是同步的.

锁对象Lock¶
于jDK5出现.
tip:这样写有个大bug:当循环break之后,锁没有释放,导致死锁.
一般的标准写法是:把所有的加锁代码放到trycatch块当中,然后在finally里添加unlock.这样,就能保证无论如何锁都能释放.
死锁的样例¶
面试喜欢问.
等待唤醒机制¶
生产消费模型¶
notify¶
其实就是条件变量
]
这些方法都是在锁对象底下等待的.
消费者:
我们可以看出,任何一个对象成为锁对象之后,就会拥有上面的方法.
生产者:
调用:

阻塞队列¶
这两个实现类都实现了上面四个接口.最常用的方法就是put tack
- 这里也解答了一个疑惑:如何给某个线程传递数据
这两个类天然就是线程安全的:
因此,这样写就好:

- tip:打印语句是线程不安全的.
线程状态¶
但是实际上,没有运行这个状态.API帮助文档
为什么没有定义运行状态,因为线程就绪之后,java就把它们交给os管理了,因此没有运行状态.
线程池¶
Executors¶
其实也有上限,超过就会崩溃.
线程池有一批准备就绪的线程.每次submit就会交给某个线程一个任务.
对于有上限的线程池,那么最多就那么多线程,如果要运行的任务过多,那么有的任务就需要等待.
如果是没有上限的线程池,也不是submit多少次就使用多少个线程,而是在submit时会观察,是否有之前submit的任务结束空出来的线程.
自定义线程池¶
其实是executors的另一种构造函数.
理论¶
线程有核心线程和临时线程之分.线程池的最大线程数量=核心线程数+临时线程数
核心线程会一直等待任务进行,是常驻的.但是临时线程只会在任务过多时被创建,并且在空闲一段时间后会被删除.
一般来说,假如任务数超过了核心线程数,那么多的任务就会在阻塞队列当中进行阻塞.
当阻塞队列也塞满了之后,那么就会创建临时线程来处理新的任务.
如果临时线程也不够了,那么多的线程会触发任务拒绝策略.默认是丢弃并抛出异常

构造函数参数:¶
默认工厂:实际上就是new了一个线程.注意,这里没有笔误
其中,最后一个参数是内部类.
最佳参数¶
线程池大小公式:

什么是最大并行数¶
4核8线程:4个cpu,每个cpu利用超线程技术虚拟化出可以处理两个线程.
8就是最大并行数.
由于os一般不会把所有的资源都给程序,因此最稳定的就是利用代码获取.

大小¶
- 为什么cpu密集计算要+1? 因为避免某个线程阻塞(如页缺失),这样多的线程就能顶上来
- IO密集 最后一个分式,其实就是cpu计算时间占cpu总用时的比例的倒数 这个数据是可以通过thread dump来获得的.
未完待续:volatile JMM 悲观锁乐观锁 CAS 原子性
拓展: generate BY AI
在Java里,JUC指的是java.util.concurrent包。此包由Doug Lea设计并开发,于Java 5时被引入,目的是为Java提供强大的并发编程工具,让开发者能更便捷地处理多线程编程问题。
JUC包的核心功能与组件¶
1. 线程池(Executor框架)¶
线程池能够对线程的创建和管理进行优化,避免了频繁创建和销毁线程带来的性能损耗。其核心组件有:
- ExecutorService:这是线程池的主要接口。
- ThreadPoolExecutor:线程池的具体实现类。
- ScheduledExecutorService:可执行定时任务的线程池。
下面是一个简单的线程池使用示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建一个固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交任务给线程池执行
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("执行任务: " + taskId + ",线程: " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成任务: " + taskId);
});
}
// 关闭线程池
executor.shutdown();
}
}
2. 同步工具类¶
这些工具类用于线程间的协调与同步:
- CountDownLatch:使一个或多个线程等待其他线程完成操作。
- CyclicBarrier:让一组线程相互等待,直到所有线程都到达某个屏障点。
- Semaphore:用于控制同时访问特定资源的线程数量。
- Exchanger:可实现两个线程间的数据交换。
下面是CountDownLatch的使用示例:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int workerCount = 3;
CountDownLatch latch = new CountDownLatch(workerCount);
// 创建并启动多个工作线程
for (int i = 0; i < workerCount; i++) {
final int workerId = i;
new Thread(() -> {
System.out.println("工作线程 " + workerId + " 开始工作");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("工作线程 " + workerId + " 完成工作");
latch.countDown(); // 减少计数
}).start();
}
// 主线程等待所有工作线程完成
latch.await();
System.out.println("所有工作线程已完成,主线程继续执行");
}
}
3. 并发集合¶
JUC包提供了线程安全的集合类,相比Collections.synchronizedXXX包装的同步集合,性能更优:
- ConcurrentHashMap:线程安全的哈希表实现。
- CopyOnWriteArrayList:在读多写少场景下表现出色的线程安全列表。
- ConcurrentLinkedQueue:无界的线程安全队列。
- BlockingQueue接口及实现类(如ArrayBlockingQueue、LinkedBlockingQueue):支持阻塞操作的队列。
下面是ConcurrentHashMap的使用示例:
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 多个线程可以安全地向map中添加元素
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
map.put("key" + i, i);
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
map.put("key" + (i + 1000), i + 1000);
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Map大小: " + map.size());
}
}
4. 原子操作类(Atomic系列)¶
这些类借助CAS(Compare-and-Swap)操作实现原子操作,避免了使用锁带来的开销:
- AtomicInteger、AtomicLong、AtomicBoolean等。
下面是AtomicInteger的使用示例:
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicExample {
private static AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
int threadCount = 10;
Thread[] threads = new Thread[threadCount];
// 创建并启动多个线程
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.incrementAndGet(); // 原子操作
}
});
threads[i].start();
}
// 等待所有线程完成
for (Thread t : threads) {
t.join();
}
System.out.println("最终计数: " + counter.get()); // 输出应为10000
}
}
5. 锁机制¶
JUC包提供了比synchronized更灵活的锁实现:
- ReentrantLock:可重入锁,支持公平锁和非公平锁。
- ReentrantReadWriteLock:读写锁,允许多个读线程同时访问共享资源。
- LockSupport:用于阻塞和唤醒线程。
下面是ReentrantLock的使用示例:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private static final ReentrantLock lock = new ReentrantLock();
private static int sharedResource = 0;
public static void main(String[] args) throws InterruptedException {
int threadCount = 2;
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
lock.lock(); // 获取锁
try {
sharedResource++;
} finally {
lock.unlock(); // 释放锁
}
}
});
threads[i].start();
}
for (Thread t : threads) {
t.join();
}
System.out.println("共享资源值: " + sharedResource); // 输出应为2000
}
}
6. 并发工具类¶
Future和Callable:用于异步计算,获取计算结果。CompletableFuture:提供了更强大的异步编程功能。ForkJoinPool和ForkJoinTask:用于并行执行任务,采用工作窃取算法。
下面是CompletableFuture的使用示例:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建一个异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "异步任务结果";
});
// 任务完成后执行后续操作
future.thenAccept(result -> System.out.println("接收到结果: " + result));
// 主线程可以继续执行其他操作
System.out.println("主线程继续执行");
// 等待异步任务完成并获取结果
String result = future.get();
System.out.println("最终结果: " + result);
}
}
JUC包的优势¶
- 简化并发编程:JUC包提供了高层抽象,降低了直接操作线程的复杂度。
- 性能优化:通过高效的算法和数据结构,减少了锁的使用,提升了并发性能。
- 丰富的功能:满足了多样化的并发场景需求,如异步计算、定时任务、生产者-消费者模型等。
总之,JUC包是Java并发编程的核心工具包,它极大地简化了多线程编程,提升了代码的可靠性和性能。