在java开发中确保线程安全已成为基本要求,线程安全就是指某段代码在多线程环境下能够正确的执行,不会出现数据不一致的情况,反之就是非线程安全。
目前解决线程安全的方式有:
- 线程安全类,如AtomicInteger
- 加锁排队执行,如synchronized、reentrantLock
- 线程本地变量,如ThreadLocal
场景分析:创建一个变量num等于0,然后创建线程1,执行1000000次++操作,然后在创建线程2执行1000000次–操作,等线程1和2都执行完之后,打印num变量值,若结果为0,则说明是线程安全的,反之是非线程安全的。
实例1
public class ThreadSafeTest {
// 全局变量
private static int number = 0;
// 循环次数(100W)
private static final int COUNT = 1000000;
public static void main(String[] args) throws InterruptedException {
// 线程1:执行 100W 次 ++ 操作
Thread t1 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
number++;
}
});
t1.start();
// 线程2:执行 100W 次 -- 操作
Thread t2 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
number--;
}
});
t2.start();
// 等待线程 1 和线程 2,执行完,打印 number 最终的结果
t1.join();
t2.join();
System.out.println("最终结果:" + number);
}
}
可见,结果不为0,此为非线程安全。
实例2
public class AtomicIntegerTest {
// 创建 AtomicInteger
private static AtomicInteger number = new AtomicInteger(0);
// 循环次数
private static final int COUNT = 1000000;
public static void main(String[] args) throws InterruptedException {
// 线程1:执行 100W 次 ++ 操作
Thread t1 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
// ++ 操作
number.incrementAndGet();
}
});
t1.start();
// 线程2:执行 100W 次 -- 操作
Thread t2 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
// -- 操作
number.decrementAndGet();
}
});
t2.start();
// 等待线程 1 和线程 2,执行完,打印 number 最终的结果
t1.join();
t2.join();
System.out.println("最终结果:" + number.get());
}
}
可见,结果为0,此为线程安全的,AtomicInteger是线程安全类,++,–操作为原子性操作,解决了非线程安全问题。
实例3
public class SynchronizedTest {
// 全局变量
private static int number = 0;
// 循环次数(100W)
private static final int COUNT = 1000000;
public static void main(String[] args) throws InterruptedException {
// 线程1:执行 100W 次 ++ 操作
Thread t1 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
// 加锁排队执行
synchronized (SynchronizedTest.class) {
number++;
}
}
});
t1.start();
// 线程2:执行 100W 次 -- 操作
Thread t2 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
// 加锁排队执行
synchronized (SynchronizedTest.class) {
number--;
}
}
});
t2.start();
// 等待线程 1 和线程 2,执行完,打印 number 最终的结果
t1.join();
t2.join();
System.out.println("最终结果:" + number);
}
}
可见,结果为0,此为线程安全的,synchronized是jvm实现的同步锁,解决了非线程安全问题。
实例4
public class ReentrantLockTest {
// 全局变量
private static int number = 0;
// 循环次数(100W)
private static final int COUNT = 1000000;
// 创建 ReentrantLock
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
// 线程1:执行 100W 次 ++ 操作
Thread t1 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
lock.lock(); // 手动加锁
try {
number++; // ++ 操作
} finally {
lock.unlock(); // 手动释放锁
}
}
});
t1.start();
// 线程2:执行 100W 次 -- 操作
Thread t2 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
lock.lock(); // 手动加锁
try {
number--; // -- 操作
} finally {
lock.unlock(); // 手动释放锁
}
}
});
t2.start();
// 等待线程 1 和线程 2,执行完,打印 number 最终的结果
t1.join();
t2.join();
System.out.println("最终结果:" + number);
}
}
可见,结果为0,此为线程安全的,reentrantLock为可重入锁,通过自己加锁和释放锁解决非线程安全问题。
实例5
public class ThreadLocalTest {
// 创建 ThreadLocal(设置每个线程中的初始值为 0)
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
// 全局变量
private static int number = 0;
// 循环次数(100W)
private static final int COUNT = 1000000;
public static void main(String[] args) throws InterruptedException {
// 线程1:执行 100W 次 ++ 操作
Thread t1 = new Thread(() -> {
try {
for (int i = 0; i < COUNT; i++) {
// ++ 操作
threadLocal.set(threadLocal.get() + 1);
}
// 将 ThreadLocal 中的值进行累加
number += threadLocal.get();
} finally {
threadLocal.remove(); // 清除资源,防止内存溢出
}
});
t1.start();
// 线程2:执行 100W 次 -- 操作
Thread t2 = new Thread(() -> {
try {
for (int i = 0; i < COUNT; i++) {
// -- 操作
threadLocal.set(threadLocal.get() - 1);
}
// 将 ThreadLocal 中的值进行累加
number += threadLocal.get();
} finally {
threadLocal.remove(); // 清除资源,防止内存溢出
}
});
t2.start();
// 等待线程 1 和线程 2,执行完,打印 number 最终的结果
t1.join();
t2.join();
System.out.println("最终结果:" + number);
}
}
可见,结果为0,此为线程安全的,通过threadLocal线程本地变量解决线程安全问题,给每个线程独自创建一份属于自己的私有变量,不同的线程操作的是不同的变量,从而解决非线程安全问题。