# 多线程、线程同步、反射、注解的基本原理
# 多线程
为什么需要多线程?这个问题问得很基础,但也很核心。简单来说,多线程是为了榨干 CPU 的性能,同时让程序响应得更快。当然,多线程也带来了新麻烦比如资源竞争、死锁,但为了效率和体验,这些代价通常是值得的。
# Java 开启线程一
class MyThread extends Thread { | |
@Override | |
public void run() { | |
for (int i = 0; i <= 500; i++) { | |
System.out.println("i=" + i); | |
} | |
} | |
} | |
class MyThread2 implements Runnable { | |
@Override | |
public void run() { | |
for (int i = 0; i <= 500; i++) { | |
System.out.println("ww=" + i); | |
} | |
} | |
} | |
public class TDemo2 { | |
public static void main(String[] args) { | |
MyThread myThread = new MyThread(); | |
myThread.setName("线程1"); | |
myThread.start(); | |
MyThread2 myThread2 = new MyThread2(); | |
Thread thread = new Thread(myThread2); | |
thread.setName("线程2"); | |
thread.start(); | |
} | |
} |
# Java 的垃圾回收
当无用对象过多的时候,JVM 会自动执行垃圾回收。
class Dome2 { | |
@Override | |
protected void finalize() throws Throwable { | |
System.out.println("finalize!"); | |
} | |
} | |
public class TDemo2 { | |
public static void main(String[] args) { | |
for (int i = 0; i < 500000; i++) { | |
new Dome2(); | |
} | |
} | |
} |
# Java 开启线程二
implements Runnable 是 Java 推荐的方式,把任务和线程分离开,比继承 Thread 类更灵活。
class MyThread implements Runnable { | |
@Override | |
public void run() { | |
for (int i = 0; i < 500; i++) { | |
System.out.println("i = " + i); | |
} | |
} | |
} | |
class MyThread2 implements Runnable { | |
@Override | |
public void run() { | |
for (int i = 0; i < 500; i++) { | |
System.out.println("ww = " + i); | |
} | |
} | |
} | |
public class TDemo2 { | |
public static void main(String[] args) { | |
new Thread(new MyThread()).start(); | |
new Thread(new MyThread2()).start(); | |
} | |
} |
# Java 开启线程三
Callable 代替了 Runnable,它的 call () 方法不仅能执行任务,还能返回结果(这里返回累加和 Integer),而 Runnable 的 run () 是 void 无返回的。
class MyCallable implements Callable<Integer> { | |
@Override | |
public Integer call() throws Exception { | |
int sum = 0; | |
for (int i = 0; i < 500; i++) { | |
System.out.println("i = " + i); | |
sum += i; | |
} | |
return sum; | |
} | |
} | |
class MyCallable2 implements Callable<Integer> { | |
@Override | |
public Integer call() throws Exception { | |
int sum = 0; | |
for (int i = 0; i < 600; i++) { | |
System.out.println("ww = " + i); | |
sum += i; | |
} | |
return sum; | |
} | |
} | |
public class TDemo2 { | |
public static void main(String[] args) throws ExecutionException, InterruptedException { | |
MyCallable myCallable = new MyCallable(); | |
FutureTask<Integer> integerFutureTask = new FutureTask<Integer>(myCallable); | |
new Thread(integerFutureTask).start(); | |
Integer i = integerFutureTask.get(); | |
System.out.println("ans : " + i); | |
MyCallable2 myCallable2 = new MyCallable2(); | |
FutureTask<Integer> integerFutureTask2 = new FutureTask<Integer>(myCallable2); | |
new Thread(integerFutureTask2).start(); | |
Integer i2 = integerFutureTask2.get(); | |
System.out.println("ans : " + i2); | |
} | |
} |
# 设置线程优先级
class PriorityTask implements Runnable { | |
private String taskName; | |
public PriorityTask(String taskName) { | |
this.taskName = taskName; | |
} | |
@Override | |
public void run() { | |
// 让线程多跑一会儿,便于观察效果 | |
for (int i = 0; i < 100; i++) { | |
System.out.println(taskName + " 执行第 " + i + " 次"); | |
// 主动让出 CPU,增加其他线程执行机会 | |
Thread.yield(); | |
} | |
System.out.println(taskName + " 执行完毕!"); | |
} | |
} | |
public class PriorityDemo { | |
public static void main(String[] args) { | |
// 创建三个任务 | |
PriorityTask lowTask = new PriorityTask("【低优先级】"); | |
PriorityTask normalTask = new PriorityTask("【普通优先级】"); | |
PriorityTask highTask = new PriorityTask("【高优先级】"); | |
// 创建三个线程 | |
Thread lowThread = new Thread(lowTask); | |
Thread normalThread = new Thread(normalTask); | |
Thread highThread = new Thread(highTask); | |
// 设置优先级(1-10,默认 5) | |
lowThread.setPriority(Thread.MIN_PRIORITY); // 1 | |
normalThread.setPriority(Thread.NORM_PRIORITY); // 5 | |
highThread.setPriority(Thread.MAX_PRIORITY); // 10 | |
// 启动线程 | |
lowThread.start(); | |
normalThread.start(); | |
highThread.start(); | |
// 让主线程等待子线程执行完 | |
try { | |
lowThread.join(); | |
normalThread.join(); | |
highThread.join(); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
System.out.println("所有线程执行结束!"); | |
} | |
} |
# 守护线程
守护线程是后台保姆线程,当所有非守护线程执行完毕后,JVM 会自动退出并强制终止所有守护线程。
public class TDemo2 { | |
public static void main(String[] args) throws ExecutionException, InterruptedException { | |
Thread thread = new Thread(new Runnable() { | |
@Override | |
public void run() { | |
for (int i = 0; i < 50; i++) { | |
System.out.println(Thread.currentThread().getName() + " i=" + i); | |
} | |
} | |
}, "线程1"); | |
Thread thread1 = new Thread(new Runnable() { | |
@Override | |
public void run() { | |
for (int i = 0; i < 500; i++) { | |
System.out.println(Thread.currentThread().getName() + " i=" + i); | |
} | |
} | |
}, "线程2"); | |
thread1.setDaemon(true); | |
thread.start(); | |
thread1.start(); | |
} | |
} |
线程 1 执行完 50 次循环后,JVM 会立即退出,此时线程 2 是守护线程即使才执行到一半,也会被强制终止,控制台不会打印完所有线程 2 的输出。
# 线程安全
public class TDemo2 { | |
class TicketTast implements Runnable { | |
int ticket = 100; | |
@Override | |
public void run() { | |
while (ticket != 0) { | |
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票"); | |
ticket--; | |
} | |
} | |
} | |
public static void main(String[] args) { | |
new Thread(new TDemo2().new TicketTast(), "窗口1").start(); | |
new Thread(new TDemo2().new TicketTast(), "窗口2").start(); | |
new Thread(new TDemo2().new TicketTast(), "窗口3").start(); | |
} | |
} |
这段代码是多线程卖票的经典错误示例,核心问题在于每个线程各自拥有独立的票池。
窗口 3 正在卖第 100 张票
窗口 3 正在卖第 99 张票
窗口 3 正在卖第 98 张票
窗口 3 正在卖第 97 张票
窗口 3 正在卖第 96 张票
窗口 3 正在卖第 95 张票
窗口 1 正在卖第 100 张票
窗口 1 正在卖第 99 张票
# 通过线程同步技术
# 同步代码块 synchronized
synchronized 中使用 TicketTast.class 作为锁对象,可以让所有 TicketTast 实例共享同一把锁(类锁)。
class TicketTast implements Runnable { | |
int ticket = 1000; | |
@Override | |
public void run() { | |
while (true) { | |
synchronized (TicketTast.class) { | |
if (ticket <= 0) { | |
break; | |
} | |
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票"); | |
ticket--; | |
} | |
} | |
} | |
} |
# 同步方法 synchronized
synchronized 修饰在 extracted () 方法上,锁的是当前 TicketTast 实例对象。因为两个线程共享同一个 ticketTast 对象,所以锁生效,保证票数安全递减。
public class TDemo2 { | |
class TicketTast implements Runnable { | |
int ticket = 10; | |
@Override | |
public void run() { | |
while (true) { | |
if (extracted()) break; | |
} | |
} | |
private synchronized boolean extracted() { | |
if (ticket <= 0) { | |
return true; | |
} | |
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票"); | |
ticket--; | |
return false; | |
} | |
} | |
public static void main(String[] args) { | |
TDemo2 tDemo2 = new TDemo2(); | |
TicketTast ticketTast = tDemo2.new TicketTast(); | |
new Thread(ticketTast, "窗口1").start(); | |
new Thread(ticketTast, "窗口2").start(); | |
} | |
} |
| 方法类型 | 锁对象 | 说明 |
|---|---|---|
synchronized 修饰非静态方法 |
this (当前实例对象) |
每个实例有自己的锁 |
synchronized 修饰静态方法 |
类名.class (Class 对象) |
所有实例共享一把锁 |
# 代码中到底几把锁?
TicketTast ticketTast = tDemo2.new TicketTast(); // 只创建了 1 个 TicketTast 对象 | |
new Thread(ticketTast, "窗口1").start(); | |
new Thread(ticketTast, "窗口2").start(); |
只有 1 把锁!因为两个线程共享同一个 ticketTast 对象,非静态同步方法锁的是 this ,也就是这个对象。
# 如果是多个实例呢?
TicketTast task1 = new TicketTast(); // 对象 1 | |
TicketTast task2 = new TicketTast(); // 对象 2 | |
new Thread(task1, "窗口1").start(); | |
new Thread(task2, "窗口2").start(); |
有 2 把锁!每个对象都有自己的锁,窗口 1 锁 task1,窗口 2 锁 task2,互不干扰 —— 这就是 "每个实例有自己的锁"。
结果就是各卖各的 10 张票,共卖出 20 张(又回到了最初的错误)。
synchronized 锁的是对象本身:非静态方法锁 this ,有多少个对象就有多少把锁;静态方法锁 类.class ,所有对象只有一把锁。代码只创建了一个 TicketTast 实例,所以只有一把锁,两个窗口争抢这一把锁,安全有效。
# Lock 锁
public class TDemo2 { | |
class TicketTast implements Runnable { | |
int ticket = 10; | |
ReentrantLock lock = new ReentrantLock(); | |
@Override | |
public void run() { | |
while (true) { | |
try { | |
lock.lock(); | |
if (ticket <= 0) { | |
break; | |
} | |
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票"); | |
ticket--; | |
} finally { | |
lock.unlock(); | |
} | |
} | |
} | |
} | |
public static void main(String[] args) { | |
TDemo2 tDemo2 = new TDemo2(); | |
TicketTast ticketTast = tDemo2.new TicketTast(); | |
new Thread(ticketTast, "窗口1").start(); | |
new Thread(ticketTast, "窗口2").start(); | |
} | |
} |
ReentrantLock lock = new ReentrantLock(); 是 TicketTast 的成员变量,两个线程共享同一个 ticketTast 对象,所以共享同一把锁。即使 ticket<=0 执行 break 退出循环, finally 块依然会执行,保证锁被释放 —— 这是正确的做法。
# 线程池
线程池的核心作用是:复用线程、控制并发、提升性能。
1. 复用线程(降低开销)
创建和销毁线程是有成本的(内存分配、系统调用)。线程池让线程执行完一个任务后不销毁,而是继续执行下一个任务,避免了频繁创建销毁的开销。
2. 控制并发(防止资源耗尽)
没有线程池时,你可以无限制地创建线程(比如循环创建 10000 个),系统可能崩溃。线程池限制了最大线程数,超过的任务进入队列排队,保护系统稳定。
3. 统一管理(方便监控)
线程池提供了任务统计(执行数、失败数)、定时执行、线程状态监控等功能,方便统一管理。
没有线程池:每次来客人都临时招聘一个服务员,客人走了就辞退 —— 频繁招聘培训,成本极高。
有线程池:店里固定养了 10 个服务员,客人来了直接服务,走了继续服务下一位 —— 高效、可控。
public class TDemo2 { | |
public static void main(String[] args) { | |
// 创建线程池 | |
ExecutorService executorService = Executors.newFixedThreadPool(10); | |
// 提交任务 | |
for (int i = 0; i < 100; i++) { | |
executorService.execute(new Runnable() { | |
@Override | |
public void run() { | |
System.out.println(Thread.currentThread().getName() + "正在执行任务"); | |
} | |
}); | |
} | |
// 关闭线程池 | |
executorService.shutdown(); | |
} | |
} |
# 自定义线程池
public class TDemo2 { | |
public static void main(String[] args) { | |
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( | |
2, | |
5, | |
60, | |
TimeUnit.SECONDS, | |
new ArrayBlockingQueue<>(10), | |
Executors.defaultThreadFactory(), | |
new ThreadPoolExecutor.AbortPolicy()); | |
for (int i = 0; i < 103; i++) { | |
threadPoolExecutor.submit(new Runnable() { | |
@Override | |
public void run() { | |
System.out.println(Thread.currentThread().getName() + " is running"); | |
} | |
}); | |
} | |
} | |
} |
Exception in thread “main” java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@6442b0a6[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@67b64c45[Wrapped task = com.KarryCode.process.TDemo2AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2055)
at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:825)
at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1355)
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:118)
at com.KarryCode.process.TDemo2.main(TDemo2.java:30)
| 参数 | 值 | 含义 |
|---|---|---|
corePoolSize |
2 |
核心线程数:池中保有的最小线程数,即使空闲也不会销毁 |
maximumPoolSize |
5 |
最大线程数:池中最多允许的线程数 |
keepAliveTime |
60 |
空闲存活时间:非核心线程空闲 60 秒后被回收 |
unit |
TimeUnit.SECONDS |
时间单位:秒 |
workQueue |
new ArrayBlockingQueue<>(10) |
任务队列:容量 10 的有界阻塞队列,存放等待执行的任务 |
threadFactory |
Executors.defaultThreadFactory() |
线程工厂:创建线程的方式(默认即可) |
handler |
new ThreadPoolExecutor.AbortPolicy() |
拒绝策略:队列满了且线程数达上限时,抛出 RejectedExecutionException |
当提交了超过线程池承载能力的任务数。根据配置:
- 核心线程:2 个
- 最大线程:5 个
- 队列容量:10 个
核心线程 = 2:这 2 个线程一直活着,即使空闲也不销毁(除非设置 allowCoreThreadTimeOut )
最大线程 = 5:这是总线程数上限,包含核心线程 2 个 + 临时线程最多 3 个
# 枚举应用
public enum Constant { | |
DOWN, UP, HALF_UP, DELETE_LEFT | |
} |
public class test { | |
public static void handleData(double number, Constant constant) { | |
switch (constant) { | |
case DOWN: | |
System.out.println("DOWN: " + Math.floor(number)); | |
break; | |
case UP: | |
System.out.println("UP: " + Math.ceil(number)); | |
break; | |
case HALF_UP: | |
System.out.println("HALF_UP: " + Math.round(number)); | |
break; | |
case DELETE_LEFT: | |
System.out.println("DELETE_LEFT: " + (int) number); | |
break; | |
default: | |
throw new IllegalArgumentException("Unknown constant: " + constant); | |
} | |
} | |
public static void main(String[] args) { | |
System.out.println("Hello World"); | |
} | |
} |
# 反射
反射就是加载某个类到内存,允许访问变量、方法、构造器。反射就是要去拿类中的信息。
public class re { | |
public static void main(String[] args) throws ClassNotFoundException { | |
// 方法 1 | |
Class c1 = Student.class; | |
System.out.println(c1); | |
// 方法 2 | |
Class c2 = Class.forName("com.KarryCode.process.Student"); | |
System.out.println(c2); | |
Student zhangsan = new Student("zhangsan", 18); | |
Class c3 = zhangsan.getClass(); | |
System.out.println(c3); | |
} | |
} |
# 反射获取构造器
public class Cat { | |
public static int a; | |
public static final String COUNTY = "China"; | |
private String name; | |
private int age; | |
public Cat() { | |
System.out.println("我是无参构造方法"); | |
} | |
private Cat(String name, int age) { | |
System.out.println("我是有参构造方法"); | |
this.name = name; | |
this.age = age; | |
} | |
public void get() { | |
System.out.println("我是公开方法,eat!!"); | |
} | |
private void run() { | |
System.out.println("我是私有方法,run!!"); | |
} | |
public String getName() { | |
return name; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
public int getAge() { | |
return age; | |
} | |
public void setAge(int age) { | |
this.age = age; | |
} | |
} |
@Test | |
public void test1() throws NoSuchMethodException { | |
Class catClass = Cat.class; | |
Constructor[] constructors1 = catClass.getConstructors(); | |
for (Constructor constructor : constructors1) { | |
System.out.println(constructor); | |
System.out.println(constructor.getParameterCount()); | |
} | |
System.out.println("--------------"); | |
Constructor[] declaredConstructors = catClass.getDeclaredConstructors(); | |
for (Constructor constructor : declaredConstructors) { | |
System.out.println(constructor); | |
System.out.println(constructor.getParameterCount()); | |
} | |
System.out.println("--------------"); | |
Constructor declaredConstructor = catClass.getDeclaredConstructor(String.class, int.class); | |
System.out.println(declaredConstructor); | |
} |
# 通过反射的构造器实例化对象
Constructor declaredConstructorNoneParam = catClass.getDeclaredConstructor(); | |
Constructor declaredConstructorDoubleParam = catClass.getDeclaredConstructor(String.class, int.class); |
# 无参实例化
Cat noNameCat = (Cat) declaredConstructorNoneParam.newInstance(); | |
System.out.println(noNameCat.toString()); |
# 有参实例化
此时会报错:
Cat TomCat = (Cat) declaredConstructorDoubleParam.newInstance("Tom", 3); | |
System.out.println(TomCat.toString()); |
因为,有参构造器是私有的。
java.lang.IllegalAccessException: class com.KarryCode.process.reTest cannot access a member of class com.KarryCode.process.Cat with modifiers “private”
# 暴力反射
将 setAccessible 设置为真。
declaredConstructorDoubleParam.setAccessible(true); | |
Cat TomCat = (Cat) declaredConstructorDoubleParam.newInstance("Tom", 3); | |
System.out.println(TomCat.toString()); |
我是无参构造方法
name=null, age=0
我是有参构造方法
name=Tom, age=3
# 通过暴力反射修改成员变量
@Test | |
public void test2() throws NoSuchFieldException, IllegalAccessException { | |
Cat karry = new Cat("karry", 22, true); | |
System.out.println(karry.toString()); | |
Class catClass = Cat.class; | |
Field[] declaredFields = catClass.getDeclaredFields(); | |
for (Field field : declaredFields) { | |
System.out.println(field); | |
System.out.println(field.getType()); | |
System.out.println(field.getName()); | |
System.out.println("________"); | |
} | |
System.out.println("修改前的karry对象:" + karry.toString()); | |
Field age = karry.getClass().getDeclaredField("age"); | |
age.setAccessible(true); | |
age.setInt(karry, 100); | |
System.out.println("修改后的karry对象:" + karry.toString()); | |
} |
# 通过暴力反射执行方法
@Test | |
public void test3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { | |
Class catClass = Cat.class; | |
for (Method declaredMethod : catClass.getDeclaredMethods()) { | |
System.out.println(declaredMethod); | |
System.out.println(declaredMethod.getParameterCount()); | |
System.out.println(declaredMethod.getReturnType()); | |
System.out.println("----------"); | |
} | |
Method run = catClass.getDeclaredMethod("run", String.class, int.class); | |
run.setAccessible(true); | |
run.invoke(new Cat("Karry", 22, true), "北京", 22); | |
} |
# 反射的作用
- 破坏封装性
- 适合做 Java 的框架功能
# 注解
注解本质上是一种元数据(即 "数据的标签"),它本身不包含业务逻辑,只是给代码打上标记。真正起作用的是解析注解的处理器。
# 元注解
# @Retention(保留策略)
指定注解在什么阶段保留。
| 取值 | 保留阶段 | 说明 |
|---|---|---|
SOURCE |
源代码 | 编译时丢弃,只存在于 .java 文件中 |
CLASS |
字节码 | 编译到 .class 文件,但运行时不可用(默认值) |
RUNTIME |
运行时 | 编译到 .class ,运行时可通过反射获取 |
# @Target(适用目标)
指定注解可以用在什么地方。
| 取值 | 目标 |
|---|---|
TYPE |
类、接口、枚举 |
FIELD |
字段(包括枚举常量) |
METHOD |
方法 |
PARAMETER |
方法参数 |
CONSTRUCTOR |
构造函数 |
LOCAL_VARIABLE |
局部变量 |
ANNOTATION_TYPE |
注解类型 |
PACKAGE |
包 |
TYPE_PARAMETER |
泛型类型参数(Java 8) |
TYPE_USE |
类型使用处(Java 8) |