JAVA多线程学习笔记-2021-08-15
Runnable是一个函数式接口,函数式接口是一个接口中有且仅有一个抽象方法,有了多个抽象方法会提示错误
其中default 关键字修饰的方法可以有方法体,并且实现了该函数式接口的类可以重写这个方法也可以不重写。这样就避免了在一个接口中增加一个方法的时候需要给所有实现的类里都修改一下,虽然在接口中增加新方法违反了 开闭原则 但确实挺方便的。
没有可以强制终止线程的方法,可以用interrupt()方法请求终止线程。调用interruprt()方法之后,该线程的中断状态将被置位。
先用Thread.currentThread()获取当前线程,然后调用isInterruped()方法判断是否被请求终止,但是如果此时该线程处在阻塞状态,就会被抛出
InterruptedException异常
sleep方法让当前线程进入阻塞状态,放弃CPU资源,但是不会放弃锁。
wait方法会让当前线程进入等待状态,放弃CPU资源和放弃锁,
yield方法让出CPU资源给其他进程使用,但是不进入阻塞状态,而是就绪状态
join方法,在当前线程方法中调用其他线程的join方法,让当前线程方法进入等待状态,等待其他线程执行完毕,再由阻塞状态变为就绪状态。
执行synchronized同步代码块的时候,等待获取锁,只能进入阻塞状态
执行IO操作,等待需要的资源而进入阻塞状态。
Thread.currentThread().interrupt()方法会请求终止当前线程,此时调用Thread.currentThread().isInterruped()返回结果是true,
Thread.interrupted()方法则会清楚终止状态,此时调用Thread.currentThread().isInterruped()返回结果是 false;
如果在线程被请求终止了,再调用sleep方法,这个线程不会进入睡眠状态,而是抛出InterruptedException异常,也会清除这个状态。
调用wait方法的时候,如果当前线程不含有当前对象的资源的锁的时候,就会报 IllegalMonitorStateException异常
线程终止的原因:
* 正常退出run方法而自然死亡
* 因为一个没有捕获的异常而导致run方法意外死亡
ReentrantLock是可重入锁,线程可以重复的获得已经持有的锁,锁保持一个持有计数来跟踪线程对lock方法的嵌套调用,lock和unlock需要成对出现,被一个锁保护的代码块可以调用另一个使用相同锁的方法。
在多线程下,如果进入到if( x > 5){…wait(),…}代码块中,执行wait方法,等到这个线程被唤醒的话,会继续执行wait下的代码块,但是在被唤醒的这段时间里可能x的值已经变了,不应该继续执行。用while条件的话,如果执行wait之后被唤醒,还是会先判断条件是否成立。
条件变量: 可能一个线程获得了锁,但是需要满足条件之后才可以继续执行,尝试取钱的线程需要先判断当前余额是否大于0,发现余额小于0,就需要等待存钱的线程去工作。 java.util.concurrent.locks.Condition 可以用作条件对象,调用condition.await(),当前线程就会进入阻塞状态并且放弃持有的锁,等待其他线程去唤醒她。
锁和条件对象的关键之处:
* 锁用来保护代码块,任何时刻只能有一个线程执行该代码块
* 锁可以管理尝试进入被保护的代码块的线程(wait,signal,signAll)
* 锁可以用一个或多个条件对象
* 条件对象管理那些已经持有锁但是没有满足条件而进入阻塞状态的线程(signal,signAll)
synchroized的三个特点:
第一条:当一个线程访问某对象的synchronized方法或者synchronized代码块时,其他线程对该对象的该synchronized方法或者synchronized代码块的访问将被阻塞。
第二条:当一个线程访问某对象的synchronized方法或者synchronized代码块时,其他线程仍然可以访问该对象的非同步代码块。
第三条:当一个线程访问某对象的synchronized方法或者synchronized代码块时,其他线程对该对象的其他的synchronized方法或者synchronized代码块的访问将被阻塞。
volatile关键字为实例域的同步访问提供了一个免锁机制。volatile关键字不能保证原子操作。
线程在条用lock()方法来尝试获取锁的时候,如果锁被其他线程持有,当前线程就会进入阻塞状态,可以使用tryLock()方法申请锁。
Lock lock = new ReentrantLock();
if(lock.tryLock()){
// 获得了锁
}else{
// 没有获得锁
}
tryLock()方法会返回true或者false,也可以加入超时参数。
如果很多线程尝试读取数据而很少的去修改数据,可以考虑使用ReentrantReadWriteLock类,
ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
// 获取读锁
Lock readLock = rw.readLock();
// 获取写锁
Lock writeLock = rw.writeLock();
这样在获取数据的方法上加上读锁,在修改数据的方法上加上写锁。
线程不安全的集合可以通过集合工具类,同步包装器变成线程安全的
List<String> list1 = Collections.synchronizedList(new ArrayList<>());
Map<String,String> map1 = Collections.synchronizedMap(new HashMap<>());
Set<Integer> set1 = Collections.synchronizedSet(new HashSet<>());