可见性
- 线程与线程之间互相独立私有,线程与线程之间如何通信
- 利用堆内存交换更新数据 达到线程间通信
- 通信不及时,或者永远都不会更新,线程间不可见
- 线程内存模型
- 堆内存:所以线程公共可见
- 栈内存的数据是从堆内存中读来的,读的时机 以及写的时机不受进程控制
- 线程内存:
- 从堆栈获取数据,拷贝副本,对副本进行操作,操作完更新到堆内存
- 堆内存:所以线程公共可见
- 原因:多个线程在使用同一个资源的时候,会产生 某个线程修改资源后,其它线程不知道的情况
- 解决:使用volatile 保证这个被修饰变量 被某个线程更新后,其它线程都知道
有两个线程:Thread1,Thread2;
Thread1由flag决定是否停止;
Thread2修改flag;
使用volatile修饰flag时,Thread2修改flag之后Thread1会知道修改后的flag;
如果没有volatile修饰,运行程序,Thread1可能无法停止,因为它知道的flag始终是true 。
public class test {
// volatile 保证这个被修饰变量 被某个线程更新后,其它线程能够知道
volatile static boolean flag = true;
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
System.out.println("Thread 01 start");
while (flag) {
// System.out.print("");
}
System.out.println("Thread 01 end");
}
}.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread() {
@Override
public void run() {
System.out.println("Thread 02 start");
flag = false;
System.out.println("Thread 02 end");
}
}.start();
}
}
原子同步,与可见性
- 可见性可以解决 原子同步性吗?
- 是不可以的
- Volatile实现内存可见性是通过store和load指令完成的;也就是对volatile变量执行写操作时,会在写操作后加入一条store指令,即强迫线程将最新的值刷新到主内存中;而在读操作时,会加入一条load指令,即强迫从主内存中读入变量的值。但volatile不保证volatile变量的原子性。
- 是不可以的
Lock:
- 隐式锁:不需要手动解锁、自动解锁
- 显示锁:需要手动加锁,手动解锁
- 上锁后一定要确保可以实现对资源的解锁操作
任务Task 有String属性,add() 方法——给String增添字符;
线程WorkThread implements Runnable,属性有Task、Lock、String;
重写run()方法,循环调用30次Task.add() 方法;
在主线程中创建两个WorkThread,在线程安全的情况下运行正确。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Task {
String str = "";
public void add(String value) {
this.str += value;
if (str.length() == 40) {
throw new IndexOutOfBoundsException("测试异常!!!");
}
}
}
class WorkThread implements Runnable{
// 创建一把锁
static Lock lock = new ReentrantLock();
String value;
Task task;
public WorkThread(String value, Task task) {
this.value = value;
this.task = task;
}
@Override
public void run() {
int count = 30;
// System.out.println(task.str);
while (count > 0) {
count--;
lock.lock();
try {
task.add(value);
lock.unlock();
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
lock.unlock();
}
// System.out.println(task.str);
}
}
}
public class TestLock {
public static void main(String[] args) {
Task task = new Task();
WorkThread wt1 = new WorkThread("a", task);
WorkThread wt2 = new WorkThread("b", task);
Thread t1 = new Thread(wt1);
Thread t2 = new Thread(wt2);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(task.str);
System.out.println(task.str.length());
}
}
参考资料
使用线程的场景 - 线程并发安全的产生原因以及解决方法
volitale怎么保证可见性
《Java并发编程实战》Brian Goetz / Tim Peierls / Joshua Bloch / Joseph Bowbeer / David Holmes / Doug Lea