java并发学习第三章--线程安全问题
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了java并发学习第三章--线程安全问题,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含4431字,纯文字阅读大概需要7分钟。
内容图文
线程的安全问题一直是我们在开发过程中重要关注的地方,出现线程安全问题的必须满足两个条件:存在着两个或者两个以上的线程;多个线程共享了着一个资源, 而且操作资源的代码有多句。接下来我们来根据JDK自带的方法来解决线程带来的问题。
一、同步代码块synchronized
我们来看一个实例,创建两个线程,每个线程就对计算器i进行减1操作,当i等于0时停止线程
public class Main implements Runnable { int i = 10; @Override publicvoid run() { while (i > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("这是线程" + Thread.currentThread().getName() + "当前的值是:" + i--); } } publicstaticvoid 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 publicsynchronizedvoid run() { while (i > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("这是线程" + Thread.currentThread().getName() + "当前的值是:" + i--); } } publicstaticvoid 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 publicvoid run() { while (i > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (Main.class){ System.out.println("这是线程" + Thread.currentThread().getName() + "当前的值是:" + i--); } } } publicstaticvoid 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; publicstaticvoid main(String[] args) { //这个是线程A,当A执行完一段代码后,会修改isok的值new Thread(new Runnable() { @Override publicvoid 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(); //这个是线程Bnew Thread(new Runnable() { @Override publicvoid 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; publicstaticvoid main(String[] args) { //这个是线程A,当A执行完一段代码后,会修改isok的值new Thread(new Runnable() { @Override publicvoid 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(); //这个是线程Bnew Thread(new Runnable() { @Override publicvoid run() { //只有当A线程修改了isok的值,B线程才会停止循环while (!isok) { } System.out.println("B线程开始执行"); } }).start(); } }
执行后的结果:
这样我们就可以清楚的看到A线程修改了isok的值后,在B线程中也能看到
三、JDK提供的原子类
上面我们介绍了volatile关键字是不具备原子性操作,那么什么是原子性呢,原子性是指一个操作是不可中断的。即使是多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
我们来看一个实例
public class Main implements Runnable { //定义一个静态变量staticint i = 0; publicstaticvoid main(String[] args) { Main main = new Main(); //创建10个线程,每个线程都输出I的值while (i < 10) { new Thread(main).start(); i++; } } @Override publicvoid run() { System.out.println("当前是第" + i + "线程"); } }
运行的结果是:
可以看到i的值有多个重复,证明这段代码的操作是线程不安全的。
面对这样的问题,除了前面介绍的使用synchronized外,我们还可以使用JDK给我们提供的原子类AtomicInteger来进行:
public class Main implements Runnable { //定义一个静态变量publicstatic AtomicInteger i = new AtomicInteger(0); publicvoid addI() { i.incrementAndGet(); } publicstaticvoid main(String[] args) throws InterruptedException { Main main = new Main(); //创建10个线程,每个线程都输出I的值while (i.get()<10) { Thread.sleep(10); new Thread(main).start(); } } @Override publicvoid run() { addI(); System.out.println("当前是第" + i.get() + "线程"); } }
输出的结果是:
原文:https://www.cnblogs.com/daijiting/p/11524086.html
内容总结
以上是互联网集市为您收集整理的java并发学习第三章--线程安全问题全部内容,希望文章能够帮你解决java并发学习第三章--线程安全问题所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。