volatile 关键字 使一个变量在多个线程可见
- AB 线程都使用到一个变量。 java 默认是A 线程中保留一份copy。 这样如果B线程修改了该变量。 则A线程未必知道
- 使用了volatile关键字。 会在变量被修改的时候。 通知每一个线程。 让所有的线程都会读到变量的修改值。
public class T {
/*volatile*/ boolean running = true; // 对比一下有无volatile的情况下,整个程序运行结果的区别。
void m() {
System.out.println("m.start");
while (running) {
}
System.out.println("m.end");
}
/**
* 不使用volatile 每一个线程都会由自己的一块内存和cpu的缓冲区,就是线程自己存放变量的位置。
* 每一个线程都会把原来的变量复制到自己的内存中来进行执行。 所以线程之间的变量是不可见的。
* 使用了volatile 之后, 不是指每次我执行的时候,都要去读一下这个变量的值。而是当我有线程修改了这个变量的值。
* 都会对每一个线程进行通知一下这个值已经改变了需要重新去读取。 缓存过期通知
* @param args
*/
public static void main(String[] args) {
T t = new T();
new Thread(t::m,"t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t.running = false;
}
}
在下面的代码中,running 是存在于对内存的 t 对象中。
- 当线程 t1 开始运行的时候。 会把running 值 从内存中读到 t1 线程的工作区,在运行过程中直接使用这个复制过来的变量。
- 并不会每次都要去读取堆内存,当主线程修改 running 的值之后,t1线程感知不到。 所以不会停止运行。
- 使用volatile 将会强制所有的线程都去堆内存中去读取 running 的值
- volatile 并不能保证多个线程共同修改running 变量时所带来的不一致问题,也就是说volatile 不能替代synchronized
volatile 并不能保证 多个线程共同修改running 变量时所带来的不一致问题。也就是说volatile不能够代理synchronized
** synchronized 可以保证可见性和原子性。 volatile 只能保证可见性 **
public class T {
volatile int count = 0;
/*synchronized*/ void m() {
for (int i = 0; i < 10000; i++) {
count ++;
}
}
public static void main(String[] args) {
T t = new T();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(t::m,"thread" + i));
}
threads.forEach((o)->o.start());
threads.forEach((o)->{
try {
o.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
try {
Thread.sleep(3000); // 让主线程沉睡 三秒,等待其他线程执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t.count); // 打印数值
}
}
AtomicXXX 类
/**
* 解决同样的问题的更高效的方法。 使用AtomicXXX类
* AtomicXXX类本身方法都是原子性的。 但不能保证多个方法连续调用时原子性的
* @author gssznb
*
*/
public class T {
AtomicInteger count = new AtomicInteger(0);
/*synchronized*/ void m() {
for (int i = 0; i < 10000; i++) {
count.incrementAndGet();
}
}
public static void main(String[] args) {
T t = new T();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(t::m,"thread" + i));
}
threads.forEach((o)->o.start());
threads.forEach((o)->{
try {
o.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
淘宝的面试题
曾经的面试题:淘宝
实现一个容器,提供两个方法,add,size
写两个线程,线程1 添加十个元素到容器中,线程2 实现监控元素的个数, 当个数到五个的时候。线程2 给出提示并结束
两种解决方式
第一种
package cn.fllday.A16_active02;
import java.util.ArrayList;
import java.util.List;
/**
* 曾经的面试题:淘宝
* 实现一个容器,提供两个方法,add,size
* 写两个线程,线程1 添加十个元素到容器中,线程2 实现监控元素的个数, 当个数到五个的时候。线程2 给出提示并结束
*
* 思路:
* 给list 添加volatile。 t2 能够接到通知,但是t2线程的死循环会浪费cpu。如果不使用死循环。该怎么做呢?
*
* 这里使用wait 和 notify 。 wait 会释放锁。而notify不会释放锁。
* 需要注意的是:运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以。
*
* notify 之后。t1 必须释放锁。t2 推出后,也必须notify 通知t1 继续执行
* sleep是不会释放锁的。 notify也是不会释放锁的。wait 是会释放锁的。
* @author gssznb
*
*/
public class Mycontainer02 {
volatile List<Object> list = new ArrayList<>();
public void add(Object o) {
list.add(o);
}
public int size() {
return list.size();
}
public static void main(String[] args) {
final Object lock = new Object();
Mycontainer02 m2 = new Mycontainer02();
new Thread(new Runnable() {
@Override
public void run() {
synchronized(lock) {
if (m2.size()!=5) {
try {
System.out.println("唤醒其他线程自己沉睡");
lock.notifyAll();
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程结束");
lock.notifyAll();
}
}
},"t2") .start();;
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
for (int i = 0; i < 10; i++) {
m2.add(new Object());
System.out.println("添加第" + i + "个");
if (m2.size()==5) {
lock.notifyAll();
try {
System.out.println("自身等待");
lock.wait();
} catch (InterruptedException e) {
}
}
}
}
}
}).start();;
}
}
第二种
package cn.fllday.A17_active;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* 曾经的面试题:淘宝
* 实现一个容器,提供两个方法,add,size
* 写两个线程,线程1 添加十个元素到容器中,线程2 实现监控元素的个数, 当个数到五个的时候。线程2 给出提示并结束
*
* 思路:
* 给list 添加volatile。 t2 能够接到通知,但是t2线程的死循环会浪费cpu。如果不使用死循环。该怎么做呢?
*
* 这里使用wait 和 notify 。 wait 会释放锁。而notify不会释放锁。
* 需要注意的是:运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以。
*
* notify 之后。t1 必须释放锁。t2 推出后,也必须notify 通知t1 继续执行
* sleep是不会释放锁的。 notify也是不会释放锁的。wait 是会释放锁的。
*
* 使用 Latch 门闩 替代wait notify 通知t1继续执行
* 好处是通信简单,同时也可以指定等待事件
* 使用await 和 countdown 方法替代 wait 和 notify
* CountDownLatch 不涉及锁定,当count 的值为零的时候当前线程继续运行
* 当不涉及同步,只是涉及线程通信的时候,用synchronized 和 wait/notify 就显得太重了。
* 这时候应该考虑 countdownlatch/cyclicbarrier/semaphore
* @author gssznb
*
*/
public class MyContainer03 {
volatile List<Object> list = new ArrayList<>();
public void add(Object o) {
list.add(o);
}
public int size() {
return list.size();
}
public static void main(String[] args) {
MyContainer03 m3 = new MyContainer03();
CountDownLatch latch = new CountDownLatch(1);
new Thread(new Runnable() {
@Override
public void run() {
if (m3.size()!=5) {
try {
latch.await();
System.out.println("线程结束");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}).start();;
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("添加" + i +"个");
m3.add(new Object());
if (m3.size() == 5) {
System.out.println("添加到第五个");
// 每countDown一次就会 减少一次门闩
latch.countDown();
}
}
}
}).start();;
}
}