Synchronized、ReenTrantLock、线程池
Synchronized原理
使用
对于普通方法: 锁住的是当前实例对象
对于静态方法: 锁住的是当前类的class对象
对于静态代码块: 锁住的是括号里面的配置对象
原理
public class Test {
public static void main(String[] args) {
}
public void test(){
synchronized (Test.class){
}
}
}
执行javap -c class文件
public class com.example.demothreadpool.Test {
public com.example.demothreadpool.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: return
public void test();
Code:
0: ldc #2 // class com/example/demothreadpool/Test
2: dup
3: astore_1
4: monitorenter
5: aload_1
6: monitorexit
7: goto 15
10: astore_2
11: aload_1
12: monitorexit
13: aload_2
14: athrow
15: return
Exception table:
from to target type
5 7 10 any
10 13 10 any
}
通过反编译我们看到了两个字节码:monitorenter、monitorexit,代码块的同步是利用monitorenter和monitorexit这两个字节码指令。它们分别位于同步代码块的开始和结束位置。当jvm执行到monitorenter指令时,当前线程试图获取monitor对象的所有权,如果未加锁或者已经被当前线程所持有,就把锁的计数器+1;当执行monitorexit指令时,锁计数器-1;当锁计数器为0时,该锁就被释放了。如果获取monitor对象失败,该线程则会进入阻塞状态,直到其他线程释放锁。
ReenTrantLock使用
ReenTrantLock(boolean fair)
ReenTrantLock有两个构造方法,一个空参,另一个ReenTrantLock(boolean fair)当参数fair=true时,表示构造一个公平锁。synchronized是非公平锁,公平锁会按时间顺序,先唤醒等待队列中等待的线程。公平锁的一大特点就是不会产生饥饿现象。但是,由于实现公平锁要求系统维护一个有序队列,因此实现公平锁的成本很高,性能也相对低下。因此,默认情况锁是非公平的。
lock()
获得锁,如果锁已经被占用,则等待
unlock()
释放锁
tryLock()(获取不败不会等待)
尝试获得锁,如果成功放回true,失败返回false,该方法不等待,立即返回,获取失败,等待指定时间后也获取失败,则不会等待通过lock.tryLock()方法来实现。如果其他线程占有锁,则当前线程不会等待,立即返回false。tryLock(long ,TimeUnit),tryLock方法既可以接受无参调用,还可以接受参数,第一个参数等待时长,第二个表示计时单位。如果超过等待时长还没有获取到锁,会停止等待并返回false。
lockInterruptibly()
获得锁,但优先响应中断。如果当前线程未被中断,则获取锁
newCondition()
返回与lock实例一起使用的Condition对象
使用ReentrantLock类的newCondition()方法可以获取Condition对象
需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了,而synchronized是随机唤醒线程的
Condition接口:此接口中封装了await(),signal(),signalAll(),方法。代替了原来锁中的wait(),notify(),notifyAll()监视器方法
Synchronized、ReenTrantLock区别
第一
synchronized 它是jvm层面实现的(jdk1.6进行了优化)
ReentrantLock是jdk层面实现的(jdk1.5引入)
Synchronized释放锁,被动释放: 方法执行结束、异常
ReentrantLock 可以手动灵活释放和获得锁
第二
Synchronized可以修饰在方法层面、或者代码块层面
Lock只能写在代码块中
第三
Synchronized只能实现非公平锁
Lock可以实现非公平和公平锁
线程池Executors
相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
Java通过Executors提供四种线程池,分别为
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
案例
/**
* 创建固定长度的线程池
*/
public class FixedThreadPool implements Runnable{
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(3);
for(int i=0;i<10;i++){
service.execute(new FixedThreadPool());
}
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"ok");
}
}
newSingleThreadExecutor
/**
* 创建单一线程池
*/
public class SingleThreadExecutor implements Runnable{
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
for(int i=0;i<10;i++){
service.execute(new SingleThreadExecutor());
}
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行了");
}
}
newCachedThreadPool
/**
* 缓存线程池,可伸展
*/
public class CachedThreadPool implements Runnable{
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
for(int i=0;i<10;i++){
service.execute(new CachedThreadPool());
if(i==5){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行");
}
}
newScheduledThreadPool
public class ScheduledThreadPool implements Runnable{
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
for(int i=0;i<10;i++){
//service.schedule(new ScheduledThreadPool(),2,TimeUnit.SECONDS);延迟两秒执行
//延迟两秒后,每3秒执行一次
service.scheduleAtFixedRate(new ScheduledThreadPool(),2,3,TimeUnit.SECONDS);
}
}
@Override public void run() {
System.out.println(Thread.currentThread().getName()+"执行");
}
}
自定义线程池
Executors线程池弊端
Executors返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致大量资源被占用。
2)CachedThreadPool 和 ScheduledThreadPool :
允许创建的线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致大量资源被占用。
自定义线程池
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
通过上面的两个源码可以看出:java给我们提供的线程池底层就是new ThreadPoolExecutor的实现,因此我们可以自己去实现new ThreadPoolExecutor来自定义我们的线程池
构造一个线程池
参数:
corePoolSize 核心线程池大小
maximumPoolSize 线程池最大容量大小
keepAliveTime 线程池空闲时,线程存活的时间
TimeUnit 时间单位
最后一位 阻塞队列
构建一个核心线程池大小为5,线程池最大容量大小为6, 线程池空闲时,线程存活的时间3秒,阻塞队列长度为3的线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 6, 3,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3)
);
JDK 7 提供了7个阻塞队列,如下
1、ArrayBlockingQueue 数组结构组成的有界阻塞队列。
此队列按照先进先出(FIFO)的原则对元素进行排序,但是默认情况下不保证线程公平的访问队列,即如果队列满了,那么被阻塞在外面的线程对队列访问的顺序是不能保证线程公平(即先阻塞,先插入)的。
2、LinkedBlockingQueue一个由链表结构组成的有界阻塞队列
此队列按照先出先进的原则对元素进行排序
3、PriorityBlockingQueue支持优先级的无界阻塞队列
4、DelayQueue支持延时获取元素的无界阻塞队列,即可以指定多久才能从队列中获取当前元素
5、SynchronousQueue不存储元素的阻塞队列,每一个put必须等待一个take操作,否则不能继续添加元素。并且他支持公平访问队列。
6、LinkedTransferQueue由链表结构组成的无界阻塞TransferQueue队列。
7、LinkedBlockingDeque链表结构的双向阻塞队列,优势在于多线程入队时,减少一半的竞争。