多线程编程技巧
- 线程 操作 资源类
- 操作里面分为三步:判断 干活 通知
- 当多线程交互(多线程通信)的情况下,为了防止虚假唤醒的情况,使用
wait()
方法时,一定要使用while
进行判断,而不是if
。 ```java As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:
synchronized (obj) {
while (
4. `JUC`中使用`Lock`和`Condition`,替代了`synchronized`,`wait()`,`notify()`等方法,`Lock`和`Condition`联合使用可以实现精准唤醒
```java
public synchronized void increment(){
//防止虚假唤醒
while( num == 1){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("操作者:"+Thread.currentThread().getName() + "\t当前num = " + ++num);
this.notifyAll();
}
public synchronized void decrement(){
while( num == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("操作者:"+Thread.currentThread().getName() + "\t当前num = " + --num);
this.notifyAll();
}
public void increment(){
//加锁
lock.lock(); // block until condition holds
try {
// ... method body
//判断
//防止虚假唤醒
while (num == 1){
condition.await();
}
//干活
System.out.println("操作者:"+Thread.currentThread().getName()+" 当前num:" + num++ + " 执行后num:" + num);
//唤醒
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
//干活
//唤醒
}
public void decrement(){
//加锁
lock.lock(); // block until condition holds
try {
// ... method body
//判断
while(num == 0){
condition.await();
}
//干活
System.out.println("操作者:"+Thread.currentThread().getName()+" 当前num:" + num-- + " 执行后num:" + num);
//唤醒
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); //释放锁
}
}
- 8锁问题的实质是锁对象问题
- synchronized修饰静态方法或
class
文件,是类锁,当前资源类的所有对象共用一把锁 - synchronized修饰普通方法和常量,是对象锁
- synchronized修饰静态方法或
ArrayList/Set/Map
是线程不安全的List<String> list = new ArrayList<>();
for (int i = 0; i < 3; i++) {
new Thread(() -> {
//method operation...
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
- 故障现象
出现异常:java.util.ConcurrentModificationException
- 故障原因
当多个线程同时对ArrayList进行读写操作时,可能结果错误
- 解决办法
List
集合可以将ArrayList
替换成Vector
,不推荐- 使用
Collections._synchronizedList_(List)
线程安全包装类 - 使用
CopyOnWriteArrayList/CopyOnWriteArraySet
,写时复制技术一种读写分离的思想 - 如果是
Map
集合,使用ConcurrentHashMap
写时复制技术
当多个线程对一个资源进行访问(读操作)时,如果某个线程需要对当期资源进行修改(写操作),系统会将当前资源拷贝一个副本,线程(写线程)会对副本进行修改,其他线程(读线程)对当前资源的访问不变,线程(写线程)完成修改之后,将修改后的资源合并到当前资源,是一种读写分离的思想。
Callable接口
public class MyCallable implements Callable<T>{
@Override
public String call() throws Exception {
return null;
}
}
Callable
与Runnable
的区别
Callable
接口核心方法是call()
方法,Runnable
接口的核心方法是run()
方法Callable
接口可以有返回值,Runnable
接口没有返回值Callable
接口可以抛出异常,Runnable
接口不能抛出异常
Callable
接口需要和FutureTask
结合使用,因为Thread
类并没有接收Callable接口的构造方法,jdk底层使用适配器模式,通过FutureTask
接收Callable参数。
Tips:同一个FutureTask
只能执行一次,在FutureTask底层有一个记录Task执行次数的属性state,该属性可能包含六个状态。
/**
* Possible state transitions:
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
*/
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
因为FutureTask实现了Runnable接口,所以当Task调用run()方法之后,会调用set()方法,使用CAS方法更新state状态值,当下一次再想调用run()方法时,会检查state状态值是否为NEW。
CountDownLatch
&CycleBarrier
相同点
- 功能相似:
- 都可以实现经过若干个线程线程之后,执行特定的功能;
不同点
- 构造方法不同:
CountDownLatch
只有一个有参构造方法,需要传入一个count
,当count = 0
时,阻塞的线程可以执行CycleBarrier
有两个构造方法,一个构造方法需要传入一个count
参数,count
个线程等待之后程序继续执行;第二个构造方法有两个参数,一个是count
参数,另一个是Runnable
接口,count
个线程进入等待之后由最后一个进入barrier
的线程执行参数中提供的目标任务。
count
线程不同:CountDownLatch
的执行线程在执行之后正常结束,进入Terminal
状态CycleBarrier
的执行线程在执行结束后被挂起,等待最后的线程执行目标任务 ```java CountDownLatch count = new CountDownLatch(6);
for (int i = 0; i < 6; i++) { new Thread(() -> { //method operation… System.out.println(Thread.currentThread().getName() + “ 离开”); count.countDown(); //减少计数器,当count计数器数字为0时,唤醒等待线程 },”学生:”+String.valueOf(i+1)).start(); }
count.await(); //除非计数器为0,否则等待 System.out.println(Thread.currentThread().getName() + “ 关门”);
```java
CyclicBarrier barrier = new CyclicBarrier(7);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
//method operation...
System.out.println("学生:"+Thread.currentThread().getName()+"离开自习室");
barrier.await();
},String.valueOf(i+1)).start();
}
barrier.await();//通过调整await()执行顺序和CountDownLatch执行效果相同
System.out.println(Thread.currentThread().getName() + " 给自习室关门");
CyclicBarrier barrier = new CyclicBarrier(7,()->{
System.out.println("学生:"+Thread.currentThread().getName()+"给自习室关门");
//最后进入barrier的线程执行
});
for (int i = 0; i < 7; i++) {
new Thread(() -> {
//method operation...
System.out.println("学生:"+Thread.currentThread().getName()+"离开自习室");
barrier.await();
},String.valueOf(i+1)).start();
}
多线程相关问题
sleep()
与wait()
区别
sleep()
不会释放锁资源wait()
会释放锁资源
- Java线程状态
NEW, //新建状态,还没启动RUNNABLE_, //可运行(就绪状态)等待cpu时间片,BLOCKED, //阻塞状态,等待获得锁__WAITING, //等待状态,执行wait(),join(),park()方法进入,等待其他线程通知(notify())之后可以继续执
行(线程通信),不见不散型TIMEDWAITING, //即时等待,线程等待一个固定的时间,使用wait(long),join(long)进入TERMINATED_; //线程执行结束
Lambda表达式
- 接口是函数式接口,即只含有一个抽象方法,可以使用Lambda表达式,
- 口诀:抄写大括号,写死右箭头,落地大括号
- default 关键字,jdk 1.8之后接口允许有默认实现,使用default关键字进行标注的方法可以实现方法
- 静态实现方法
- @FunctionalInterface 使用注解标注的接口,即为函数式接口,注解会检查接口内的方法时候符合函数式接口的定义规范
- 接口是函数式接口,即只含有一个抽象方法,可以使用Lambda表达式,
HashMap
相关知识点HashMap
底层是Node
节点数组+单向链表+红黑树的数据结构HashMap
默认初始值是16,负载因子0.75,即当HashMap
容量达到12时,会发生扩容HashMap
扩容会扩充到原来的一倍,ArrayList
会扩充为原来的一半,HashMap
每次扩容都是2的幂数- 当发生哈希冲突时,后添加的Node节点会通过尾插法添加到原链表的尾部
- 当哈希冲突的
Node
节点数过多(超过8时),链表就会转化成红黑树