JUC笔记续
读写锁
ReadWriteLock
读可以被多个线程同时读,写只能有一个线程写
独占锁(写锁)
共享锁(读锁)
public class ReadWriteLockDemo{
public static void main(String[] args){
MyCache myCache = new MyCache();
//写入
for(int i=1; i<=5; i++){
final int temp = i;
new Thread(()->{
myCache.put(temp+"", temp+"");
},String.valueOf(i)).start();
}
//读取
for(int i=1; i<=5; i++){
final int temp = i;
new Thread(()->{
myCache.get(temp+"");
},String.valueOf(i)).start();
}
}
}
/*
自定义缓存
*/
class MyCache{
private volatile Map<String, Object> map - new HashMap<>();
//读写锁,更加细粒度的控制
private ReadWriteLock RWlock = new ReentrantReadWriteLock();
//写入的时候,只希望同时只有一个线程在写
public void put(String key, Object value){
RWlock.writeLock().lock();//写锁
//try
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"写入OK");
//finally
RWlock.writeLock().unlock();//解锁
}
//读取,所有人都可以读
public void get(){
RWlock.readLock().lock();//读锁
//try
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取ok");
//finally
RWlock.readLock().unlock();//解锁
}
}
阻塞队列
BlockingQueue
写入:如果队列满了,就必须阻塞等待
取:如果队列为空,必须阻塞等待生产
什么情况下使用阻塞队列:多线程并发处理,线程池
public class Test{
public static void main(String[] args){
}
}
学会使用队列
添加、移除、判断队列首部
四组API:
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
-
抛出异常
容量为3,如果超出容量
IllegalStateException:Queue full抛出异常如果队列为空,还删除
java.util.NoSuchElementException抛出异常检查队首元素
System.out.println(blockingQueue.element()); -
不会抛出异常
添加元素
System.out.println(blockingQueue.offer("a"));返回true或者false删除元素
System.out.println(blockingQueue.poll());返回元素值或null检查队首元素
System.out.println(blockingQueue.peek()); -
阻塞 等待
添加元素
blockingQueue.put("a");没有返回值如果添加多了元素,队列没有位置了,一直等待
删除元素
System.out.println(blockingQueue.take());打印出相应元素如果队列为空,还在删除元素,一直等待
-
超时等待(超了时间就不等了)
添加元素
blockingQueue.offer("a",2,TimeUnite.SECONDS);等待两秒,还没有位置就结束程序 ,offer(元素,等待时间,等待单位);删除元素
blockingQueue.poll(2, TimeUnit.SECONDS);等待两秒,如果队列还为空,结束程序
| ---------------------------------------------------------------- |
| | 方式 | 抛出异常 | 有返回值 | 阻塞等待 | 超时等待 | |
| | ---- | -------- | -------- | -------- | ----------- | |
| | 添加 | add | offer() | put() | offer(,,) | |
| | 删除 | remove | poll() | take() | poll(,) | |
| | 队首 | element | peek() | - | - | |
| |
SynchronousQueue同步队列
没有容量,进去一个元素必须等待取出来之后,才能再往里面放一个元素。
put,take
和其他BlockingQueue不一样,SynchronousQueue不存储元素,put之后必须take才能继续put
public class SynchronousQueueDemo{
public static void main(String[] args){
BlockingQueue<String> bQueue = new SynchronousQueue<>();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"put 1");
System.out.println(Thread.currentThread().getName()+"put 2");
bQueue.put("2");
System.out.println(Thread.currentThread().getName()+"put 3");
bQueue.put("3");
},"T1").start();
new Thread(()->{
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+bQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+bQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+bQueue.take());
},"T2").start();
}
}
线程池:3方法、7参数、4拒绝策略(重要
池化技术
程序的运行,本质:占用系统的资源,优化资源的使用 =>池化技术
线程池、连接池、内存池、对象池... //创建,销毁十分浪费资源
池化技术:事先准备好一些资源,有人要用就来拿,用完之后还回来。
线程池的好处:
线程复用,可以控制最大并发数,管理线程
- 降低资源的消耗
- 提高响应的速度
- 方便管理
三大方法
public class Demo1{
//Executors 工具类、3大方法
public static void main(String[] args){
// Executors.newSingleThreadExecutor();//单个线程
// Executors.newFixedThreadPool(5);//固定的线程池大小:5个线程
// Executors.newCachedThreadPool();//可伸缩的
//可伸缩的线程池范围是0-21亿,会造成OOM,这就是为什么不让使用Executor创建线程池,要通过ThreadPoolExecutor的方式
ExecutorService threadPoll = Executors.newSingleThreadExecutor();
//try
for(int i=0; i<10; i++){
//使用了线程池之后,使用线程池创建线程
threadPoll.execute(()->{
System.out.println(Thread.currentThread.getName());
});
}
//线程池用完,程序结束,关闭线程池
//finally
threadPool.shutdown();
}
}
七大参数
线程池本质:调用ThreadPoolExecutor()
public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
int maximumPoolSize,//最大核心线程池大小
long keepAliveTime,//超时了没有人调用就会释放
TimeUnit unit,//超时单位
BlockingQueue<Runnable> workQueue,//阻塞队列
ThreadFactory threadFactory,//线程工厂,创建线程的,一般不用动
RejectedExectionHandler handler)//拒绝策略
例子:关于银行
刚开始就开的柜台是核心线程池,候客区满了会多开几个柜台,就是最大线程池,等待的地方就是阻塞队列,当阻塞队列也满了,就是拒绝策略
根据上面的例子,创建线程池
ExecutorService threadPool = new ThreadPoolExecutor(2,
5,
3,//后开的线程超过3秒没人调用就释放了
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),//候客区的容量是3
Executors.defaultThreadFactory(),//默认的线程工厂
new ThreadPoolExecutor.AbortPolicy()//拒绝策略
);
//最大承载是:Deque的容量+max线程池大小
//超过这个数量 就会触发拒绝策略 抛出异常 RejectedExecutionException
四种拒绝策略
new ThreadPoolExecutor.AbortPolicy()满了还有人进来,不处理这个人,抛出异常new ThreadPoolExecutor.CallerRunsPolicy()哪来的去哪里 例如:mainnew ThreadPoolExecutor.DiscardPolicy()队列满了,丢掉任务,不抛出异常new ThreadPoolExecutor.DiscardOldestPolicy()队列满了,尝试去和最早的竞争,也不会抛出异常
最大线程池的大小到底该如何定义?
(用于调优)
-
CPU密集型 几核,就定义为几,可以保证CPU的效率最高
Runtime.getRuntime().avaliableProcessors();获取CPU的核数 -
IO密集型
判断程序中十分耗IO的线程有多少个,大于这个数就可以了(因为IO十分占用资源)
四大函数式接口(重要重要)
函数式接口:只有一个方法的接口
eg:
@FunctionalInterface
public interface Runnable{
public sbatract void run();
}
//简化编程模型,在框架底层大量应用
//foreach(消费者类型的函数接口)
四大函数式接口
Consumer、Function、predicate、Supplier
/*
Function 函数型接口,有一个输入参数,有一个输出
只要是函数式接口,都可以用Lambda
*/
public class Demo1{
publilc static void main(String[] args){
//工具类:输出输入的值
Function function = new Function<String, String>(){
//泛型:传入一个String,返回一个String
public String apply(String o){
return o;
}
};
//Lambda
Function<String,String> function = (str)->{return str;};
//调用
function.apply("FUNCTION");
}
}
/*
Predicate 断定型接口,返回值只能是布尔值
*/
public class Demo2{
public static void main(String[] args){
Predicate<String> predicate = new Predicate<String>(){
//输入一个String,输出是boolean值
public boolean test(String o){
return false;
}
};
Predicate<String> predicate = (str)->{return str.isEmpty();};
}
}
/*
Consumer 消费型接口
*/
public class Demo3{
public static void main(String[] args){
Consumer<String> consumer = new Consumer<String>(){
//只有输入,没有返回值
puclic void accept(String o){
System.out.println(o);
}
};
Consumer<String> consumer = (str)->{System.out.println(o);};
}
}
/*
Supplier 供给型接口
*/
public class Demo4{
public static void main(String[] args){
Supplier supplier = new Supplier<Integer>(){
//没有参数,只有返回值
public Integer get(){
return 1024;
}
};
Supplier supplier = ()->{return 1024;};
}
}
四个函数式接口的作用
代码更简洁
Stream流式计算
大数据:存储+计算
存储:集合、MySQL
计算交给流完成
/*
要求:
筛选用户
1. ID是偶数
2. 年龄大于23
3. 用户名转化为大写字母
4. 用户名字母为倒序
5. 只输出一个用户
*/
public class Test{
public static void main(String[] args){
User u1 = new User(1,"a",21);
User u2 = new User(2,"b",22);
User u3 = new User(3,"c",23);
User u4 = new User(4,"d",24);
User u5 = new User(6,"e",25);
//集合就是存储
List<User> list = Arrays.aslist(u1,u2,u3,u4,u5);
//计算交给Stream
//链式编程
list.stream()
.filter(u->{return u.getId()%2 == 0;})
.filter(u->{return u.getAge()>23;})
.map(u->{return u.getName().toUpperCase();})
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
.limit(1)
.forEach(System.out::println);
}
}
ForkJoin
并行执行任务,在大数据量的情况下提高效率,小数据量没必要
把大任务拆分为小任务
ForkJoin特点:工作窃取
先做完自己的工作的去别人那里拿没做完的工作继续做
ForkJoin的操作
类似递归
.fork(); 拆分任务,把任务压入线程队列
.join()+.join(); 把结果加到一起
forkJoinPool.submit(task); 提交任务
forkJoinPool.execute(task); 执行任务
submit.get(); 获取结果
异步回调
Future设计的初衷:对未来的get方法返回的结果类型,对将来的某个事件的结果进行建模
异步调用:Ajax
实现类:CompletableFuture 异步执行、成功回调、失败回调
没有返回值的
CompletableFuture<Void> comF = CompletableFuture.runAsync();
comF.get(); 获取结果,如果阻塞,就一直等着直到获取结果
有返回值的
CompletableFuture<Integer> comF = CompletableFuture.supplyAsync();
comF.whenComplete((t,u)->{
System.out.println("t"+t+"u"+u);})
.execeptionally((e)->{
System.out.println(e.getMessage());
return 233;
}).get());
执行输出t(正常的返回结果,有错时是null)和u(没错时是null,有错误时是错误信息)两个参数。有错误的话输出获取到的错误信息,然后输出233

浙公网安备 33010602011771号