1. 内存可见性问题: ??
现象:主存中数据改了,但是Main线程中获取的数据没有跟新?
package com.denganzhi.pp;
public class Main {
public static void main(String[] args) {
ThreadDemo threadDemo=new ThreadDemo();
new Thread(threadDemo).start();
while(true){
// synchronized(threadDemo){
if(threadDemo.isFlag()){
System.out.println("--main---");
break;
}
// }
}
}
}
class ThreadDemo implements Runnable{
//private volatile boolean flag=false;
private boolean flag=false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
flag=true;
System.out.println("ThreadDemo->flag:"+flag);
}
public boolean isFlag() {
return flag;
}
}
执行结果: Main 线程 while 循环中获取的 是 false
问题原因:
private boolean flag=false;
是多个线程共享数据,存在主存中,堆内存中
Java 每一个线程运行在栈中,
线程1 获取flag 读到自己的栈中,改掉,flag=true,然后在写到主存中去
Main线程 上来读取 主存数据 flag=false,
Main线程 后面 while(true) 该代码系统底层执行效率非常高,
不会进行系统调用,永远取的是 Main线程中数据, 返回的是false
解决方法1:
synchronized(threadDemo)
保证Main线程每次执行的时候,都到主存中更新一下
但是synchronized效率低,如果一个线程持有锁,另外一个线程阻塞,等待cpu调度
package com.denganzhi.pp;
public class Main {
public static void main(String[] args) {
ThreadDemo threadDemo=new ThreadDemo();
new Thread(threadDemo).start();
while(true){
synchronized(threadDemo){
if(threadDemo.isFlag()){
System.out.println("--main---");
break;
}
}
}
}
}
class ThreadDemo implements Runnable{
//private volatile boolean flag=false;
private boolean flag=false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
flag=true;
System.out.println("ThreadDemo->flag:"+flag);
}
public boolean isFlag() {
return flag;
}
}
运行结果: Main线程获取true
解决效率低问题:
直接操作主存中数据,使用volatile 关键字解决问题
package com.denganzhi.pp;
public class Main {
public static void main(String[] args) {
ThreadDemo threadDemo=new ThreadDemo();
new Thread(threadDemo).start();
while(true){
// synchronized(threadDemo){
if(threadDemo.isFlag()){
System.out.println("--main---");
break;
}
// }
}
}
}
class ThreadDemo implements Runnable{
private volatile boolean flag=false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
flag=true;
System.out.println("ThreadDemo->flag:"+flag);
}
public boolean isFlag() {
return flag;
}
}
输出结果:Main线程获取true
volatile问题:
1. volatile 不具备 "互斥性"
2. volatile 不能保证 变量的 "原子性"
2. 什么是原子性:
int i=0;
i++
int temp =i ; // i存储主存中, 线程A读存入temp
i=i+1; // 线程A执行
i= temp; // 线程A把 值 写入主存中
上面是3个操作不可以分隔, 叫做原子性
但是 在线程A 读取的时候线程 B也读取了,
A 写入数据的时候, 写入以后主存数据变1
B 也写入, 写入以后主存储数据变1
破坏原子性
这个时候会有2个重复数据 ,比如下列代码:
package com.denganzhi.pp;
public class Main2 {
public static void main(String[] args) {
AtomicDemo auAtomicDemo=new AtomicDemo();
for (int i = 0; i < 10; i++) {
new Thread(auAtomicDemo).start();
}
}
}
class AtomicDemo implements Runnable{
private int serialNumber=0;
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getId()+ " name:" + getSerialNumber());
}
public int getSerialNumber() {
return serialNumber++;
}
}
执行 结果:
出现重复数据,原子性被破坏
CAS算法: JVM 底层 对CAS算法 进行了支持,解决上面问题,但是没有解决原子性问题
多次线程可以同时执行,只有其中一个 线程 可以执行成功,但是不会阻塞线程
原理:
内存值 V
预估值 A
更新值 B
当且 当 V==A 时, V=B ,否则不做任何操作
例子:
线程A:
读1 V=0
sn=sn+1
读2 A=0,在写入之前读取,V==A
B=1 写入 主存中
线程B:
读1 V=0
sn=sn+1
读2 A=1 V != A
写入失败
线程C....失败
线程D... 失败,失败了,但是没有阻塞,CPU还可以继续调度,就是由于这个原因,优于 synchronized
使用 CAS 算法解决上面问题:
* jdk1.5 原子性提供api接口:
*java.util.concurrent.atomic 下提供 类 实现了CAS 算法,来解决原子性,并没有解决原子性(要解决只能使用sychronized)
AtomicBoolean
AtomicInteger: Int类型
AtomicIntegerArray 数组
AtomicIntegerFieldUpdater
AtomicLong
AtomicLongArray
AtomicLongFieldUpdater
AtomicMarkableReference
AtomicReference
AtomicReferenceArray
AtomicReferenceFieldUpdater
AtomicStampedReference
代码实现:
public class Main2 {
// 实现抢票 用例
public static void main(String[] args) {
AtomicDemo auAtomicDemo=new AtomicDemo();
Thread thread=null;
for (int i = 0; i < 7; i++) {
thread=new Thread(auAtomicDemo);
thread.setName("线程:"+i );
thread.start();
}
}
}
class AtomicDemo implements Runnable{
// 还 剩下 5 张票 ,7个人来 抢票
AtomicInteger automicInteger=new AtomicInteger(5);
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
int num=getSerialNumber();
if(num <= 0){
System.err.println(Thread.currentThread().getName() +" 没有抢到火车票");
}else{
System.out.println(Thread.currentThread().getName() +" 抢到第" + num + "张火车票");
}
}
public int getSerialNumber() {
return automicInteger.getAndDecrement(); //--
}
}
线程:4 抢到第2张火车票
线程:3 抢到第1张火车票
线程:0 没有抢到火车票
线程:1 抢到第5张火车票
线程:6 抢到第3张火车票
线程:2 抢到第4张火车票
线程:5 没有抢到火车票
3. JUC 问题:
jdk1.5 以后 Java 提供了JUC 处理多线程
java.util.concurrent.* 下 ConcurrentHashMap 也实现线程安全的,使用Java8 CAS实现【底层C】
都是 哈希表 :功能对比
java.util.concurrent.* 包下类 在高并发 效率高比 java.utils.hashmap 【简称 juc】
LIst、hashmap: 线程不安全
Vector、hashtable:线程安全的,底层使用锁实现,只有一个线程可以访问hashtable
Jdk1.5以后,解决效率低问题:
CopyOnWriteArraySet/CopyOnWriteArrayList/ConcurrentHashMap 使用 CAS算法实现底层
public class Main {
/**
* 每一个线程有个 一个ThreadLocal
* 为单独线程 共享数据
*/
public static void main(String[] args) {
/**
* jdk1.5 以后 Java 提供了JUC 处理多线程
* 线程安全容器
* Vector,Hashtable 线程安全容器,使用syschronized实现
* java.util.concurrent.* 下 ConcurrentHashMap 也实现线程安全的,使用Java8 CAS实现【底层C】
* 都是 哈希表 :功能对比
* java.util.concurrent.* 包下类 在高并发 效率高比 java.utils.hashmap 【简称 juc】
*/
ConcurrentHashMap<String, String> con=new ConcurrentHashMap<>();
con.put("name", "小明");
System.out.println(con.get("name"));
}
}