进程:程序运行资源分配的最小单位
线程: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)
方法将连接放回线程池