并发编程-基础概念

      进程:程序运行资源分配的最小单位

      线程:CPU调度的最小单位,必须依赖于进程而存在

 

      CPU时间片轮转机制:RR调度

 

并行和并发的区别:

       并行:同时执行

       并发:交替执行

高并发编程的好处:

       1.充分利用CPU的资源,多核,减少cpu的空闲时间

       2.加快响应用户的时间

       3.使代码模块化,简单化,异步化

并发编程的注意事项:

       1.安全性

       2.死锁

       3.线程太多会将服务器资源耗尽,造成死机宕机

启动线程的方式:

      有两种:继承thread类或者实现runable接口

      他们的区别:thread是java语言里面对线程的抽象           runable是对任务,对业务逻辑的抽象

停止线程的方法:stop,destory,suspend,强制性,不建议使用,不利于释放资源

                             interrupt,中断,但不是强制性的。

注:jdk中的线程是协作式的,而不是抢占式的。

 

interrupted:

        可以检测是否存在终止线程的标志位,有true和false两种状态

注:不建议自定义一个取消标志位来中止线程的运行,原因如下:run方法中有阻塞调用时会无法检测到取消标志。

        使用中断会更加好:1.一般的阻塞方法,如sleep本身就支持中断的检查

                                         2.检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。

注:处于死锁状态的线程无法被中断

 

线程的状态:

start方法: 只能被调用一次,使线程进入就绪状态,是真正意义上的启动线程

run方法:   是实现业务逻辑的地方,可以调用多次,本质上和成员方法没有区别

yield方法: 是当前线程让出cpu占有权(时间片到期),让出时间不确定,并且不会释放锁资源

join方法:   让指定的线程加入当前线程,顺序执行

 

priorty:可以设置线程的优先级,1-10     但不一定真正的有用,取决于cpu的调度

 

守护线程:jdk内部启动或者经过参数配置启动的线程,是守护线程,给程序起支持性作用。

                 thread.setdaemon(true),将线程设置成为守护线程。

                 守护线程中,finally不一定执行,取决于cpu会不会给她分配时间片

 

synchronized:可以在方法上加锁,称为方法块

                         可以在代码块加锁,称为同步块

                 对象锁:如果锁的是一个对象,各个线程就会来抢这个锁,先到先执行。

                 类锁:    本质上其实还是对象,只是是class对象

 

volatile关键字:最轻量的同步机制,不能保证原子性,但是可以保证主线程和子线程之间的可见性

 

ThreadLocal:

ThreadLocal为每一个线程提供了变量的副本,使每一个线程在某一时间访问到的并非是同一个对象,以此隔离多个线程对对数据的共享。

其中Spring中的事务就借助了ThreadLocal类。

 
ThreadLocal 类接口只有 4 个方法
 
void set(Object value) 设置当前线程的线程局部变量的值。
 
• public Object get() 该方法返回当前线程所对应的线程局部变量。
 
public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是 JDK 5.0 新增的方法。需要指出的
 
是,当线程结束后,对应该线程的局部变量将自动 被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但
 
可以加快内存回收的速度。
 
protected Object initialValue() 返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为 了让子类覆盖而设计
 
的。这个方法是一个延迟调用方法,在线程第 1 次调用 get() set(Object) 时才执行,并且仅执行 1 次。 ThreadLocal 中的缺省
 
实现直接返回一 null

 

 

ThreadLocal的实现解析

 

        

        

        上面先取到当前线程,然后调用 getMap 方法获取对应的 ThreadLocalMap , ThreadLocalMap 是 ThreadLocal 的静态内部
 
类,然后 Thread 类中有一个这样类型 成员,所以 getMap 是直接返回 Thread 的成员。
 
看下 ThreadLocal 的内部类 ThreadLocalMap 源码:
 
 
 

 

         可以看到有个 Entry 内部静态类,它继承了 WeakReference,总之它记录了 两个信息,一个是 ThreadLocal类型,一个是

Object 类型的值。getEntry 方法则是获取某个 ThreadLocal 对应的值,set 方法就是更新或赋值相应的 ThreadLocal 对应的值。

     
         get 方法,其实就是拿到 每个线程独有的 ThreadLocalMap 然后再用 ThreadLocal 的当前实例,拿到 Map 中的相应的
 
Entry ,然后就可 以拿到相应的值返回出去。当然,如果 Map 为空,还会先进行 map 的创建,初始化等工作。
 
 
 
内存泄漏的问题:
 
引用
 
Object o = new Object(); 这个 o ,我们可以称之为对象引用,而 new Object() 我们可以称之为在内存 中产生了一个对象实例。
 

 

当写下 o=null 时,只是表示 o 不再指向堆中 object 的对象实例,不代表这 个对象实例不存在了。
 
强引用 就是指在程序代码之中普遍存在的,类似“ Object obj=new Object ()” 这类的引用,只要强引用还存在,垃圾收集器永远
 
不会回收掉被引用的对象实例。
 
软引用 是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象, 在系统将要发生内存溢出异常之前,将会把这些对
 
象实例列进回收范围之中进行 第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在 JDK 1.2 之后,提供了
 
SoftReference 类来实现软引用。
 
弱引用 也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱 引用关联的对象实例只能生存到下一次垃圾收集发生
 
之前。当垃圾收集器工作时, 无论当前内存是否足够,都会回收掉只被弱引用关联的对象实例。在 JDK 1.2 之 后,提供了
 
WeakReference 类来实现弱引用。
 
虚引用 也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象 实例是否有虚引用的存在,完全不会对其生存时间构
 
成影响,也无法通过虚引用 来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象 实例被收集器回收时
 
收到一个系统通知。在 JDK 1.2 之后,提供了 PhantomReference 类来实现虚引用。

 

 

根据我们前面对 ThreadLocal 的分析,我们可以知道每个 Thread 维护一个 ThreadLocalMap,这个映射表的 key ThreadLocal
 
实例本身, value 是真正需 要存储的 Object ,也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从
 
ThreadLocalMap 获取 value 。仔细观察 ThreadLocalMap ,这个 map 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象
 
GC 时会被回收。
 
因此使用了 ThreadLocal 后,引用链如图所示
 
 
图中的虚线表示弱引用。
 
这样,当把 threadlocal 变量置为 null 以后,没有任何强引用指向 threadlocal 实例,所以 threadlocal 将会被 gc 回收。这样一
 
来, ThreadLocalMap 中就会出现 key 为 null Entry ,就没有办法访问这些 key null Entry value ,如果当前
 
线程再迟迟不结束的话,这些 key null Entry value 就会一直存在一条强 引用链:Thread Ref -> Thread ->
 
ThreaLocalMap -> Entry -> value ,而这块 value 永 远不会被访问到了,所以存在着内存泄露。 只有当前 thread 结束以后,
 
current thread 就不会存在栈中,强引用断开, Current Thread、 Map value 将全部被 GC 回收。最好的做法是不在需要使用
 
ThreadLocal 变量后,都调用它的 remove() 方法,清除数据。
 
其实考察 ThreadLocal 的实现,我们可以看见,无论是 get() set() 在某些时 候,调用了 expungeStaleEntry 方法用来清除 Entry
 
Key null Value ,但是 这是不及时的,也不是每次都会执行的,所以一些情况下还是会发生内存泄露。 只有 remove()
 
法中显式调用了 expungeStaleEntry 方法。 从表面上看内存泄漏的根源在于使用了弱引用,但是另一个问题也同样值得
 
 
 
那么为什么使用弱引用而不是强引用?
 
 
 
下面我们分两种情况讨论:
 
key 使用强引用:引用 ThreadLocal 的对象被回收了,但是 ThreadLocalMap 还持有 ThreadLocal 的强引用,如果没有手动删
 
除, ThreadLocal 的对象实例不会 被回收,导致 Entry 内存泄漏。
 
 
 
key 使用弱引用:引用的 ThreadLocal 的对象被回收了,由于 ThreadLocalMap 持有 ThreadLocal 的弱引用,即使没有手动删
 
除, ThreadLocal 的对象实例也会被 回收。value 在下一次 ThreadLocalMap 调用 set get remove 都有机会被回收。
 
 
 
比较两种情况,我们可以发现:由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果都没有手动删除对应 key ,都会导致
 
内存泄漏,但是使用弱引用可 以多一层保障。
 
因此, ThreadLocal 内存泄漏的根源是:由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会
 
导致内存泄漏,而不是因为弱引用。
 
 
 
总结
 
JVM 利用设置 ThreadLocalMap Key 为弱引用,来避免内存泄露。
 
JVM 利用调用 remove get set 方法的时候,回收弱引用。
 
ThreadLocal 存储很多 Key null Entry 的时候,而不再去调用 remove 、 get、 set 方法,那么将导致内存泄漏。
 
使用 线程池 + ThreadLocal 时要小心,因为这种情况下,线程是一直在不断的 重复运行的,从而也就造成了 value 可能造成累积
 
的情况。
 
 
 
 
yield    不会释放锁
 
sleep   不会释放锁
 
wait     会释放自己的锁
 
notify   对锁没有影响
 
 
 
 
等待 通知机制:
 
 
是指一个线程 A 调用了对象 O wait() 方法进入等待状态,而另一个线程 B 调用了对象 O notify() 或者 notifyAll() 方法,线程 A
 
收到通知后从对象 O wait() 方法返回,进而执行后续操作。上述两个线程通过对象 O 来完成交互,而对象 上的 wait()
 
notify/notifyAll() 的关系就如同开关信号一样,用来完成等待方和通 知方之间的交互工作。
notify()
 
通知一个在对象上等待的线程 , 使其从 wait 方法返回 , 而返回的前提是该线程 获取到了对象的锁,没有获得锁的线程重新进入
 
WAITING 状态。
 
 
 
notifyAll()
 
通知所有等待在该对象上的线程
 
 
wait()
 
调用该方法的线程进入 WAITING 状态 , 只有等待另外线程的通知或被中断 才会返回. 需要注意 , 调用 wait() 方法后 , 会释放对象的锁
 
 
wait(long)
 
超时等待一段时间 , 这里的参数时间是毫秒 , 也就是等待长达 n 毫秒 , 如果没有通知就超时返回
 
 
wait (long,int)
 
对于超时时间更细粒度的控制 , 可以达到纳秒
 
 
等待和通知的标准范式
 
等待方遵循如下原则。
 
1 )获取对象的锁。
 
2 )如果条件不满足,那么调用对象的 wait() 方法,被通知后仍要检查条件。 3 )条件满足则执行对应的逻辑。
 
 
 
通知方遵循如下原则。
 
1 )获得对象的锁。
 
2 )改变条件。
 
3 )通知所有等待在对象上的线程。
 
 
在调用 wait ()、 notify() 系列方法之前,线程必须要获得该对象的对象级 别锁,即只能在同步方法或同步块中调用 wait ()方
 
法、 notify() 系列方法 ,进 入 wait ()方法后,当前线程释放锁,在从 wait ()返回前,线程与其他线程竞 争重新获得锁,执行
 
notify() 系列方法的线程退出调用了 notifyAll synchronized 代码块的时候后,他们就会去竞争。如果其中一个线程获得了该对象
 
锁,它就会 继续往下执行,在它退出 synchronized 代码块,释放锁后,其他的已经被唤醒的 线程将会继续竞争获取该锁,一直
 
进行下去,直到所有被唤醒的线程都执行完毕。
 
 
 
notify notifyAll 应该用谁
 
尽可能用 notifyall() ,谨慎使用 notify() ,因为 notify() 只会唤醒一个线程,我 们无法确保被唤醒的这个线程一定就是我们需要唤醒
 
的线程
 
 
等待超时模式实现一个连接池
 
调用场景:调用一个方法时等待一段时间(一般来说是给定一个时间段), 如果该方法能够在给定的时间段之内得到结果,那么
 
将结果立刻返回,反之,超 时返回默认结果。
 
假设等待时间段是 T ,那么可以推断出在当前时间 now+T 之后就会超时
 
等待持续时间: REMAINING=T
 
•超时时间: FUTURE=now+T
 
 
 
// 对当前对象加锁
 
public synchronized Object get(long mills) throws InterruptedException {
 
long future = System.currentTimeMillis() + mills;
 
long remaining = mills;
 
// 当超时大于 0 并且 result 返回值不满足要求
 
while ((result == null) && remaining > 0) { wait(remaining);
 
remaining = future - System.currentTimeMillis();
 
}
 
return result;
 
}
 
具体实现参见:包下 cn.enjoyedu.ch1.pool 的代码
 
客户端获取连接的过程被设定为等待超时的模式,也就是在 1000 毫秒内如 果无法获取到可用连接,将会返回给客户端一个
 
null 。设定连接池的大小为 10 个,然后通过调节客户端的线程数来模拟无法获取连接的场景。 它通过构造函数初始化连接的最大
 
上限,通过一个双向队列来维护连接,调 用方需要先调用 fetchConnection(long) 方法来指定在多少毫秒内超时获取连接,
 
当连接使用完成后,需要调用 releaseConnection(Connection) 方法将连接放回线程池
 
 
 
 
 
 
 
 
 
 
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值