概念:指在多线程环境下,某个代码、函数或对象能够被多个线程同时调用或访问时,仍能保持正确的行为和数据一致性。简单来说,线程安全的代码在多线程环境下运行可靠,不会因线程间的交互而产生不可预测的结果
示例:
public class ThreadDemo {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(()->{
for (int i = 0; i < 500000; i++) {
count++;
}
});
Thread thread2 = new Thread(()->{
for (int i = 0; i < 500000; i++) {
count++;
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("count = " + count);//每次执行的结果都不一致
}
}按照上述代码的逻辑,期望得到的结果是
1000000,但实际计算的结果与期望值不一致线程不安全:当多个线程同时访问或修改共享资源时,由于缺乏适当的同步机制,可能导致程序行为不可预测、数据损坏或错误结果的现象
1.访问修改共享变量:当多个线程同时读写同一内存区域时,可能导致数据状态不一致2.原子性:原子性指一个操作是不可分割的单元,要么完全执行,要么完全不执行。如果操作不是原子的,在并发环境下,线程可能被中断在中间状态,导致部分修改
3.内存可见性:在多线程编程中,每个线程都有自己的工作内存(本地内存),用于存储共享变量的副本。由于CPU缓存、编译器优化等因素,操作可能只发生在工作内存中,而不是直接在主内存中进行,导致程序行为不符合预期
4.指令重排序:是计算机处理器或编译器为了提高程序执行效率,对指令执行顺序进行优化的一种技术。在保证程序最终结果正确的前提下,允许指令的执行顺序与代码编写的顺序不一致。但可能导致多线程下的逻辑错误
5.线程之间抢占式执行:这是操作系统层面的调度机制,线程的执行顺序是随机的和不可预测的。操作系统可能随时中断一个线程(抢占),切换到另一个线程执行。一般不轻易改变,当引发线程安全时优先考虑前4个原因
共享变量访问修改是线程安全问题的前提,但需结合2/3/4才会引发问题;抢占式执行是线程调度的特性,无法避免
synchronized(监视器锁monitor lock):用于实现线程同步,确保多线程环境下对共享资源的访问安全。通过加锁机制,防止多个线程同时访问同步块代码或对象,避免数据不一致问题
确保了代码块的原子性,即被同步的代码块在执行过程中不会被其他线程中断。这意味着在一个线程执行完整个同步块之前,其他线程无法进入同一个同步块,从而保证了操作的完整性

public class ThreadDemo {
//锁对象
private static final Object locker = new Object();
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(()->{
for (int i = 0; i < 500000; i++) {
synchronized (locker) {
count++;
}
}
});
Thread thread2 = new Thread(()->{
for (int i = 0; i < 500000; i++) {
synchronized (locker) {
count++;
}
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("count = " + count);//1000000
}
}获取锁时:线程会将工作内存中的变量副本失效,强制从主内存重新读取最新值释放锁时:线程会将工作内存中修改过的变量刷新到主内存 
这种机制确保了共享变量的修改对所有线程立即可见
确保在同一时间只有一个线程可以进入被同步的代码块或方法,这意味着当一个线程进入同步块或方法时,其他试图进入同一同步块的线程会被阻塞,直到第一个线程退出同步块

synchronized关键字是可重入的,这意味着如果一个线程已经持有某个对象的锁,那么它可以再次获取该对象的锁,而不会被阻塞
public class Reentry_Lock {
public static void main(String[] args) {
Object locker = new Object();
Thread thread = new Thread(()->{
synchronized (locker){
System.out.println("第一层锁");
synchronized (locker){
System.out.println("第二层锁");
}
}
});
thread.start();
}
}作用于对象实例,每个对象实例拥有自己的锁。当一个线程访问对象的synchronized实例方法或代码块时,其他线程无法访问该对象的其他synchronized方法或代码块,但可以访问非synchronized方法或代码块
public class Example {
// 实例方法锁
public synchronized void instanceMethod() {
// 同步代码
}
// 实例代码块锁
public void anotherMethod() {
synchronized (this) {
// 同步代码
}
}
}作用于类的Class对象,所有实例共享同一把锁。当一个线程访问synchronized静态方法或代码块时,其他线程无法访问该类的其他synchronized静态方法或代码块,但可以访问非synchronized静态方法或代码块
public class Example {
// 静态方法锁
public static synchronized void staticMethod() {
// 同步代码
}
// 静态代码块锁
public static void anotherStaticMethod() {
synchronized (Example.class) {
// 同步代码
}
}
}
概念:指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,导致这些线程都无法继续执行下去。这种情况下,系统资源被占用,但程序无法继续运行

死锁产生的必要条件:
预防死锁:通过破坏死锁的四个必要条件之一,可以预防死锁的发生
volatile:是编程语言中的关键字,用于修饰变量,告知编译器该变量可能被意外修改。其核心作用是防止编译器优化导致的数据不一致问题(在Java中仅能修饰成员变量)
对volatile变量的每次访问都会强制从主内存读取,每次修改都会立即写回主内存
public class demo_volatile {
//每次访问都会强制从主内存读取,每次修改都会立即写回主内存
//去除volatile关键字会导致thread1线程在访问num时不从主内存读取
public static volatile int num = 0;
public static void main(String[] args) {
//thread1线程的生命周期掌握在thread2手中
Thread thread1 = new Thread(()->{
while (num == 0){}
System.out.println("Over thread1");
});
Thread thread2 = new Thread(()->{
System.out.println("请输入一个整数");
Scanner in = new Scanner(System.in);
num = in.nextInt();
});
thread1.start();
thread2.start();
}
}在多线程场景下,指令重排序可能会导致线程间数据同步问题。
volatile变量通过插入内存屏障(Memory Barrier)来禁止重排序


public class FixedReorderingExample {
int a = 0;//普通变量
int b = 0;//普通变量
volatile boolean flag = false;//标志变量使用volatile
// 写线程方法
public void writer() {
a = 1;
b = 1;
flag = true;//volatile 写,插入写屏障:确保flag写操作在a、b写操作之后
}
boolean demo = true
// 读线程方法
public void reader() {
if (flag) {//volatile 读,插入读屏障:确保println读操作不会再if读操作之前
int r1 = a;
int r2 = b;
System.out.println("r1: " + r1 + ", r2: " + r2);//总是输出 r1: 1, r2: 1
}
}
}volatile不保证操作的原子性,多线程环境下仍需结合锁或原子操作
概念:wait()和notify()是Java中用于线程间通信的机制,属于Object类的方法。它们必须在同步代码块(如synchronized块)中使用,否则会抛出IllegalMonitorStateException
public class Demo {
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread thread1 = new Thread(()->{
//thread1拿到锁
synchronized (locker) {
System.out.println("thread1线程wait之前");
try {
//thread1释放锁,进入waiting状态,等待被唤醒
locker.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("thread1线程wait之后");
}
});
Thread thread2 = new Thread(()->{
//thread1进入waiting之后,thread2拿到锁
synchronized (locker){
System.out.println("thread2线程notify之前");
//虽然notify执行之后thread1被唤醒了,但此时仍处于thread2的synchronized中
//同一对象才能唤醒
locker.notify();
System.out.println("thread2线程notify之后");
}
});
thread1.start();
Thread.sleep(1000);
thread2.start();
}
}
wait与sleep的区别