WWH
JUC是什么
java.util.concurrent在并发编程中使用的工具类
API调用 工程师
进程/线程是什么?
-
进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
-
线程:通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。
一个qq 进程,一边视频一边逛pyq 多线程
Thread.State
6种
public enum State {
NEW,(新建)
RUNNABLE,(准备就绪)
BLOCKED,(阻塞)
WAITING,(不见不散)
TIMED_WAITING,(过时不候)
TERMINATED;(终结)
}
wait/sleep的区别?
在哪睡的在哪醒,醒完继续往下走
功能都是当前线程暂停,有什么区别?
wait放开手去睡,放开手里的锁
sleep握紧手去睡,醒了手里还有锁
什么是并发?什么是并行?
并发:同一时刻多个线程在访问同一个资源 12306
并行:多项工作一起执行,之后再汇总 烧水泡面
2、 Lock接口
复习Synchronized
例子卖票程序
1、线程 操作 资源类
2、高内聚低耦合
Synchronized | Lock |
---|---|
自动上锁 | 手动上解锁 |
效率低 | 性能佳更灵活 |
Lock
锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。
Lock接口的实现:ReentrantLock可重入锁(API)
- 创建线程方式
推荐:Thread(Runnable target, String name)
- 实现runnable方法
newThread(new Runable(){
public void run()
})
3、Java8特性
lambda表达式
感受lambda之美,推荐收藏,需要时查阅
Lambda小例子
查看例子:LambdaDemo
要求
接口只有一个未实行方法
写法
拷贝小括号(),写死右箭头->,落地大括号{…}
Foo foo = (int x,int y) -> {System.out.println("****hello lambda");};
new Thread(() ->{
},"AAA").start();
函数式接口
lambda表达式,必须是函数式接口,必须只有一个方法
如果接口只有一个方法java默认它为函数式接口。
为了正确使用Lambda表达式,需要给接口加个注解:@FunctionalInterface
如有两个方法,立刻报错
Runnable接口为什么可以用lambda表达式?
接口实现方法?
default方法
接口里在java8后容许有接口的实现,可以多个default方法默认实现
default int div(int x,int y) {
return x/y;
}
静态方法实现
静态方法实现:接口新增
public static int sub(int x,int y){
return x-y;
}
注意静态的叫类方法,能用foo去调吗?要改成Foo
4、线程间通信
线程间通信:1、生产者+消费者2、通知等待唤醒机制
在进行判断是必须要用while,如果用if会出现虚假唤醒进程;
synchronized实现
设置初始值为0的变量
实现一个线程对变量增加1,一个线程对变量减1
交替10轮 01010101…
**资源类 操作 线程 **
class ShareDataOne {
/**
* 设置初始值为0的变量
* * 实现一个线程对变量增加1,一个线程对变量减1
* * 交替10轮
* * 资源类 操作 线程
*/
//设置计数器参数
private int number = 0;
//加法线程--先用synchronized
public synchronized void incr() throws InterruptedException {
//1、判断
// 在进行判断是必须要用while,如果用if(只判断一次)会出现虚假唤醒进程;
while (number!=0) this.wait();
//2、干活
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
//3、通知
this.notifyAll();
}
//减法线程
public synchronized void decr() throws InterruptedException {
//1、判断
while (number != 1) this.wait();
//2、干活
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
//3、通知
this.notifyAll();
}
}
public class NotifyWaitDeno {
public static void main(String[] args) {
//拿取资源类
ShareDataOne shareDataOne = new ShareDataOne();
//生产者
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
shareDataOne.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
//消费者
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
shareDataOne.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
}
}
多线程编程模板
资源类 操作 线程
线程间:(while)判断 干活 通知
java8特性lock实现
API调用Condition(钥匙)
5、线程间定制化调用通信
jucdemo/Lock5_15.java
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print5(int total)
{
lock.lock();
try
{
//1 判断
while(number != 1)
{
//A 就要停止
c1.await();
}
//2 干活
for (int i = 1; i <=5; i++)
{
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t total: "+total);
}
//3 通知
number = 2;
c2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
6、NotSafeDemo
集合不安全
集合类是不安全的
Arrayxxx在迭代的时候如果同时对其进行修改就会
抛出java.util.ConcurrentModificationException
异常
看源码没有synchronized
线程不安全
解决方案
-
Vector 太老,效率低
-
Collections
synchronized
效率低 -
CopyOnWriteArrayXXX(); //lock
写时复制-lock
Ctrl + F12 展示一个类的所有方法
- ArrayList
new CopyOnWriteArrayList();
输出字符串时int转String
// i+"" 注意占内存禁止使用
String.valueOf(i)
类比
-
HashSet
set 底层new hashMap(key唯一,new object常量) //源码 -
HashMap
Map<String,String> map = new ConcurrentHashMap<>();//线程安全
对比 new SynchronizedHashMap<>();
ConcurrentHashMap | SynchronizedHashMap |
---|---|
并发 | 同步 |
行锁 | 表锁 |
7、多线程锁
老师和班长抢桌子上的手机
Synchonized作用范围
TimeUnit
- 代码
import java.util.concurrent.TimeUnit;
class Phone
{
public synchronized void sendSMS() throws Exception
{
System.out.println("------sendSMS");
}
public synchronized void sendEmail() throws Exception
{
System.out.println("------sendEmail");
}
public void getHello()
{
System.out.println("------getHello");
}
}
/**
* @Description:**锁的8个问题**
1 标准访问,先打印短信还是邮件
2 停4秒在短信方法内,先打印短信还是邮件
3 新增普通的hello方法,是先打短信还是hello
4 现在有两部手机,先打印短信还是邮件
5 两个静态同步方法,1部手机,先打印短信还是邮件
6 两个静态同步方法,2部手机,先打印短信还是邮件
7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
* ---------------------------------
*/
public class Lock_8
{
public static void main(String[] args) throws Exception
{
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "AA").start();
Thread.sleep(100);
new Thread(() -> {
try {
phone.sendEmail();
//phone.getHello();
//phone2.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "BB").start();
}
}
- 8锁分析
synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
具体表现为以下3种形式。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的Class对象。
对于同步方法块,锁是Synchonized括号里配置的对象
8、Callable接口
这是一个函数式接口,因此可以用作lambda表达式或方法引用的赋值对象 。
获得多线程的方法几种?
如果只回答两个你连被问到juc的机会都没有
与runnable对比
面试题:callable接口与runnable接口的区别?
答:(1)是否有返回值
(2)是否抛异常
(3)落地方法不一样,一个是run,一个是call
创建新类MyThread实现runnable接口
class MyThread implements Runnable{
@Override
public void run() {
}
}
新类MyThread2实现callable接口
class MyThread2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 200;
}
}
FutureTask
java多态,一个类可以实现多个接口!!
同一个FutureTask只计算一次
FutureTask<Integer> ft = new FutureTask<Integer>(new MyThread());
new Thread(ft, "AA").start();
//运行成功后如何获得返回值?
get方法放到最后
9、JUC强大的辅助类讲解
CountDownLatch-减少计数
6个同学(thread)陆续离开教室,班长(main)最后锁门
秦灭六国
- 枚举(六国)
固定不变的字段含义翻译
countDownLatch.countDown();//其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞)
countDownLatch.await();//当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行
CyclicBarrier-循环栅栏
- 集五福
- await();//留住已获取的
Semaphore-信号灯
- 3个停车位,6辆车
acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),
要么一直等下去,直到有线程释放信号量,或超时。
release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
10、ReentrantReadWriteLock
读写锁–教室控屏(一个人写多个人读)
Read–共享锁
Write–独占锁
volatile 不确定的
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
11、BlockingQueue
阻塞队列:等饭
线程1往阻塞队列里添加元素,线程2从阻塞队列里移除元素
当队列是空的,从队列中获取元素的操作将会被阻塞
当队列是满的,从队列中添加元素的操作将会被阻塞
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起
BlockingQueue核心方法
ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列。 无界
SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列。
12、ThreadPool线程池
heap养老区周期长 原理ThreadPoolExecutor
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
第二:提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
编码实现,但是都不用 OOM
线程池的四种实现
Executors.newFixedThreadPool(int):一池N线程
Executors.newSingleThreadExecutor():一池一线程
Executors.newCachedThreadPool():异步扩容,遇强则强
.execute();
.shutdown();
线程池7个重要参数
工作中需要手写线程池
- 1、corePoolSize:线程池中的常驻核心线程数
- 2、maximumPoolSize:线程池中能够容纳同时
执行的最大线程数,此值必须大于等于1 - 3、keepAliveTime:多余的空闲线程的存活时间
当前池中线程数量超过corePoolSize时,当空闲时间
达到keepAliveTime时,多余线程会被销毁直到
只剩下corePoolSize个线程为止 - 4、unit:keepAliveTime的单位
- 5、workQueue:任务队列,被提交但尚未被执行的任务
- 6、threadFactory:表示生成线程池中工作线程的线程工厂,
用于创建线程,一般默认的即可 - 7、handler:拒绝策略,表示当队列满了,并且工作线程大于
等于线程池的最大线程数(maximumPoolSize)时如何来拒绝
请求执行的runnable的策略
线程池底层工作原理
1、在创建了线程池后,开始等待请求。
2、当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
2.1如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
2.2如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
2.3 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务; 新线程执行新来的任务 阻塞队列中的会被拒绝策略
2.4如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和 拒绝策略来执行。
3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4、当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。
线程池用哪个?生产中如设置合理参数
线程池的拒绝策略
等待队列已经排满了,再也塞不下新任务了
同时,线程池中的max线程也达到了,无法继续为新任务服务。
JDK内置的拒绝策略
以下内置拒绝策略均实现了RejectedExecutionHandle
接口
13、J8函数式接口,Stream流
函数式接口
java.util.function
Stream流
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
“集合讲的是数据,流讲的是计算!”
请按照给出数据,找出同时满足
偶数ID且年龄大于24且用户名转为大写且用户名字母倒排序
最后只输出一个用户名字
public static void main(String[] args)
{
User u1 = new User(11,"a",23);
User u2 = new User(12,"b",24);
User u3 = new User(13,"c",22);
User u4 = new User(14,"d",28);
User u5 = new User(16,"e",26);
List<User> list = Arrays.asList(u1,u2,u3,u4,u5);
list.stream().filter(p -> {
return p.getId() % 2 == 0;
}).filter(p -> {//过滤
return p.getAge() > 24;
}).map(f -> {//toUpperCase()变大写
return f.getUserName().toUpperCase();
}).sorted((o1, o2) -> {//排序
return o2.compareTo(o1);
//限制谁输出;增强for遍历输出
}).limit(1).forEach(System.out::println);
//4种函数式接口实现
// R apply(T t);
Function<String,Integer> function = t -> {return t.length();};
System.out.println(function.apply("abc"));
// boolean test(T t);
Predicate<String> predicate = t -> {return t.startsWith("a");};
System.out.println(predicate.test("a"));
//void accept(T t);
Consumer<String> consumer = t -> {System.out.println(t);};
consumer.accept("java2020");
// T get();
Supplier<String> supplier = () -> {return UUID.randomUUID().toString();};
System.out.println(supplier.get());;
}
14、分支合并框架
Fork:把一个复杂任务进行分拆,大事化小
Join:把分拆任务的结果进行合并
相关类
ForkJoinPool分支合并池 类比=> 线程池
ForkJoinTask 类比=> FutureTask
RecursiveTask递归任务:继承后可以实现递归(自己调自己)调用的任务
例如计算0+…+100分十个分支
15、异步回调
- 同步:找人 死等
- 异步 :找人等不到发信息
- 异步回调:人回信息
开发中MQ实现
并发级别
ThreadLocal
面试思路:多线程 - ThreadLocal - Spring事务(隔离级别)
ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,如何防止自己的变量被其它线程篡改。
1.使用场景:Spring事务,ThreadLocal包装SimpleDataFormat
2.由于是弱引用:内存泄漏 使用remove()
ThreadLocal包装SimpleDataFormat
public class DateUtil {
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
private static ThreadLocal threadLocal = new ThreadLocal();
// 第一次调用get将返回null
// 获取线程的变量副本,如果不覆盖initialValue,第一次get返回null,
// 故需要初始化一个SimpleDateFormat,并set到threadLocal中
public static DateFormat getDateFormat() {
DateFormat df = (DateFormat) threadLocal.get();
if (df == null) {
df = new SimpleDateFormat(DATE_FORMAT);
threadLocal.set(df);
}
return df;
}
}
//apache commons-lang包的DateFormatUtils或者FastDateFormat实现,apache保证是线程安全的,并且更高效