一、Properties
1. 概述
java.util.Properties 类继承自 Hashtable,是 Java 中用于处理配置文件的核心工具类。它以 键值对(key-value) 的形式存储数据,键和值均为字符串类型,常用于读取和写入配置文件(如 .properties 文件)。
2. Properties 基本操作
(1) 创建 Properties 对象
java
Properties prop = new Properties();
(2) 设置属性(setProperty)
java
prop.setProperty("username", "root");
prop.setProperty("password", "123456");
prop.setProperty("url", "jdbc:mysql://localhost:3306/test");
(3) 获取属性(getProperty)
java
String username = prop.getProperty("username");
String password = prop.getProperty("password");
String driver = prop.getProperty("driver", "com.mysql.cj.jdbc.Driver"); // 带默认值
(4) 移除属性(remove)
java
prop.remove("password");
(5) 遍历属性
java
// 方式1:通过 keySet() 遍历
Set<String> keys = prop.stringPropertyNames();
for (String key : keys) {
String value = prop.getProperty(key);
System.out.println(key + "=" + value);
}
// 方式2:通过 entrySet() 遍历
Set<Map.Entry<Object, Object>> entries = prop.entrySet();
for (Map.Entry<Object, Object> entry : entries) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
2. Properties 与配置文件
(1) 读取 .properties 文件
java
Properties prop = new Properties();
try (InputStream in = new FileInputStream("config.properties")) {
prop.load(in); // 从输入流加载配置
} catch (IOException e) {
e.printStackTrace();
}
String username = prop.getProperty("username");
String password = prop.getProperty("password");
(2) 写入 .properties 文件
java
Properties prop = new Properties();
prop.setProperty("username", "root");
prop.setProperty("password", "123456");
prop.setProperty("url", "jdbc:mysql://localhost:3306/test");
try (OutputStream out = new FileOutputStream("config.properties")) {
// 第二个参数是注释,第三个参数是日期格式
prop.store(out, "Database Configuration");
} catch (IOException e) {
e.printStackTrace();
}
二、线程的创建方式(3 种核心方式)
1. 方式 1:继承 Thread 类
(1) 步骤:
- 自定义类继承
Thread; - 重写
run()方法(定义线程执行的任务); - 创建子类对象,调用
start()方法启动线程(注意:不可直接调用 run (),否则会以主线程方式执行)。
(2) 代码示例:
java
package demo1;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "HelloWorld");
}
}
}
java
package demo1;
public class test1{
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程1"); // 不太理解
t2.setName("线程2");
t1.start();
t2.start();
}
}
关键逻辑 1: t1.setName("线程1");
① 这个方法的作用是给这个线程对象起一个名字
② 为什么需要给线程起名字?
- 调试和日志:这是最主要的原因。当一个程序中有多个线程在同时运行时,你在控制台打印日志或使用调试器(Debugger)时,很难区分哪一行输出是来自哪个线程的。给线程起一个有意义的名字(比如 "用户登录验证线程"、"数据备份线程")可以让你一目了然,极大地提高调试效率。
- 监控和管理:在一些复杂的应用中,你可能需要通过线程的名称来监控系统状态、进行性能分析或动态管理线程。
关键逻辑 2:为什么可以直接写getName()
在这段代码中,MyThread类继承了Thread类 (extends Thread)。而Thread类本身就提供了getName()方法,用于获取线程的名称(这是Thread类的内置方法)。
2. 方式 2:实现 Runnable 接口
(1) 步骤:
- 自定义类实现
Runnable接口; - 重写
run()方法(定义任务); - 创建
Runnable实现类对象,作为参数传入Thread构造器; - 调用
Thread对象的start()方法启动线程。
(2) 代码示例:
java
// 1. 实现Runnable接口(解耦任务与线程)
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
public static void main(String[] args) {
// 2. 创建任务对象
MyRunnable task = new MyRunnable();
// 3. 传入Thread构造器,创建线程对象
Thread t1 = new Thread(task, "线程1");
Thread t2 = new Thread(task, "线程2");
// 4. 启动线程
t1.start();
t2.start();
}
}
(3) 方式 3:实现 Callable 接口(带返回值)
① 场景:
需要获取线程执行结果(如多线程计算后汇总结果)。
② 步骤:
- 自定义类实现
Callable<V>接口(V为返回值类型); - 重写
call()方法(任务逻辑,可抛异常,有返回值); - 创建
Callable实现类对象,包装为FutureTask(实现Future和Runnable接口,用于接收结果); - 将
FutureTask传入Thread构造器,调用start()启动线程; - 调用
FutureTask.get()方法获取返回值(会阻塞主线程,直到子线程执行完成)。
③ 代码示例:
java
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
// 1. 实现Callable,指定返回值类型为Integer
public class MyCallable implements Callable<Integer> {
private int num; // 计算1~num的和
public MyCallable(int num) {
this.num = num;
}
// 2. 重写call(),有返回值、可抛异常
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= num; i++) {
sum += i;
}
return sum;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 3. 创建Callable任务对象
MyCallable task = new MyCallable(100);
// 4. 包装为FutureTask(兼具Runnable和Future功能)
FutureTask<Integer> futureTask = new FutureTask<>(task);
// 5. 启动线程
new Thread(futureTask, "计算线程").start();
// 6. 获取结果(主线程会阻塞,直到子线程返回结果)
Integer result = futureTask.get();
System.out.println("1~100的和:" + result); // 输出5050
}
}
④ 核心特点:
- 带返回值、可抛异常;
FutureTask.get()可取消任务(cancel())、判断任务是否完成(isDone())。
三、线程的常用 API
| 方法名 | 作用 | 注意事项 |
|---|---|---|
Thread.currentThread() |
获取当前执行的线程对象 | 静态方法,常用于日志打印或线程身份判断 |
thread.setName(String) |
设置线程名称 | 建议设置有意义的名称(如 "订单处理线程"),便于问题排查 |
thread.getName() |
获取线程名称 | 默认名称:Thread-0、Thread-1... |
Thread.sleep(long millis) |
让当前线程休眠指定毫秒数 | 静态方法,休眠时不会释放已持有的锁;需捕获InterruptedException |
thread.join() |
让调用者线程等待当前线程执行完毕 | 如t1.join(),主线程会等待 t1 执行完再继续;可传超时时间(join(long)) |
Thread.yield() |
让当前线程主动放弃 CPU,进入就绪状态 | 静态方法,仅为 "建议",CPU 是否调度其他线程由操作系统决定 |
thread.setPriority(int) |
设置线程优先级(1~10,默认 5) | 优先级仅为 "调度提示",高优先级线程不一定先执行(依赖 OS 调度) |
Thread.interrupt() |
中断线程(设置中断标志位) | 不会直接终止线程,需线程内部判断Thread.interrupted()处理 |
(1) Thread.currentThread()、getName()、setName()(线程名称操作)
用于获取当前线程对象、获取 / 设置线程名称。
java
public class ThreadApiDemo1 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
// 获取当前线程对象
Thread currentThread = Thread.currentThread();
// 获取默认线程名(格式:Thread-0、Thread-1...)
System.out.println("默认线程名:" + currentThread.getName());
// 自定义线程名称
currentThread.setName("我的自定义线程");
System.out.println("修改后线程名:" + currentThread.getName());
});
t.start();
}
}
(2) Thread.sleep(long millis)(线程休眠)
让当前线程暂停执行指定时间(毫秒),常用于模拟耗时操作。
java
public class ThreadApiDemo2 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " 正在执行...");
try {
// 休眠1秒(1000毫秒)
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "休眠演示线程");
t.start();
}
}
(3) setPriority(int)、getPriority()(线程优先级)
设置 / 获取线程优先级(范围1~10,默认5)。优先级仅为调度 "建议",实际执行顺序仍由操作系统决定。
java
public class ThreadApiDemo4 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()
+ "(优先级" + Thread.currentThread().getPriority() + ")执行");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "高优先级线程");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()
+ "(优先级" + Thread.currentThread().getPriority() + ")执行");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "低优先级线程");
// 设置t1为最高优先级(10),t2为最低优先级(1)
t1.setPriority(Thread.MAX_PRIORITY); // Thread.MAX_PRIORITY等价于10
t2.setPriority(Thread.MIN_PRIORITY); // Thread.MIN_PRIORITY等价于1
t1.start();
t2.start();
}
}
(4) Thread.yield()(主动让出 CPU)
建议性地让当前线程放弃 CPU 使用权,进入 "就绪状态",但最终是否调度其他线程由操作系统决定。
java
public class ThreadApiDemo5 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " 执行中...");
// 主动让出CPU,建议调度其他线程
Thread.yield();
}
}, "yield演示线程");
t.start();
}
}
(5) thread.join()(线程等待)
让调用者线程 等待目标线程执行完毕后再继续。例如:主线程等待子线程t1执行完,再执行后续逻辑。
java
public class ThreadApiDemo3 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("子线程t1 执行第 " + (i+1) + " 轮");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
Thread mainWaitThread = new Thread(() -> {
try {
// 主线程等待t1执行完毕
t1.join();
System.out.println("t1执行完毕,主线程继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "主线程-等待");
t1.start();
mainWaitThread.start();
}
}
四、线程同步与线程安全
1. 线程安全问题的原因
① 线程安全问题指多线程并发访问共享资源时,出现结果与预期不符的情况(如超卖、余额计算错误),核心原因有 3 点:
(1) 抢占式执行:CPU 随机调度线程,导致线程执行顺序不确定;
(2) 共享资源:多个线程访问同一份可变资源(如共享变量、数据库记录);
(3) 非原子操作 :对共享资源的操作需多步完成(如i++,实际分为 "读 i→i+1→写 i" 三步),中间可能被其他线程打断。
② 代码示例(线程安全问题演示):
java
package demo3;
public class MyThread extends Thread{
static int ticket = 0;
@Override
public void run() {
while (true) {
if (ticket < 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
} else {
break;
}
}
}
}
java
package demo3;
public class test1 {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
2. 解决线程安全:synchronized 关键字
synchronized是 Java 内置的 "互斥锁",确保同一时间只有一个线程进入 "同步代码块",从而保证操作的原子性、可见性和有序性(避免指令重排序)。
① synchronized 的用法
用法 1:同步代码块(锁定指定对象)
java
synchronized (锁对象) {
// 需保证线程安全的代码(共享资源操作)
}
(1) 锁对象要求 :必须是 "多线程共享的对象"(如this、静态对象、Class 对象),若每个线程用独立对象,锁会失效。
(2) 代码示例(修复线程安全问题):
java
package demo3;
public class MyThread extends Thread{
static int ticket = 0;
static Object lock = new Object();
@Override
public void run() {
while (true) {
synchronized (lock) {
if (ticket < 100) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
} else {
break;
}
}
}
}
}
用法 2:同步实例方法(锁定当前对象this)
java
public synchronized void method() {
// 线程安全的代码
}
- 等价于:
synchronized (this) { 方法体 }; - 注意:若多个线程操作不同的实例对象 ,锁会失效(因
this不同)。
用法 3:同步静态方法(锁定当前类的 Class 对象)
java
public static synchronized void method() {
// 线程安全的代码
}
- 等价于:
synchronized (当前类.class) { 方法体 }; - 特点:Class 对象是全局唯一的,即使多个实例对象,也会共享同一把锁。
3. 解决线程安全:Lock 接口(JUC)
java.util.concurrent.locks.Lock是 JUC 提供的锁接口,比synchronized更灵活(可手动加锁 / 解锁、可中断、可超时获取锁),常用实现类是ReentrantLock(可重入锁)。
(1) ReentrantLock 的基本用法
java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo {
private static int count = 0;
// 创建ReentrantLock对象(默认非公平锁,可传true设为公平锁)
private static final Lock LOCK = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[1000];
for (int i = 0; i < 1000; i++) {
threads[i] = new Thread(() -> {
try {
// 1. 手动加锁(必须在try块外或try块首,避免加锁前抛异常)
LOCK.lock();
// 2. 线程安全操作
count++;
} finally {
// 3. 手动解锁(必须在finally中,确保锁一定释放,避免死锁)
LOCK.unlock();
}
});
}
for (Thread t : threads) t.start();
for (Thread t : threads) t.join();
System.out.println("最终count:" + count); // 1000
}
}