JUC组件实践总结

  2020-3-12 


JUC组件总结

volatile

volatile变量应该作为类变量,不能放在方法里,否则局部变量保存在栈中,就没意义了

线程中断机制

阻塞方法收到中断请求的时候就会抛出InterruptedException异常(所以对于阻塞方法,必须捕获处理中断异常

阻塞方法有wait、sleep、join、以及很多阻塞读写等等、

中断不是暂停,也不是停止,中断是改变了该线程的一个请求中断标志位

中断标志位只是一个标志,如果不处理的话程序会继续执行下去(阻塞会抛出中断异常,非阻塞不会抛出)

①对于阻塞方法,该中断标志位为true则直接抛出InterruptedException(然后可以在catch中捕获异常并处理,甚至可以再使用interrupted函数修改标志位让线程继续执行);

②对于非阻塞方法,该中断标志位为true不会抛出InterruptedException,可以通过在线程程序中根据中断标志位的值来做出很多操作

线程中断相关函数:

isInterrupted:实例方法,检查请求中断标志位是true还是false

interrupted:静态方法,首先检查请求中断标志位是true还是false(并返回该检查值),然后将中断标志位重置为false(未请求中断的状态)。可用于线程被中断后,经过处理(或不打算处理)还要让线程继续运行的情况(注意该函数是先检查然后返回该值,再改变标志位,也就是说返回的不是改变后的中断标志位值)

interrupt:中断线程

可以理解为isInterrupted是检查标志位,interrupted是检查并恢复中断标志位,interrupt就是中断

Synchronizer

锁代码块

在代码块中锁住某对象(在代码块中获得该对象的对象锁)

synchronized(Object)  //锁住圆括号里的对象
{
    //锁住该对象的作用范围
}

如果要锁的目标是本实例,则锁this引用指代的对象

synchronized(this)
{
    //todo
}

如果要锁的目标是该类的所有对象,则锁该类的Class对象

synchronized(Object.class) 
{
    //todo
}

锁方法

锁的作用范围是该方法,线程执行该方法必须获得同步锁

public synchronized void method()
{
   // todo
}

如果锁的是静态方法,则锁定的就是这个类的所有对象的该静态方法

public synchronized static void method()
{
   // todo
}

接口方法不适用,synchronized方法不可被继承,synchronized方法被重写默认不同步

ReentrantLock

ReentrantLock实现了Lock接口:

//可用的Lock接口方法
void lock(); //阻塞获取锁
void lockInterruptibly();  //阻塞获取锁,获取到之前可被中断,中断后会抛出中断异常
boolean tryLock(); //非阻塞获取锁
boolean tryLock(long time, TimeUnit unit);
void unlock(); //释放锁
Condition newCondition();  //返回当前线程的Condition,可用获得多个Condition

ReentrantLock自己添加的方法,主要是管理作用:

ReentrantLock(boolean fair);   //构造器方法,传入true为公平锁,false为非公平锁
int getQueueLength(); //等待锁的线程数量 
boolean hasQueuedThreads() //是否有线程等待锁
boolean hasQueuedThread(Thread thread) //是否有指定线程等待锁
int getHoldCount() //当前线程是否获取到了锁,返回1表示获取到了
boolean isLocked() //是否有线程持有该锁
boolean isFair()  //是不是公平锁

一个ReentrantLock锁可以创建多个Condition,每个Condition都维护一个等待队列

await() / signal()是属于ReentrantLock机制的Condition组件的方法,而不是Object的

ReetrentLock需要处理异常

使用Lock时一定要在finally语句里面释放锁,否则发生异常时可能会导致锁无法被释放,导致程序奔溃

synchronized是获取目标对象关联的monitor对象的锁(获取锁的过程即修改monitor中的markword字段),而reetrentlock本身就是锁对象(不过可以通过Condition来维护多个等待队列)。线程争用的实际上是锁。

对象监视器:对象监视器实际上就是与每个对象关联的Monitor对象,也叫做管程锁。实际上监视器也是一个数据结构,里面维护着一系列等待队列、同步队列等。wait方法、notify方法、notifyAll方法,在使用的时候,必须要有自己的同步监视器(锁对象)。换句话:wait方法、notify方法、notifyAll方法,必须注册在某个同步监视器上(锁上)。

Condition即reetrentlock的监视器,它和monitor,也即Object的对象监视器是一样的作用

Condition:

Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition。

Condition类能实现synchronized和wait、notify搭配的功能,另外比后者更灵活,Condition可以实现多路通知功能,也就是在一个Lock对象里可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择的进行线程通知,在调度线程上更加灵活(这样就可以只通知部分等待线程唤醒,开始抢锁)。而synchronized就相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在这个对象上。线程开始notifyAll时,需要通知所有的WAITING线程,没有选择权,会有相当大的效率问题

1、Condition是个接口,基本的方法就是await()和signal()方法。

2、Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()

3、调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用。

4、Conditon中的await()对应Object的wait(),Condition中的signal()对应Object的notify(),Condition中的signalAll()对应Object的notifyAll()。

参考:https://blog.csdn.net/weixin_43767015/article/details/104933955

reentrantlock和synchronized主要区别

reentrantlock:多次加解锁、可中断、可公平、可非阻塞、多个condition-多同步队列-精确控制

reentrantlock底层:AQS同步器、CAS方式获取同步器状态

实践例子见生产者消费者问题

信号量

多个线程竞争获取许可信号,并发控制交由信号量本身实现,我们只需要调用其提供的API即可

release()释放信号量,信号量计数+1;acquire()获取信号量,信号量计数-1

例子见生产者消费者问题,信号量方法

阻塞队列

阻塞队列就相当于给你提供了一个带有同步功能的list,那就不需要自己辛苦去做线程同步了,直接使用阻塞队列提供的put() get() take()等方法即可

见生产者消费者问题,阻塞队列BlockingQueue方法

Object线程相关方法

wait、notify等都是Object类的方法:

Object.wait()
Object.notify()
Object.notifyAll()

每个Object都有一个等待队列,队列上是等待线程(若无多线程同步访问,则该等待队列是空的)

wait()必须配合notify()使用,wait()等待由notify()唤醒(wait带参数可以设定最大等待时长)

wait()等待会让当前线程立即放弃锁(即使它在同步块中),加入等待队列,直到notify()唤醒才会重新获取锁

notify()会让当前线程放弃锁,并通知Object的等待队列第一个等待线程可以获取锁了

notifyAll()会让当前线程放弃锁,并通知Object的等待队列上的所有等待线程可以获取锁了

(sleep()不放弃锁,且sleep()是Thread类的方法)

调用该对象的wait(),notify()和notifyAll()的线程在调用这些方法前必须”拥有”对象的锁。当前的线程不是此对象锁的所有者,却调用该对象的notify(),notifyAll(),wait()方法时抛出IllegalMonitorStateException异常

ThreadLocal

为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。主要就是不同线程有不同数据副本的情形下使用,如用来解决数据库连接、Session管理等;而如果该数据需要不同线程同步访问一份,那就不能用ThreadLocal

每个线程包含一个自己的ThreadLocal.ThreadLocalMap实例对象(成员变量引用名为threadLocals,private的),其中包含<threadlocal-value>键值对,我们通过ThreadLocal管理ThreadLocalMap。每创建一个ThreadLocal实例就是在当前线程中断ThreadLocalMap中创建了一个键,通过set函数设置其value

如下例子:

ThreadLocal th1 = new ThreadLocal();
th1.set(new Object());

ThreadLocal th2 = new ThreadLocal();
th2.set(1);

System.out.println(th1.get().getClass().toString());
//class java.lang.Object
System.out.println(th2.get());
//1

P.S. ThreadLocal可能引发内存泄漏问题(原因同weakhashmap):ThreadLocalMap的key是弱引用(ThreadLocalMap->Entry),而Value是强引用(Entry->Value)。这就导致ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,这样value就会一直存在,导致内存泄漏。

https://blog.csdn.net/Rex_WUST/article/details/98959422

线程池

ExecutorService pool = Executors.newXXXThreadPool();
pool.submit(Callable<T> callable);
pool.execute(Runnable runnable);

四要素:核心线程,阻塞队列,非核心线程,最大线程数。各种线程池由以上不同的设置实现

P.S.

new Exception.printStackTrace打印日志(最好用log4j)

sleep、wait等函数的单位都是ms

在Runnable、Callable、FutureTask中可以可以使用Thread.currentThread来获取运行时当前任务所在线程的引用

Thread.currentThread可以用在任何地方)


且听风吟