java并发学习--第三章 线程安全问题
线程的安全问题一直是我们在开发过程中重要关注的地方,出现线程安全问题的必须满足两个条件:存在着两个或者两个以上的线程;多个线程共享着共同的一个资源, 而且操作资源的代码有多句。接下来我们来根据JDK自带的方法来解决线程带来的问题。
一、同步代码块synchronized
我们来看一个实例,创建两个线程,每个线程就对计算器i进行减1操作,当i等于0时停止线程
public class Main implements Runnable {
int i = 10;
@Override
public void run() {
while (i > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("这是线程" + Thread.currentThread().getName() + "当前的值是:" + i--);
}
}
public static void main(String[] args) {
Main main = new Main();
Thread t1 = new Thread(main);
Thread t2 = new Thread(main);
t1.start();
t2.start();
}
}
运行的结果是:

可以看到,i的值出现了一次相同的情况,这样就出现了线程安全的问题、
我们来分析这个问题:在两个线程情况下,给i赋值的操作前,我们暂停了1秒,就是说如果此时A线程开始运行,A已经拿到了i的值为2,但是A要暂停1S,但是如果此时B线程抢占资源成功进来了,B也获取了i的值i的值也为2,然后并在暂停后输出了i=2,A在B运行完后也开始输出i=2。
为了解决这个问题,JDK给我们提供了一个关键字synchronized,synchronized是内置锁,放在普通方法上,内置锁锁的是当前类的实例;
被synchronized修饰的方法就会锁住方法中请求的资源,只有当它释放后,其他线程才能进来
public class Main implements Runnable {
int i = 10;
@Override
public synchronized void run() {
while (i > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("这是线程" + Thread.currentThread().getName() + "当前的值是:" + i--);
}
}
public static void main(String[] args) {
Main main = new Main();
Thread t1 = new Thread(main);
Thread t2 = new Thread(main);
t1.start();
t2.start();
}
}
运行的结果绝对不会出现重复的内容:

synchronized除了修饰方法外,还能够直接修饰代码块,直接修饰代码块的好处是,我们只需要在非原则性操作的地方加锁,这样能够提供程序的性能:
public class Main implements Runnable {
int i = 10;
@Override
public void run() {
while (i > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Main.class){
System.out.println("这是线程" + Thread.currentThread().getName() + "当前的值是:" + i--);
}
}
}
public static void main(String[] args) {
Main main = new Main();
Thread t1 = new Thread(main);
Thread t2 = new Thread(main);
t1.start();
t2.start();
}
}
二、volatile关键字
我们来看这样一个多线程场景,有A、B两个线程,这两个线程有公共变量isok,如果A修改了这个变量,如何才能够告诉B这个变量进行了修改,我们来看代码:
public class Main {
//A、B两个线程都会使用这个变量
private static boolean isok = false;
public static void main(String[] args) {
//这个是线程A,当A执行完一段代码后,会修改isok的值
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A线程当前执行了" + i + "次");
}
isok = true;
}
}).start();
//这个是线程B
new Thread(new Runnable() {
@Override
public void run() {
//只有当A线程修改了isok的值,B线程才会停止循环
while (!isok) {
}
System.out.println("B线程开始执行");
}
}).start();
}
}
我们来看看执行的结果:

我们可以看到A线程虽然修改了isok的值,但是B线程不知道,并且此时程序还在运行中,说明B的循环没有停止,简单的说B拿到了isok的值后就锁定了,即使其他的线程进行了修改,B拿到的还是最初始的值。
面对这样的问题,jdk为我们提供了volatile关键字来解决:volatile是轻量级锁,被volatile修饰的变量在线程之间是可见的;volatile具有可见性、有序性,不具备原子性。
可见的定义:一个线程修改了这个变量的值,在另外一个线程中能够读到这个修改后的值。
volatile的主要作用有两个:
1、保证了不同线程对该变量操作的内存可见性
2、禁止了指令重排序
我们来看看改造后的代码,只需要用volatile修饰isok就行:
public class Main {
//A、B两个线程都会使用这个变量
private volatile static boolean isok = false;
public static void main(String[] args) {
//这个是线程A,当A执行完一段代码后,会修改isok的值
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A线程当前执行了" + i + "次");
}
isok = true;
}
}).start();
//这个是线程B
new Thread(new Runnable() {
@Override
public void run() {
//只有当A线程修改了isok的值,B线程才会停止循环
while (!isok) {
}
System.out.println("B线程开始执行");
}
}).start();
}
}
执行后的结果:

这样我们就可以清楚的看到A线程修改了isok的值后,在B线程中也能看到
java并发学习--第三章 线程安全问题的更多相关文章
- java并发学习第五章--线程中的锁
一.公平锁与非公平锁 线程所谓的公平,就是指的是线程是否按照锁的申请顺序来获取锁,如果是遵守顺序来获取,这就是个公平锁,反之为非公平锁. 非公平锁的优点在于吞吐量大,但是由于其不是遵循申请锁的顺序来获 ...
- java并发学习--第六章 线程之间的通信
一.等待通知机制wait()与notify() 在线程中除了线程同步机制外,还有一个最重要的机制就是线程之间的协调任务.比如说最常见的生产者与消费者模式,很明显如果要实现这个模式,我们需要创建两个线程 ...
- java并发学习--第七章 JDK提供的线程工具类
一.ThreadLocal ThreadLocal类用于隔离多线程中使用的对象,为ThreadLocal类中传递的泛型就是要隔离的对象,简单的来说:如果我们在主线程创建了一个对象,并且需要给下面的多线 ...
- java并发编程(三)----线程的同步
在现实开发中,我们或多或少的都经历过这样的情景:某一个变量被多个用户并发式的访问并修改,如何保证该变量在并发过程中对每一个用户的正确性呢?今天我们来聊聊线程同步的概念. 一般来说,程序并行化是为了获得 ...
- Java并发学习之中的一个——线程的创建
本文是学习网络上的文章时的总结,感谢大家无私的分享. 1.与每一个Java语言中的元素一样,线程是对象.在Java中,我们有两种方式创建线程: a.通过直接继承thread类,然后覆盖run方法. b ...
- java并发学习--第四章 JDK提供的线程原子性操作工具类
在了解JDK提供的线程原子性操作工具类之前,我们应该先知道什么是原子性:在多线程并发的条件下,对于变量的操作是线程安全的,不会受到其他线程的干扰.接下来我们就学习JDK中线程的原子性操作. 一.CAS ...
- Java并发学习之十九——线程同步工具之Phaser
本文是学习网络上的文章时的总结.感谢大家无私的分享. JDK 1.7 加入了一个新的工具Phaser.Phaser的在功能上与CountDownLatch有部分重合. 以下使用Phaser类来同步3个 ...
- java并发编程(三)线程挂起,恢复和终止的正确方法
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17095733 下面我们给出不用上述两个方法来实现线程挂起和恢复的策略--设置标志位. ...
- Java并发学习(一):进程和线程
好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航 前言 俗话说得好"一人 ...
随机推荐
- python的filter,reduce,map
1.filter filter(func,iter) 只能处理一个参数(iter),仅仅将满足func方法的数值过滤出来 如: a = [,,,,] list(filter(lambda x:x> ...
- 图论之点双&边双
说人话: 边双联通: a到b的路径上无必经边 点双联通: a到b的路径上除了a,b没有必经点 tarjan求点双联通: 代码(补图) 割点: 桥: 求点双:强制dfs时不越过割点,即可求出一个块 求边 ...
- SQLite入门语句之ALTER命令
SQLite 的 ALTER TABLE 命令不通过执行一个完整的转储和数据的重载来修改已有的表,在 SQLite 中,除了重命名表和在已有的表中添加列,ALTER TABLE 命令不支持其他操作. ...
- tjuthesis 图标题左对齐修改办法
图标题格式默认是居中的. 将 format 文件里定义图表标题样式部分的 \centering 删去,可变为左对齐. 如下: %% 定制浮动图形和表格标题样式\makeatletter\long\de ...
- Oracle-优化SQL语句
建议不使用(*)来代替所有列名 用truncate代替delete 在SQL*Plus环境中直接使用truncate table即可:要在PL/SQL中使用,如: 创建一个存储过程,实现使用trunc ...
- 阶段1 语言基础+高级_1-3-Java语言高级_1-常用API_1_第1节 Scanner类_5-练习二_键盘输入三个数字
思路分析: 获取前两个数字中的看最大值,有多重写法,这里先演示第一种.三元运算符的方式
- 《图解设计模式》读书笔记9-1 Flyweight模式
目录 模式简介 示例代码 代码功能与实现思路 类图 代码 结果图示分析 模式角色和类图 角色 类图 拓展思路 对多个地方产生影响 什么要共享,什么不要共享 垃圾回收 模式简介 Flyweight是轻量 ...
- DFS序1
给一棵有根树,这棵树由编号为1..N的N个结点组成.根结点的编号为R.每个结点都有一个权值,结点i的权值为vi .接下来有M组操作,操作分为两类:1 a x,表示将结点a的权值增加x:2 a,表示求结 ...
- TensorFlow学习笔记6-数值计算基础
TensorFlow学习笔记6-数值计算 本笔记内容为"数值计算的基础知识".内容主要参考<Deep Learning>中文版. \(X\)表示训练集的矩阵,其大小为m ...
- Ecshop二次开发必备基础
EcShop二次开发学习方法 近年来,随着互联网的发展,电子商务也跟着一起成长,B2B,C2C,B2C的电子商务模式也不断的成熟.这时催生出了众多电子商务相关的PHP开源产品.B2C方面有Ecshop ...