目录
一、Java多线程并发
1.1 理解线程模型与内存语义
1.2 线程同步机制
1.3 并发工具类
1.4 线程池与任务管理机制
1.5 理解ThreadPoolExecutor的源码和原理
二、加密算法与安全漏洞
2.1 对称加密:AES、DES
2.2 非对称加密:RSA、DSA
2.3 摘要算法:MD5、SHA
2.4 XSS 跨站脚本攻击
2.5 SQL 注入
2.6 CSRF(跨站请求伪造)
2.7 JAR 包升级与依赖安全
一、Java多线程并发
1.1 理解线程模型与内存语义
首先明确
(1)进程:是一个正在执行中的程序。
每一个进程执行都是有一个执行顺序,这个顺序是一个执行路径,或者叫一个控制单元。
(2)线程:就是进程中的一个独立的控制单元。
线程在控制着进程的执行。
一个进程中至少有一个线程。
jvm启动的时候会有一个进程java.exe,该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码在main方法里,该线程称为主线程。
那么java对线程这类事物提供了一个描述,就是Thread类。
(3) 并行与并发
并行:多个处理器同时执行多个线程(物理层面的同时)。
并发:多个线程在同一处理器上交替执行(逻辑层面的同时)。
(4)同步机制与内存语义
synchronized
关键字
- 语义:确保同一时刻只有一个线程执行同步块,并在解锁时将工作内存的变量刷新到主内存,加锁时从主内存读取最新值。
volatile
关键字
-
语义:
- 保证变量的可见性:写操作会立即刷新到主内存,读操作会从主内存读取。
- 禁止指令重排序:通过内存屏障确保特定操作的顺序。
-
原子类(Atomic Classes) :通过 CAS(Compare-and-Swap)实现无锁原子操作,如
AtomicInteger
。 -
显式锁(Lock) :
ReentrantLock
提供与synchronized
类似的语义,但更灵活(可中断、可定时)。 -
并发容器 :
ConcurrentHashMap
、CopyOnWriteArrayList
等,内部通过锁分段或写时复制保证线程安全
1.2 线程同步机制
传统同步方式
synchronized
关键字
-
作用范围:
- 实例方法:锁当前对象实例。
- 静态方法:锁当前类的 Class 对象。
- 代码块:锁指定对象。
js
public class SynchronizedExample {
private int count = 0;
// 实例方法锁(this)
public synchronized void increment() {
count++;
}
// 静态方法锁(Class对象)
public static synchronized void staticMethod() {
// ...
}
// 代码块锁
public void blockLock() {
synchronized(this) {
count++;
}
}
}
ait()
、notify()
、notifyAll()
- 作用 :用于线程间协作,必须在
synchronized
块中调用。
js
public class ProducerConsumer {
private final List<Integer> buffer = new ArrayList<>();
private final int MAX_SIZE = 10;
public synchronized void produce(int value) throws InterruptedException {
while (buffer.size() == MAX_SIZE) {
wait(); // 等待消费者消费
}
buffer.add(value);
notifyAll(); // 通知消费者
}
public synchronized int consume() throws InterruptedException {
while (buffer.isEmpty()) {
wait(); // 等待生产者生产
}
int value = buffer.remove(0);
notifyAll(); // 通知生产者
return value;
}
}
JUC 包中的高级同步工具
1. ReentrantLock
(可重入锁)
- 特性 :与
synchronized
类似,但支持公平锁、可中断锁、条件变量。
js
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final Lock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock(); // 必须在finally中释放锁
}
}
}
ReadWriteLock
(读写锁)
- 特性:允许多个线程同时读,但写时互斥。适用于读多写少场景。
js
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Cache {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private Map<String, Object> cache = new HashMap<>();
public Object get(String key) {
rwLock.readLock().lock(); // 读锁
try {
return cache.get(key);
} finally {
rwLock.readLock().unlock();
}
}
public void put(String key, Object value) {
rwLock.writeLock().lock(); // 写锁
try {
cache.put(key, value);
} finally {
rwLock.writeLock().unlock();
}
}
}
Condition
接口
- 作用 :替代传统的
wait()
/notify()
,支持多个等待队列。
js
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BoundedQueue {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Object[] items = new Object[10];
private int count, head, tail;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await(); // 队列满时等待
items[tail] = x;
if (++tail == items.length)
tail = 0;
++count;
notEmpty.signal(); // 通知取元素线程
} finally {
lock.unlock();
}
}
}
1.3 并发工具类
CountDownLatch
- 作用:让一个或多个线程等待其他线程完成操作。
js
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("子线程执行...");
latch.countDown(); // 计数器减1
}).start();
}
latch.await(); // 等待所有子线程完成
System.out.println("所有子线程执行完毕");
}
}
CyclicBarrier
- 作用:让一组线程到达某个屏障时被阻塞,直到所有线程都到达后继续执行。
js
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程准备就绪,开始执行...");
});
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 准备就绪");
barrier.await(); // 等待其他线程
System.out.println(Thread.currentThread().getName() + " 开始执行");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
Semaphore
(信号量)
- 作用:控制同时访问特定资源的线程数量。
js
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static final int THREAD_COUNT = 10;
private static final Semaphore SEMAPHORE = new Semaphore(3); // 允许3个线程同时访问
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
try {
SEMAPHORE.acquire(); // 获取许可
System.out.println(Thread.currentThread().getName() + " 获取许可");
Thread.sleep(2000);
SEMAPHORE.release(); // 释放许可
System.out.println(Thread.currentThread().getName() + " 释放许可");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
同步方式 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
synchronized |
简单互斥场景 | 语法简洁,自动释放锁 | 不可中断,无法实现公平锁 |
ReentrantLock |
复杂同步场景(公平锁、可中断锁) | 灵活性高 | 需要手动释放锁 |
ReadWriteLock |
读多写少场景 | 提高读并发 | 写锁可能饥饿 |
Atomic 类 |
简单原子操作(计数、ID 生成) | 无锁,性能高 | 只能保证单个变量原子性 |
CountDownLatch |
一个 / 多个线程等待其他线程 | 一次性使用 | 不可重置 |
CyclicBarrier |
一组线程相互等待 | 可重复使用 | 实现复杂 |
1.4 线程池与任务管理机制
线程池的优势
- 降低资源消耗:复用已创建的线程,减少线程创建和销毁的开销。
- 提高响应速度:任务到达时无需等待线程创建,直接使用池中的线程执行。
- 统一管理线程:控制最大并发数,避免资源耗尽,提供定时执行、定期执行等功能。
线程池的工作流程
-
提交任务 :调用
execute(Runnable)
或submit(Callable)
方法提交任务。 -
线程池判断:
- 若核心线程数未满,创建新线程执行任务。
- 若核心线程已满 且任务队列未满,将任务放入队列。
- 若任务队列已满 且线程数未达最大,创建新线程执行任务。
- 若线程数已达最大 ,触发拒绝策略。
1.5 源码
js
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 线程空闲时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
任务提交方法
execute(Runnable command)
:提交无返回值的任务。submit(Callable<T> task)
:提交有返回值的任务,返回Future
对象。
java
状态控制:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 高3位表示线程池状态,低29位表示线程数量
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
execute 方法核心逻辑:
scss
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 1. 核心线程未满,创建新线程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2. 核心线程已满,任务入队
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 3. 队列已满,尝试创建新线程
else if (!addWorker(command, false))
reject(command); // 4. 创建失败,触发拒绝策略
}
二、加密算法与安全漏洞
2.1 对称加密:AES、DES
AES(高级加密标准)
AES 是目前最流行的对称加密算法,取代了 DES。它支持 128、192 和 256 位密钥长度,分组长度为 128 位。其基本流程包括:
- 密钥扩展:将用户密钥扩展成多个轮密钥
- 初始轮:异或轮密钥
- 多轮处理:包括 SubBytes(字节替换)、ShiftRows(行移位)、MixColumns(列混合)和 AddRoundKey(轮密钥加)
- 最终轮:省略 MixColumns
DES(数据加密标准)
DES 是一种较旧的对称加密算法,密钥长度 56 位(实际 64 位,包含 8 位奇偶校验)。由于密钥过短,已不安全,逐渐被 AES 取代。其基本流程与 AES 类似但更简单,采用 Feistel 网络结构,经历 16 轮加密。
分组模式
-
ECB(电子密码本)
- 最简单的模式,将明文分成固定大小的块,每块单独加密
- 相同明文块生成相同密文块,不安全,易受统计分析攻击
-
CBC(密码块链接)
- 每个明文块先与前一个密文块异或,再加密
- 需要初始向量(IV),IV 必须随机且不重复使用
- 提供了更好的安全性,但不支持并行处理
-
GCM(计数器模式)
- 结合了计数器模式(CTR)和 GMAC 认证
- 提供加密和认证,支持并行处理
- 需要随机 IV,推荐用于需要完整性保护的场景
js
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
public class AESEncryption {
// 生成随机密钥
public static SecretKey generateKey(int keySize) throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(keySize, SecureRandom.getInstanceStrong());
return keyGen.generateKey();
}
// 生成随机IV
public static IvParameterSpec generateIV() {
byte[] iv = new byte[16]; // AES块大小为16字节
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(iv);
return new IvParameterSpec(iv);
}
// 加密
public static String encrypt(String plaintext, SecretKey key, IvParameterSpec iv) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encryptedBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
// 解密
public static String decrypt(String ciphertext, SecretKey key, IvParameterSpec iv) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(ciphertext));
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
}
2.2 非对称加密:RSA、DSA
RSA
- 基于大整数因子分解难题
- 既能用于加密,也能用于签名
- 密钥长度通常为 2048 位或更高
- 加密过程:密文 = 明文 ^E mod N
- 解密过程:明文 = 密文 ^D mod N
- 缺点:速度慢,通常用于交换对称密钥
DSA(数字签名算法)
- 专门用于数字签名,不能用于加密
- 基于离散对数难题
- 与 RSA 相比,签名生成速度更快,但验证速度较慢
- 通常与 SHA 系列算法配合使用
js
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
public class RSAExample {
// 生成密钥对
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(2048);
return keyPairGen.generateKeyPair();
}
// 加密
public static String encrypt(String plaintext, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
// 解密
public static String decrypt(String ciphertext, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(ciphertext));
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
}
2.3 摘要算法:MD5、SHA
MD5
- 生成 128 位哈希值
- 已被证明不安全,存在碰撞攻击
- 不推荐用于安全场景,仅用于文件完整性校验等非安全场景
SHA-1
- 生成 160 位哈希值
- 已被证明不安全,存在碰撞攻击
- 逐渐被 SHA-2 和 SHA-3 取代
SHA-256/SHA-512
- SHA-2 系列包括 SHA-224、SHA-256、SHA-384 和 SHA-512
- 目前被广泛认为是安全的
- SHA-256 生成 256 位哈希值,SHA-512 生成 512 位哈希值
java
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class HashExample {
// MD5(仅作示例,不推荐用于安全场景)
public static String md5(String input) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hashBytes = md.digest(input.getBytes(StandardCharsets.UTF_8));
return bytesToHex(hashBytes);
}
// SHA-256
public static String sha256(String input) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hashBytes = md.digest(input.getBytes(StandardCharsets.UTF_8));
return bytesToHex(hashBytes);
}
// 字节数组转十六进制字符串
private static String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}
// 加盐哈希示例
public static String saltedHash(String password, String salt) throws NoSuchAlgorithmException {
String saltedPassword = password + salt;
return sha256(saltedPassword);
}
}
2.4 XSS跨站脚本攻击
攻击类型
-
存储型 XSS
- 恶意脚本存储在服务器端(如数据库)
- 所有访问该页面的用户都会执行该脚本
- 危害最大,如论坛、评论系统等
-
反射型 XSS
- 恶意脚本通过 URL 参数传递
- 服务器将参数内容反射到 HTML 响应中
- 仅影响点击恶意链接的用户
-
DOM 型 XSS
- 攻击发生在客户端 DOM 处理阶段
- 页面本身不改变,但用户浏览器中的 DOM 被修改
- 通过 JavaScript 动态修改 DOM 时易受攻击
防护手段
-
输出转义
- 对用户输入进行 HTML 实体编码
- 如将
<
转为<
,>
转为>
-
HTML 过滤
- 对用户输入的 HTML 进行白名单过滤
- 只允许安全的标签和属性
-
CSP(内容安全策略)
- 通过 HTTP 头指定允许加载的资源来源
- 防止加载恶意脚本
- 示例:
Content-Security-Policy: default-src 'self'; script-src 'self' trusted.example.com
2.6 CSRF (跨站请求伪造)
攻击原理
- 攻击者诱导用户在已登录的网站上执行恶意操作
- 利用用户的会话 Cookie 或认证信息
- 例如:用户已登录银行网站,攻击者诱导用户访问恶意网站,该网站向银行网站发送转账请求
防护手段
-
CSRF 令牌
- 在表单或 URL 中包含随机令牌
- 服务器验证请求中的令牌
-
SameSite Cookie 属性
- 设置 Cookie 的 SameSite 属性为 Strict 或 Lax
- 阻止跨站请求携带 Cookie
- 示例:
Set-Cookie: session=12345; SameSite=Strict
-
验证请求来源
- 检查 HTTP 头中的 Referer 或 Origin 字段
- 确保请求来自合法来源
2.7 JAR包升级与依赖安全
漏洞检测
GitHub Dependabo
- 自动检测和提醒依赖漏洞
- 支持自动创建 PR 更新依赖
OWASP Dependency-Check
diff
- 静态分析工具,检测项目依赖中的已知漏洞
- 支持 Maven、Gradle、npm 等多种构建工具
依赖冲突管理
-
Maven 依赖处理
- 使用
mvn dependency:tree
查看依赖树 - 使用
<exclusions>
排除冲突依赖
- 使用
-
版本规范
- 在 Maven 中使用
<dependencyManagement>
- 在 Maven 中使用
-
使用单一版本源
- 避免从多个不同仓库引入相同依赖的不同版本
xml
#### 使用 OWASP Dependency-Check
<!-- 在pom.xml中添加插件 -->
<build>
<plugins>
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>8.3.1</version>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>