线程安全与线程不安全
来源:互联网 发布:数据库置疑脱机怎么办 编辑:程序博客网 时间:2024/05/04 18:56
什么是线程安全?
比较官方的解释如下:
当多个线程访问某个类,不管运行时环境采用何种调度方式或者这些线程如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类为线程安全的。
上面的解释你可能看起来会懵,但没关系,接下来用代码代码来解释下什么是线程安全。
先说线程不安全
package thread;import java.util.concurrent.CountDownLatch;/** * * @author wiggin * 线程不安全类 * */public class Unsafe {private static int num;//使用CountDownLatch是为了保证10个线程全部执行完public static CountDownLatch countDownLatch = new CountDownLatch(10);public static void increate () {num++;}public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) {//java8的lambda表达式new Thread(() -> {int j = 1000;while (j > 0) {increate();j--;}//每个线程执行完,调countDown,让其减一countDownLatch.countDown();}).start();}while (true) { if (countDownLatch.getCount() == 0) {System.out.println(num);break;}}}}
上面面的程序启动十个线程,每个线程对数字num进行1000次累加,理想状态下累加完成后最终num为10000,但是你可以试着跑多次,你会发现每次出现的结果都不一样。
之所以会出现这样的情况,是因为++操作并不是原子操作,虽然它看起来只有一行,而且非常紧凑,但是他包含了三个操作:
① 读取num的值
② 将值+1
③ 计算结果赋值给num
从代码生成的字节码序列可看出上面的三步操作
public static void increate();
Code:
0: getstatic #2 获取指定类的静态域,并将其押入栈顶
3: iconst_1 将int型1推送至栈顶
4: iadd 将栈顶两int整形值相加,并将结果押入栈顶
5: putstatic #2 为指定的类的静态域赋值
8: return 返回
多线程操作的时候,就可能出现同一时刻多个线程读取到同样的num值,之后进行累加,这就会导致结果的不准确。大概流程如下所示:
怎么能让它变成线程安全的呢?
有人可能想到将num用volatile去修饰变量,然而遗憾的是,volatile值保证可见性,但是并不保证原子性。也就是说:当前线程修改num值之后,对其他线程立即可见,但依旧没法保证num++是一个原子性的操作。也就还是同样会出现上面那样的情况。
正确的方法有:
① 使用同步synchronized关键字
package thread;import java.util.concurrent.CountDownLatch;/** * * @author wiggin * 线程安全类 * */public class Safe {private static int num;//使用CountDownLatch是为了保证10个线程全部执行完public static CountDownLatch countDownLatch = new CountDownLatch(10);public static void increate () {synchronized(countDownLatch){num ++;}}public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) {//java8的lambda表达式new Thread(() -> {int j = 1000;while (j > 0) {increate();j--;}//每个线程执行完,调countDown,让其减一countDownLatch.countDown();}).start();}while (true) { if (countDownLatch.getCount() == 0) {System.out.println(num);break;}}}}
② 将num修改为AtomicInteger变量
package thread;import java.util.concurrent.CountDownLatch;import java.util.concurrent.atomic.AtomicInteger;/** * * @author wiggin * 线程安全类 * */public class Safe {//将变量改为AtomicIntegerprivate static AtomicInteger num = new AtomicInteger(0);//使用CountDownLatch是为了保证10个线程全部执行完public static CountDownLatch countDownLatch = new CountDownLatch(10);public static void increate () {num.incrementAndGet();}public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) {//java8的lambda表达式new Thread(() -> {int j = 1000;while (j > 0) {increate();j--;}//每个线程执行完,调countDown,让其减一countDownLatch.countDown();}).start();}while (true) { if (countDownLatch.getCount() == 0) {System.out.println(num);break;}}}}
③ 使用锁
package thread;import java.util.concurrent.CountDownLatch;import java.util.concurrent.locks.ReentrantLock;/** * * @author wiggin * 线程安全类 * */public class Safe {private static int num;//使用锁private static ReentrantLock reentrantLock = new ReentrantLock();//使用CountDownLatch是为了保证10个线程全部执行完public static CountDownLatch countDownLatch = new CountDownLatch(10);public static void increate () {//使用reentrantLock必须显式释放锁try {reentrantLock.lock();num ++;} finally {reentrantLock.unlock();}}public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) {//java8的lambda表达式new Thread(() -> {int j = 1000;while (j > 0) {increate();j--;}//每个线程执行完,调countDown,让其减一countDownLatch.countDown();}).start();}while (true) { if (countDownLatch.getCount() == 0) {System.out.println(num);break;}}}}
- 线程安全与不安全
- 线程安全与不安全
- 线程安全与不安全
- 线程安全与不安全
- 线程安全与不安全
- 线程安全与不安全
- 线程安全与线程不安全
- 线程安全与线程不安全
- 线程安全与线程不安全
- 线程安全与线程不安全
- 线程安全与线程不安全
- Vector 线程安全与不安全
- Java线程安全与不安全
- 线程安全与线程不安全的区别
- Java线程(一):线程安全与不安全
- Java线程(一):线程安全与不安全
- Java线程(一):线程安全与不安全
- Java线程(一):线程安全与不安全
- - 青蛙的约会 exgcd 扩欧
- C\C++中使用_CrtDumpMemoryLeaks进行内存泄漏检测
- PAT a1019题解
- 字符设备驱动
- 接收double类型变量输入
- 线程安全与线程不安全
- PAT a1020题解
- 二分搜索(2)
- telnet 127.0.0.1 1433连接失败
- 《机器学习》阅读心得——四、决策树
- PAT a1021题解
- Raspberry 3B+: UART调试树莓派
- linux下的多线程/多进程同步/通信机制
- 使用VideoToolBox对获取到的视频进行编码