Java线程安全问题的解决方案(java线程安全问题的原因)

网友投稿 295 2022-08-06


Java线程安全问题的解决方案(java线程安全问题的原因)

目录线程安全问题演示解决线程安全问题1.原子类AtomicInteger2.加锁排队执行2.1 同步锁synchronized2.2 可重入锁ReentrantLock3.线程本地变量ThreadLocal总结

前言:

线程安全是指某个方法或某段代码,在多线程中能够正确的执行,不会出现数据不一致或数据污染的情况,我们把这样的程序称之为线程安全的,反之则为非线程安全的。在 java 中,

解决线程安全问题有以下 3 种手段:

使用线程安全类,比如 AtomicInteger。加锁排队执行使用 synchronized 加锁。使用 ReentrantLock 加锁。使用线程本地变量 ThreadLocal。

接下来我们逐个来看它们的实现。

线程安全问题演示

我们创建一个变量 number 等于 0,之后创建线程LdAgnMRnw 1,执行 100 万次 ++ 操作,同时再创建线程 2 执行 100 万次 -- 操作,等线程 1 和线程 2 都执行完之后,打印 number 变量的值,如果打印的结果为 0,则说明是线程安全的,否则则为非线程安全的,

示例代码如下:

public class ThreadSafeTest {

// 全局变量

private static int number = 0;

// 循环次数(100W)

private static final int COUNT = 1_000_000;

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 最终结果:" + number);

}

}

以上程序的执行结果如下图所示:

从上述执行结果可以看出,number 变量最终的结果并不是 0,和预期的正确结果不相符,这就是多线程中的线程安全问题。

解决线程安全问题

1.原子类AtomicInteger

AtomicInteger 是线程安全的类,使用它可以将 ++ 操作和 -- 操作,变成一个原子性操作,这样就能解决非线程安全的问题了,

如下代码所示:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerExample {

// 创建 AtomicInteger

private static AtomicInteger number = new AtomicInteger(0);

// 循环次数

private static final int COUNT = 1_000_000;

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.priLdAgnMRnwntln("最终结果:" + number.get());

}

}

以上程序的执行结果如下图所示:

2.加锁排队执行

Java 中有两种锁:synchronized 同步锁和 ReentrantLock 可重入锁。

2.1 同步锁synchronized

synchronized 是 JVM 层面实现的自动加锁和自动释放锁的同步锁,它的实现代码如下:

public class SynchronizedExample {

// 全局变量

private static int number = 0;

// 循环次数(100W)

private static final int COUNT = 1_000_000;

public static void main(String[] args) throws InterruptedException {

// 线程1:执行 100W 次 ++ 操作

Thread t1 = new Thread(() -> {

for (int i = 0; i < COUNT; i++) {

// 加锁排队执行

synchronized (SynchronizedExample.class) {

number++;

}

}

});

t1.start();

// 线程2:执行 100W 次 -- 操作

Thread t2 = new Thread(() -> {

for (int i = 0; i < COUNT; i++) {

// 加锁排队执行

synchronized (SynchronizedExample.class) {

number--;

}

}

});

t2.start();

// 等待线程 1 和线程 2,执行完,打印 number 最终的结果

t1.join();

t2.join();

System.out.println("number 最终结果:" + number);

}

}

以上程序的执行结果如下图所示:

2.2 可重入锁ReentrantLock

ReentrantLock 可重入锁需要程序员自己加锁和释放锁,它的实现代码如下:

import java.util.concurrent.locks.ReentrantLock;

/**

* 使用 ReentrantLock 解决非线程安全问题

*/

public class ReentrantLockExample {

// 全局变量

private static int number = 0;

// 循环次数(100W)

private static final int COUNT = 1_000_000;

// 创建 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(); // 手动加锁

number++; // ++ 操作

lock.unlock(); // 手动释放锁

}

});

t1.start();

// 线程2:执行 100W 次 -- 操作

Thread t2 = new Thread(() -> {

for (int i = 0; i < COUNT; i++) {

lock.lock(); // 手动加锁

number--; // -- 操作

lock.unlock(); // 手动释放锁

}

});

t2.start();

// 等待线程 1 和线程 2,执行完,打印 number 最终的结果

t1.join();

t2.join();

System.out.println("number 最终结果:" + number);

}

}

以上程序的执行结果如下图所示:

3.线程本地变量ThreadLocal

使用 ThreadLocal 线程本地变量也可以解决线程安全问题,它是给每个线程独自创建了一份属于自己的私有变量,不同的线程操作的是不同的变量,所以也不会存在非线程安全的问题,它的实现代码如下:

public class ThreadSafeExample {

// 创建 ThreadLocal(设置每个线程中的初始值为 0)

private static ThreadLocal threadLocal = ThreadLocal.withInitial(() -> 0);

// 全局变量

private static int number = 0;

// 循环次数(100W)

private static final int COUNT = 1_000_000;

public static void main(String[] args) throws InterruptedException {

// 线程1:执行 100W 次 ++LdAgnMRnw 操作

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);

}

}

以上程序的执行结果如下图所示:

总结

在 Java 中,解决线程安全问题的手段有 3 种:1.使用线程安全的类,如 AtomicInteger 类;2.使用锁 synchronized 或 ReentrantLock 加锁排队执行;3.使用线程本地变量 ThreadLocal 来处理。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Java通俗易懂讲解泛型(java泛型的定义和使用)
下一篇:API 管理简史及其发展方向,api管理系统php源码
相关文章

 发表评论

暂时没有评论,来抢沙发吧~