Java并发基础知识点详解

1.synchronized与Lock区别
 父类有synchtonized,子类调用父类的同步方法,是没办法同步的,因为synchronized不是修饰符,不会被继承下来。
 synchronized : 关键字,并且依赖于JVM,作用对象的作用范围内都是同一时刻只能有一个线程对其操作的
 Lock : 接口类,依赖特殊的CPU指定,使用代码实现,常用子类ReentrantLock
2.synchronized 使用
修饰代码块:大括号括起来的代码,也称同步代码块,作用与调用的对象
修饰方法:整个方法,也称同步方法,作用与调用的对象
修饰静态方法:整个静态方法,作用于类的所有对象
修饰类:括号括起来的部分,作用与类的所有对象
3.可见性:一个线程对主内存的修改可以及时的被其他线程观察到。
导致共享变量在线程间不可见的原因:
​ 1>线程交叉执行
​ 2>重排序结合线程交叉执行
​ 3>共享变量更新后的值没有在工作内存与主存间及时更新
对于可见性,JVM提供了 synchronized 和 volatile
​ JMM关于synchronized的两条规定:
​ a.线程解锁前,必须把共享变量的最新值刷新到主内存
​ b.线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁与解锁是同一把锁)
​ volatile:通过加入内存屏障和禁止重排序优化来实现
​ a.对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存中
​ b.对volatile变量读操作是,会在读操作前加入一条load屏障指令,从主内存中读取共享变量
​ 结论:
​ volatile进行加操作是线程不安全的,不适合计数场景
​ volatile关键字不具有原子性
​ 使用场景 使用volatile必须具备两个条件:
​ 1>对变量的写操作,不依赖于当前值
​ 2>该变量没有包含在具有其他变量的不变式子中
​ 因此volatile适合作为状态的标记量
4.有序性
   java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性:
  volatile、synchronized、Lock:通过 volatile、synchronized、Lock 保证一定的有序性。显然,synchronized、Lock 保证每一个时刻只有一个线程可以执行被同步的代码,相当于让线程顺序执行同步代码,从而保证有序性。另外,JMM具备一些先天的有序性,即不需要额外的手段,就能保证有序性,即 Happens-before 原则,如果两个操作的执行次序,没有办法通过 Happens-before 原则推导出来,虚拟机进行随意的重排序,那么就不能保证有序行。
​ happens-before
​ 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
​ 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作;
​ volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
​ 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
​ 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
​ 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
​ 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结 束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
​ 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;

5.安全发布对象
  多线程并发环境下,线程安全极为重要。往往一些问题的发生都是由于不正确的发布了对象造成了对象逸出而引起的,因此如果系统开发中需要发布一些对象,必须要做到安全发布,以免造成安全隐患。
发布对象:使一个对象能够被当前范围之外的代码所使用
对象逸出:一种错误的发布情况,当一个对象还没有构造完成时,就使它被其他线程所见
  在我们的日常开发中,经常要发布一些对象,比如通过类的非私有方法返回对象的引用,或者通过公有静态变量发布对象。
发布与逸出
​ 一个导致this引用在构造期间逸出的错误,是在构造函数中启动一个线程,无论是隐式启动线程,还是显式启动线程,都会造成this引用逸出,新线程总会在所属对象构造完毕前看到它。所以如果要在构造函数中创建线程,那么不要启动它,而应该采用一个专有的start或initialize方法来统一启动线程。我们可以采用工厂方法和私有构造函数来完成对象创建和监听器的注册,这样就可以避免不正确的创建。记住,我们的目的是,在对象未完成构造之前,不可以将其发布。
  安全发布对象的四种方法:
​ 在静态初始化函数中初始化一个对象引用
​ 将对象的引用保存到volatile类型域或者AtomicReference对象中
​ 将对象的引用保存到某个正确构造对象的final类型域中
​ 将对象的引用保存到一个由锁保护的域中
​ 注意:静态域与静态代码块是顺序执行的,若顺序有误会出现空指针异常

6.线程安全策略【不可变对象】
  对象创建后状态不能被修改的对象叫作不可变对象。不可变对象天生就是线程安全的。它们的常量(变量)是在构造函数中创建的,既然它们的状态无法被修改,那么这些常量永远不会被改变——不可变对象永远是线程安全的。
不可变对象需要满足的条件
​ 对象创建以后其状态就不能修改
​ 对象所有域都是final类型
​ 对象是正确创建的(在对象创建期间,this引用没有逸出)
​ final关键字:类、方法、变量
​ 修饰类:不能被继承,final类中的成员属性可以根据需要设置为final,但final类中所有的成员方法都被隐式指定为final方法。一般不建议将类设置为final类型。可以参考String类。
​ 修饰方法:1)锁定方法不被继承类修改;2)效率
​ 修饰变量:1)基本数据类型变量,初始化后便不能进行修改;2)引用类型变量,初始化之后不能再指向别的引用

7.将对象设置为不可变会造成线程封闭问题
线程封闭:把对象封装到一个线程里,只有这一个线程能看到该对象,那么就算这个对象不是线程安全的,也不会出现任何线程安全的问题,因为它只能在一个线程中被访问,如何实现线程封闭:
​ Ad-hoc 线程封闭:程序控制实现,非常脆弱、最糟糕,忽略
​ 堆栈封闭:简单的说就是局部变量,无并发问题。多个线程访问同一个方法的时候,方法中的局部变量都会被拷贝一份到线程栈中,方法的局部变量是不被多个线程共享的,因此不会出现线程安全问题,能用局部变量就不推荐使用全局变量,全局变量容易引起并发问题,注意,全局的变量而不是全局的常量。
​ ThreadLocal 线程封闭【$特别好的封闭方法$】:ThreadLocal提供线程级别的变量.这些变量不同于它们正常下的变量副本,在每一个线程中都有它自己获取方式(通过它的get和set方法),不依赖变量副本的初始化。它的实例通常都是私有的静态的,用于关联线程的上下文。
​ ThreadLocal的作用是提供线程内部的局部变量,这种变量只存在线程的生命周期内。
​ ThreadLocal.ThreadLocalMap threadLocals = = new ThreadLocalMap(this, firstValue);
​ 声明全局的ThreadLocal变量,private static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>;
​ 每个线程中都有属于自己的ThreadLocalMap,互不干扰
​ 全局只有一个threadLocal,当通过set填充数据时,通过获取当前操作线程的threadLocalMap,将threadLocal作为threadLocalMap中的key,需要填充的值作为value
​ 当需要从threadLocal获取值时,通过获取当前操作线程的threadLocalMap,并返回key为threadLocal对象的value
​ 那么就可以理解为:ThreadLocal的活动范围是具体的某一个线程,并且是该线程独有的。它不是用来解决共享变量的多线程安全问题。但是,有一点需要说明的是,如果ThreadLocal通过set方法放进去的值,这个值是共享对象,那么还是会存在线程安全问题。
​ 如何保证两个同时实例化的ThreadLocal对象有不同的threadLocalHashCode属性:
在ThreadLocal类中,还包含了一个static修饰的AtomicInteger(提供原子操作的Integer类)成员变量(即类变量)和一个static final 修饰的常量(作为两个相邻nextHashCode的差值)。由于nextHashCode是类变量,所以每一次调用ThreadLocal类都可以保证nextHashCode被更新到新的值,并且下一次调用ThreadLocal类这个被更新的值仍然可用,同时AtomicInteger保证了nextHashCode自增的原子性。
​ ThreadLocal的ThreadLocalMap中的key为ThreadLocal对象,由于每个实例化的ThreadLocal对象都是不相同的,所以不会存在key冲突,所以一个线程存在多个ThreadLocal对象作为key是完全没有问题的。也就是说,一个线程中的ThreadLocalMap可以存在多个key。
应用场景
ThreadLocal中存放的变量只在线程的生命周期内起作用,应用场景只要有两个方面:
​ 1>提供一个线程内公共变量(比如本次请求的用户信息、实体参数),减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度
​ 2>为线程提供一个私有的变量副本,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。

ThreadLocalMap.expungeStaleEntry(int staleSlot) {}会对ThreadLocalMap中存储的Entry中强引用的Value进行清除操作,但是其操作依赖于set和get方法,为方便操作,JDK建议使用static修饰ThreadLocal,延长ThreadLocal的生命周期 private static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>;
“在这个过程中遇到的key为null的Entry都会被擦除,那么Entry内的value也就没有强引用链,自然会被回收。仔细研究代码可以发现,set操作也有类似的思想,将key为null的这些Entry都删除,防止内存泄露。 但是光这样还是不够的,上面的设计思路依赖一个前提条件:要调用ThreadLocalMap的genEntry函数或者set函数。这当然是不可能任何情况都成立的,所以很多情况下需要使用者手动调用ThreadLocal的remove函数,手动删除不再需要的ThreadLocal,防止内存泄露。所以JDK建议将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。”
应用:MVC参数传递过程中使用ThreadLocal进行参数的封装,传递完成后在filter或interceptor中进行remove.

9.内存泄露(Memory Leak):是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

Java内存的分配是由程序进行的,而内存的释放是由GC完成。在JAVA达到内存泄露时存在下两个条件,即可认为是JAVA内存泄露,这些对象不被GC管理、回收,占用内存。
​ 1>对象是可达的,即对象引用存在
​ 2>对象无用的,即对象已经不再使用
当达到内存泄露时,扔出的异常:java.lang.OutOfMemoryError:Java heap space

转载于:https://www.cnblogs.com/htkj/p/10119280.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值