版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010285684/article/details/80090001
一、使用Runnable来实现多线程
package com.xiancheng.old;
/**
* 使用Runnable的方式来实现多线程<BR>
* 开发者 : SGX <BR>
* 时间:2018年4月26日 上午9:51:31 <BR>
* 变更原因: <BR>
* 首次开发时间:2018年4月26日 上午9:51:31 <BR>
* 版本:V1.0
*/
public class ThreadOld implements Runnable {
private boolean falg = false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
falg = true;
}
public boolean isFalg() {
return falg;
}
public void setFalg(boolean falg) {
this.falg = falg;
}
}
二、测试
- 在没有使用synchronized或轻量组锁volatile的代码如下
/**
* 需求:当flag为真的时候结束程序。
*
* @Author: SGX <BR>
* @Datetime:2018年4月26日 上午9:53:21 <BR>
*/
@Test
public void test() {
ThreadOld old = new ThreadOld();
new Thread(old).start();
// 无限循环
for (;;) {
// 当flag为true的时候就结束程序
// 这个时候出了问题,就是flag在子线程更改后,写入主线程后,主线读取不到,这就是内存可见性问题
// 针对这个问题,1.可以用同步代码块,2.可以用轻量级锁volitle来解决
if (old.isFalg()) {
assertTrue("当为真的时候应该结束程序", old.isFalg());
break;
}
}
}
运行这个代码的时候就出现了问题,程序迟迟不会结束。
造成这个问题的原因是什么呢。先看主线程和子线程内存模型
子线程已经把falg改为true了,为什么主线程读取不到呢,因为我们的子线程在改动falg的值后没有告诉主线程,主线程拿不到已经已经修改后的falg的值。所以解决方法如下。
1. 用同步代码块
public class ThreadOld implements Runnable {
private boolean falg = false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
falg = true;
}
public boolean isFalg() {
return falg;
}
public void setFalg(boolean falg) {
this.falg = falg;
}
}
@Test
public void test() {
ThreadOld old = new ThreadOld();
new Thread(old).start();
// 无限循环
for (;;) {
synchronized(old) {
// 当flag为true的时候就结束程序
// 这个时候出了问题,就是flag在子线程更改后,写入主线程后,主线读取不到,这就是内存可见性问题
// 针对这个问题,1.可以用同步代码块,2.可以用轻量级锁volitle来解决
if (old.isFalg()) {
assertTrue("当为真的时候应该结束程序", old.isFalg());
break;
}
}
}
}
2. 用volatile关键字
public class ThreadOld implements Runnable {
private volatile boolean falg = false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
falg = true;
}
public boolean isFalg() {
return falg;
}
public void setFalg(boolean falg) {
this.falg = falg;
}
}
@Test
public void test() {
ThreadOld old = new ThreadOld();
new Thread(old).start();
// 无限循环
for (;;) {
// 当flag为true的时候就结束程序
// 这个时候出了问题,就是flag在子线程更改后,写入主线程后,主线读取不到,这就是内存可见性问题
// 针对这个问题,1.可以用同步代码块,2.可以用轻量级锁volitle来解决
if (old.isFalg()) {
assertTrue("当为真的时候应该结束程序", old.isFalg());
break;
}
}
}
三、内存可见性问题的解决
1. 内存可见性(Memory Visibility)当一个线程修改了变量的值,新的值会立刻同步到主内存当中。而其他线程读取这个变量的时候,也会从主内存中拉取最新的变量值。
2. 可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。
3. 我们可以通过同步来保证对象被安全地发布。除此之外我们也可以使用一种更加轻量级的volatile 变量。
4.为什么volatile关键字可以有这样的特性?
这是因为java语言的先行发生原则(happens-before)。先行发生原则是两个事件的结果之间的关系,如果一个事件发生在另一个事件之前,结果必须反映给另一个事件。这里所谓的事件,实际上就是各种指令操作,比如读操作、写操作、初始化操作、锁操作等等。先行发生原则作用于很多场景下,包括同步锁、线程启动、线程终止、volatile。(这段话摘自程序员小灰微信公众号)
四、为什么要使用volatile
比原来的synchroized轻量,效率高,对于多线程不是一各互斥关系,保证程序指令不重排序
五、volatile的业务使用
1.运行结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
2.但是不能变量的原子性操作。变量不需要与其他的状态变量共同参与不变约束(状态)。如下代码
public class VolatileAtomicity {
private volatile static int start = 2;
private volatile static int end = 4;
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (start < end) {
System.out.println(start + " : " + end);
}
System.exit(0);
}
}, "线程一").start();
new Thread(new Runnable() {
@Override
public void run() {
for(;;) {
start += 2;
end += 2;
}
}
}, "线程二").start();
}
}
while(start < end)会在某个瞬间造成start==end,从而结束程序。