进程:程序运行资源分配的最小单位
线程: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.检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。
注:处于死锁状态的线程无法被中断
线程的状态:
![](https://i-blog.csdnimg.cn/blog_migrate/e045976b0162c1ad60286d337ce1ca4d.png)
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的实现解析
![](https://i-blog.csdnimg.cn/blog_migrate/ccfb3de117baa1ac6c865043aaea4fc5.png)
![](https://i-blog.csdnimg.cn/blog_migrate/bff6ca38c9cfbbef415c208ab32747b7.png)
![](https://i-blog.csdnimg.cn/blog_migrate/e6fc1588ddcc84ff1737a0e4582aedf9.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c6d9330985653b6e945ee3fa3090497a.png)
上面先取到当前线程,然后调用
getMap
方法获取对应的
ThreadLocalMap
, ThreadLocalMap 是
ThreadLocal
的静态内部
类,然后 Thread 类中有一个这样类型 成员,所以 getMap
是直接返回
Thread
的成员。
看下
ThreadLocal
的内部类
ThreadLocalMap
源码:
可以看到有个 Entry 内部静态类,它继承了 WeakReference,总之它记录了 两个信息,一个是 ThreadLocal类型,一个是
Object 类型的值。getEntry 方法则是获取某个 ThreadLocal 对应的值,set 方法就是更新或赋值相应的 ThreadLocal 对应的值。
![](https://i-blog.csdnimg.cn/blog_migrate/1f2ecf334b12b4ca33a1862254e78412.png)
![](https://i-blog.csdnimg.cn/blog_migrate/78b9a3593a0bc95164dc4e791f9425a8.png)
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)
方法将连接放回线程池