首先我先介绍关于对java并发的理解:在保证线程安全的情况下 尽可能的利用多核cpu的优势 缩短程序的运行耗时 提高程序的性能;

基本的方法我就不过多涉及了,下面我就讲解一下我自己对并发中各个难点的认识;

java项目经验中的并发量怎么算 java并发编程深度解析_java项目经验中的并发量怎么算

这个是知乎某篇关于并发编程的个人图表总结原文章地址

关于多线程不安全的理解:

1.多线程时,当线程的cpu时间片用完时,线程就中断了,此时cpu会发生线程上下文切换,而在这个过程中,如果刚好被中断的是这俩个线程的中的共享变量的读写操作,那么会发生错误;

2.多线程时,如果同时执行对共享变量的读写操作,那么有可能会发生指令交错,从而导致结果错误;

如何判断线程是否安全?——关于happens-before原理的介绍:

当一个线程的写,对另外一个线程的读可见,这就是线程安全,反之,线程不安全;

就像下面的代码,只要你能保证线程t1对x的写,对线程t2可见,线程就安全;具体实现有好多种(无锁,有锁,volatile等等)

intx;
new Thread(()->{
x= 5;
},"t1").start();
new Thread(()->{
System.out.println(x);
},"t2").start();

关于变量的线程安全理解:

1.成员变量和静态变量的线程安全

(1)如果它们没有被多个线程所共享,那么它们是线程安全的;

(2)如果它们被多个线程所共享:

1.如果只有读操作,那么是线程安全的

2.如果含有读写操作,那么就要考虑线程是否安全了

2.局部变量的线程安全

(1)局部变量是线程安全的;

(2)但局部变量引用的对象则未必线程安全 例如下面代码,如果同时调用了method1,2那么就不能保证局部变量x对i的引用是否是线程安全的了;

int i = 5;
public void method1(){
int x = i;
System.out.println(x);
}
public void method2(){
new Thread(()->{
this.i = 4;
}).start;
}

关于volatile关键字的原理:

这个原理首先由几个问题引出

1.有序性

Object obj = new Object();

这是一个再熟悉不过的java语句了,如果将其翻译成电脑看的懂的字节码之后是这样的

1: new //表示创建对象,将对象引用入栈2: dup //表示复制一份对象引用3: invokespecial //表示利用一个对象引用,调用构造方法4: putstatic //表示利用一个对象引用,赋值给obj

所以这个语句并不是原子性的,有可能当jvm中的JIT(Java即时编译器)对其字节码的指令做出优化(即重排序后)将3,4行指令执行顺序调换之后,会先执行第4行也就是先执行赋值的话,那么得到的obj对象就为null;

volatile Object obj = new Object();

而volatile这个关键字能保证这句话的有序性,在加入后,就能使jvm不再对其字节码进行重排序;

2.可见性

static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(run){ // ....
}
}); t.start();
sleep(1);
run = false; // 线程t不会如预想的停下来 }

这段代码当执行到run = false时,按理说,线程t会停下来,但线程t并不会停下来,这是为什么呢?

分析一下 刚开始t线程将从主内存un (run = true)读到了自己的工作内存中;然后经过一秒的sleep,main线程将run的值改成false,但此时t线程并不会从主存中再次获取run的值,是因为jvm中的jit为了提高效率避免每次读取从主存中读取run的值,而将run的值保存在了自己工作内存的高速缓存中(这样以后每次获取run的值就不用从主存中获取,提高了性能);所以t线程读取到的值仍是true(高速缓存中的值);

加了volatile的变量就能避免这个问题,volatile能使每次读取变量的值,都从主存中获取;

* volatile的底层原理是实现内存屏障

1.对 volatile 变量的写指令后会加入写屏障(1.可见性:写屏障保证在该屏障之前的,对共享变量的改动,都同步到主存当中  2.有序性:写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后)

2.对 volatile 变量的读指令前会加入读屏障(1.可见性: 读屏障保证在该屏障之后,对共享变量的读取,加载的是主存中新数据 2.有序性:读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前 )

*关于CAS(CompareAndSet或者ComareAndSwap)的实现原理及介绍:

为什么要实现CAS?以及它的用途?

为了避免多线程修改变量时,其读写操作对线程的不可见,所以在CAS中会直接从主存中得到变量的值,而不是在缓存中;

我们可以利用CAS与volatile关键字来使线程安全;

多线程运行环境下,关于其共享变量的修改是不安全的,例如

class TestUnsafe{
int i;
public TestUnsafe(int i){
this.i = i;
}
public void increase (int i){
this.i += i;
}
}

当多线程调用TestUnsafe类中的increase方法时,线程不安全,变量被多个线程所共用,线程不安全;

因为在变量被多个线程所修改时其字节码指令可能发生交错,导致最终得到结果不正确,这时候我们如果使用AtomicInteger类替换int,用CAS实现increase方法时,如下,代码就会被得线程安全了

class Testsafe{
private AtomicInteger AI = new AtomicInteger();
public TestUnsafe(int i){
this.i = i;
}
public int get(){
return this.AI.get();
}
public void increase (int i){
while(true){
int previous = this.get();//获取当前最新的值
int update = previous + i;//计算更新之后的值
if(AI.compareAndSet(previous,update)){//将更新之后的值与现在的值做比较,如果是则返回真,此循环结束,如果否,则一直循环到真为止;
return true;
}
}
}
}

我们从jdk AtomicInteger上的实现上来看

java项目经验中的并发量怎么算 java并发编程深度解析_java项目经验中的并发量怎么算_02

java项目经验中的并发量怎么算 java并发编程深度解析_多线程_03

可以看出AtomicInteger对象中的CAS方法是通过底层Unsafe类来实现的,而Unsafe类不能直接得到,只能通过java反射机制得到;

由此可以设计出自己的AtomicInteger(这里只实现了线程安全的减方法)

public class MyAtomicInteger {
private volatile int value;
private static final long valueOffset;
private static final Unsafe unsafe;
static {
unsafe = UnsafeAccessor.getUnsafe();
try {
valueOffset = unsafe.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));
}catch(NoSuchFieldException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public int getValue() {
return value;
}
public void decrement(int amount) {
while(true) {
int prev = this.value;
int next = prev - amount;
unsafe.compareAndSwapInt(this, valueOffset, prev, next);
}
}
}

关于synchronized的实现原理:

java项目经验中的并发量怎么算 java并发编程深度解析_线程安全_04

Object对象的结构 :Mark Word就是第一行,其中的数字代表加锁的类型;

java项目经验中的并发量怎么算 java并发编程深度解析_java项目经验中的并发量怎么算_05

其中01代表无锁或偏向锁 00代表轻量级锁 10代表重量级锁

java项目经验中的并发量怎么算 java并发编程深度解析_主存_06

先介绍Monitor对象,其中有三个部分分别是WaitSet EntryList Owner ,当线程拿到这个锁之后,也就是进入Owner,此时其他线程就获得不到了锁,即进入EntryList等待在Owner中的线程释放锁,再与其他线程竞争锁(非公平竞争,其中Thread可以设置执行优先级,每个系统的优先级设置范围不一样),拿到锁的线程可以调用wait方法进入WaitSet休息室进行等待,可以用interrupt,notify,notifyall来打断(唤醒)WaitSet中的线程使其进入EntryList中再次与其他线程竞争锁;

当使用synchronized为对象上锁(重量级)时,对象头的Mark Word 就会指向Monitor(类似c语言的指针),执行其中的代码时就如上面所述;

如果一个对象的多线程加锁时间是错开的(非竞争关系的),这时候JVM会对synchronized的加锁进行优化,就会加上轻量级锁,以提高程序的性能,在JDK6以后的版本,

只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现 这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有

即变成偏向锁,当其他线程对此对象加锁时,偏向锁又会变成轻量级锁;

在尝试加轻量级锁的过程中,如果有另外的线程已经给该对象加了轻量级锁时,那么此时会加锁失败(CAS失败),进行锁膨胀过程,将锁升级成重量级锁;