这里写自定义目录标题
代码
普通i++代码(线程不安全)
10000 个线程运行的时候,看看i++是否是线程安全
public class Demo {
public Integer count = 0;
public static void main(String[] args){
final Demo demo = new Demo();
Executor executor= Executors.newFixedThreadPool(10);
for (int i = 0; i <10000 ; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
demo.count++;
}
});
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("final count value:"+demo.count);
}
}
10000 个线程运行的时候,看看i++是否是线程安全最后运行出来的结果,通过结果也可以看出他是不安全的~,所谓的不安全也就是跟我们预想的值不一样。正确答案不一样
final count value:9746
使用Volatile实现i++安全(线程不安全)
public class Volatile {
//定义volatile
private static volatile int count =0 ;
public static void main(String[] args){
Executor executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i <10000 ; i++) {
executorService.execute(()->{
count++;
});
}
try {
Thread.sleep(5000);
}catch (Exception e){
System.out.println(e);
}
System.out.println("this is judy java"+count);
}
}
执行后的结果: this is judy java9998
使用Synchronized 实现i++安全(线程安全)
public class SynchronizedDemo {
private static int count=0;
public static void main(String[] args){
SynchronizedDemo syn = new SynchronizedDemo();
Executor executor= Executors.newFixedThreadPool(10);
for (int i = 0; i < 500000; i++) {
executor.execute(()->{
syn.addCount();
});
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("this is judy method"+count);
}
执行后的结果: this is judy method 9856
使用AtomicInteger 实现线程安全(线程安全)
public class AtomicIntegerDemo {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args){
Executor exception = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
exception.execute(()->{
count.addAndGet(1);
});
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("this is judy java"+count);
}
}
执行最后结果:this is judy java10000 ,哈哈哈安全喽哦 ,哼本姑娘还是很厉害的嘛~
理论
下面是理论知识,都是自己的见解,可能有不正确的地方,希望您可以评论指出问题哦 ,共同学习~
我感觉首先应该把这个图理解, 嘿嘿,不过看一眼估计就可以明白把,
1 .线程A把他所用的变量(引用)放到自己的本地工作区内,方便操作,当然他也要同步与主内存
2.线程B也会同步主内存的变量
必须要理解
1 主内存和工作内存之间的交互2一个变量如何从主内存到工作内存
读的时候
写的时候
2什么是原子性,可见性,有序性
原子性 : Atomicity,他是由java内存模型(java Memory Model, jMM)来直接保证原子性变量操作 ,其实就是read,load, use assign,store,write ,当基本数据类型范文读写是具有原子性的,所以当线程调度的时候要执行完,在去做另一个件事情,否则就别做.
JMM:他是一种规范,包装了多线程通过共享内存进行通信的时候导致本地内存与主内存不一致问题处理器优化问题
可见性: 线程A与线程B,对变量的操作都是可见的,当A操作之后会立刻同步到主内存中,线程b线程会立刻知道该变量值被修改
有序性:在java内存模型中对于单线程来说是有序的,但是对于多线程来说是无须的,因为一个线程在观察另一个线程的时候是无须的,他们各不相干,导致这种原因是因为指令重排序和工作内存与主内存同步延迟问题
归根到底影响以上元素有:缓存一致性问题,处理器优化问题,指令重排问题
- 可见性:volition
- :拒绝处理器优化,使用内存屏蔽
- 全指令重排指的是有序性: Synchronized 和 Volatile ,Volatile进制指令重排序,Synchronized只允许一个线程进入
3.Happens-before
线程A对c的写操作对于线程B的读操作一定会包装线程b知道线程a对c的操作.
假如有这么一段代码,但不一定包装A线程一定会在B线程之前执行
rivate int a
private int b
private int c
a=1
b=2
c=a+b
1如果我们按照正常顺序执行的话那么他们最终得到的结果肯定是没有问题的,但是,在多线程里由于他们之间的关系不是依赖的,所以处理器会对他们优化,也就是指令重排序,所以导致最后结果不一样
转变为
b=2
a=1
c=a+b
10000 个线程运行的时候,看看i++是否是线程安全由于他们之间是没有依赖关系的,所以完全可以让b=2 优先与a=1
CAS(Compare and Swap)比较并交换
需要3步操作
- 变量内存地址 V表示
- 旧的预期值 A表示
- B新值
当1与2的值相等则更新3赋给V,无论最后结果是否改变,都会返回V的值.这整个过程中都是原子性的,因为CAS是通过硬件命令包装了原子性. CAS实现思路类似于乐观锁,因为在更新前她会比较是否与预期值一样,如果一样则更新
为什么i++ 总是不安全
Volatile
Volatile重点突出在线程可见,看下面的图吧~嘿嘿,但他是非原子性操作,不过这个非原子性操作跟这次的 “i++” 倒是不沾边, 因为i++本身就不是安全的~
synchronized
如果使用synchronized可以实现线程同步但是有一定的缺点
- 无法做到细粒度控制,假如我查询商品方法上加上了synchronized,但是我商品的id是一直变化的所以按照情况下我们不希望对不同的商品操作依然锁住方法
- 安全他只适合单点的情况,如果你使用分布式或集群,他只能在一个服务上起作用
AtomicInteger
AtomicInteger他使用CAS指令,所以首先很定符合乐观锁的概念.
看看他的底层是什么
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the updated value
*/
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
unsafe使用CAS指令可以保证读-改-写是一个原子操作
this的意思是AtomicInteger对象
valueOffset表示属性在内存中的位置
delta:当前值增加delta,返回新值
你会发现i++其实和atomicInteger都是执行的++操作,唯一不同的是AtomicInteger使用了CAS,并且是对硬件直接操作的.
| 总结 |
这只是初步的理解,一会还会不断的颠覆自己的认知~加油 judy . 我会持续性的不断更新(最近一直没有更新blog的原因是因为太忙了,项目上线,好多东西要分享不过得过2天就可以喽~)