关键问题
在并发编程中,需要处理两个关键问题,线程之间如何通信及线程之间如何同步
通信机制:共享内存和消息传递
线程之间的通信,唤醒/等待机制(三种)
synchronized /wait/notify/notifyAll
public class WaitNotifyTest {
public static void main(String[] args) {
WaitNotify wn = new WaitNotify(1, 5);
new Thread(() -> {
wn.print("a", 1, 2);
}).start();
new Thread(() -> {
wn.print("b", 2, 3);
}).start();
new Thread(() -> {
wn.print("c", 3, 1);
}).start();
}
}
/**
* 循环打印abc
* 等待标记 下一个标记
* a 1 2
* b 2 3
* c 3 1
*/
class WaitNotify {
private int flag;
private int loopNum;
public WaitNotify(int flag, int loopNum) {
this.flag = flag;
this.loopNum = loopNum;
}
public void print(String str, int waitFlag, int nextFlag) {
//三个线程都会同时进入这里
for (int i = 0; i < loopNum; i++) {
synchronized (this) {
while (flag != waitFlag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(str);
this.flag = nextFlag;
//唤醒其他线程
this.notifyAll();
}
}
}
}
ReentrantLock /await/signal
public class AwaitSignalDemo {
public static void main(String[] args) throws InterruptedException {
AwaitSignal aw = new AwaitSignal(5);
Condition a = aw.newCondition();
Condition b = aw.newCondition();
Condition c = aw.newCondition();
//这里一开始启动,全部先到休息室等待
new Thread(() -> {
aw.print("a", a, b);
}).start();
new Thread(() -> {
aw.print("b", b, c);
}).start();
new Thread(() -> {
aw.print("c", c, a);
}).start();
//主线程作为一个发起者
Thread.sleep(1000);
aw.lock();
try {
System.out.println("开始");
//唤醒a线程
a.signal();
} finally {
aw.unlock();
}
}
}
class AwaitSignal extends ReentrantLock {
private int loopNum;
public AwaitSignal(int loopNum) {
this.loopNum = loopNum;
}
public void print(String str, Condition curr, Condition next) {
for (int i = 0; i < loopNum; i++) {
lock();
try {
//每个线程进行各自的休息室等待
curr.await();
//如果被唤醒,就执行打印
System.out.println(str);
//唤醒下一个线程
next.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
unlock();
}
}
}
}
LockSupport/park/unpark
public class ParkUnparkDemo {
static Thread t1;
static Thread t2;
static Thread t3;
public static void main(String[] args) {
ParkUnpark parkUnpark = new ParkUnpark(5);
t1 = new Thread(() -> {
parkUnpark.print("a", t2);
});
t2 = new Thread(() -> {
parkUnpark.print("b", t3);
});
t3 = new Thread(() -> {
parkUnpark.print("c", t1);
});
t1.start();
t2.start();
t3.start();
//条件准备好后,唤醒第一个线程
LockSupport.unpark(t1);
}
}
class ParkUnpark {
private int loopNum;
public ParkUnpark(int loopNum) {
this.loopNum = loopNum;
}
public void print(String str, Thread next) {
for (int i = 0; i < loopNum; i++) {
LockSupport.park();
System.out.println(str);
LockSupport.unpark(next);
}
}
}
wait和Sleep的区别
wait() 是Object中定义的native方法,wait()只能在synchronized block中调用,而wait的唤醒会比较复杂,我们需要调用notify() 和 notifyAll()方法来唤醒等待在特定wait object上的线程。
sleep()是定义Thread中的native静态类方法,自带sleep时间,时间过后,Thread会自动被唤醒
Thread类的常用方法
创建线程的方式
public class ThreadDemo {
public static void main(String[] args) throws Exception {
//直接无参的构造方法
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
//t1 线程睡眠
TimeUnit.SECONDS.sleep(1);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + i);
}
}, "t");
t1.start();
//main线程睡眠
Thread.sleep(100);
//使用Thread的构造方法
Runnable run = () -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
};
Thread t2 = new Thread(run,"t2");
t2.start();
/**
* Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。
* 必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
*/
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.sleep(100);
return 100;
}
});
new Thread(futureTask,"t3").start();
System.out.println(futureTask.get());
使用线程池进行创建
一类是通过Executors工厂类提供的方法,该类提供了4种不同的线程池可供使用。
另一类是通过ThreadPoolExecutor类进行自定义创建。
使用线程池可以大大减少开销,不用说每一个线程都要进行创建销毁,可以实现线程复用
newCachedThreadPool: 创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
newScheduledThreadPool: 创建一个周期性的线程池,支持定时及周期性执行任务。
newFixedThreadPool: 创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
newSingleThreadExecutor : 创建一个单线程的线程池,可保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
ps: ** 线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor**的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。(阿里开发手册)
FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
推荐使用下面的方式,自定义线程池
public class TreadPoolConfig {
private static class MyBlockingQueue<T> extends LinkedBlockingQueue<T> {
public MyBlockingQueue(int size) {
super(size);
}
@Override
public boolean offer(T t) {
try {
put(t);
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return false;
}
}
/**
*
* @return 线程池
*/
@Bean(value = "dealDataThreadPool")
public ExecutorService dealDataThreadPool() {
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNamePrefix("deal-data-thread-%d").build();
return new ThreadPoolExecutor(10, 20, 30,
TimeUnit.MILLISECONDS, new MyBlockingQueue<>(10), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
}
}
涉及七大参数,四大淘汰策略
corePoolSize: 线程池核心线程数最大值
maximumPoolSize: 线程池最大线程数大小
keepAliveTime:线程池中非核心线程空闲的存活时间大小 unit: 线程空闲存活时间单位 workQueue: 存放任务的阻塞队列
threadFactory: 用于设置创建线程的工厂,可以给创建的线程设置有意义的名字,可方便排查问题。 handler: 线程池淘汰策略,主要有四种类型。
拒绝策略
AbortPolicy(抛出一个异常,默认的)
CallerRunsPolicy(交给线程池调用所在的线程进行处理)
DiscardPolicy(直接丢弃任务)
DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)
ThreadLocal的使用
ThreadLocal即本地线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构,可以说一个线程分配给一个ThreadLocal变量,线程之间完全不会影响,也没有线程安全问题,主要解决的就是让每一个线程绑定自己的值,自己用自己的,不跟别人争抢。通过使用get()和set()方法,获取默认值或将其值更改为当前线程所存的副本的值从而避免了线程安全的问题)
白话:多个学生要进行签名,但现在只有一支笔,所以只能使用加锁的方式,学生拿到笔就加锁,其他学生用不了,ThreadLocal是每个学生都分配一支笔,这样就能同时进行签名,互不影响
底层说明
每个线程都有自己的ThreadLocalMap ->>> ThreadLocalMap里就保存着所有的ThreadLocal变量
ThreadLocalMap是一个比较特殊的Map,它的每个Entry的key都是一个弱引用:
这样设计的好处是,如果这个变量不再被其他对象使用时,可以自动回收这个ThreadLocal对象,避免可能的内存泄露(注意,Entry中的value,依然是强引用,所以用完一定要执行remove()方法)
sleep 与 yield
调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
yield使cpu调用其它线程,但是cpu可能会再分配时间片给该线程;而sleep需要等过了休眠时间之后才有可能被分配cpu时间片
join
在主线程中调用t1.join,则主线程会等待t1线程执行完之后再继续执行
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
},"t1");
t1.start();
//如果没有调用join,那么主线程先执行,r的值为0
t1.join();
log.debug("结果为:{}", r);
log.debug("结束");
}
java中的锁
悲观锁:单独每个人完成事情的时候,执行上锁解锁。解决并发中的问题,不支持并发操作,只能一个一个操作,效率低
乐观锁:每执行一件事情,都会比较数据版本号,谁先提交,谁先提交版本号
读锁:共享锁(可以有多个人读),会发生死锁
写锁:独占锁(只能有一个人写),会发生死锁
可重入锁:ReentrantLock
多线程中篇暂时写到这,有错误欢迎指正,觉得不错记得点个赞噢!!!