juc相关

补充:并发安全问题

当使用多线程时,发生并发安全的会在同一个类对象中,且对于成员变量容易发生并发安全。局部变量不会导致并发安全,因为每调用一次方法,都会有一个独立的栈帧存放各自的变量信息。

 

1. 实现生产者消费者问题要使用while循环,不可以使用if否则会导虚假唤醒。

 

2. 用synchronized实现生产者和消费者用的是wait和notify,用lock可以调用newCondition方法用的是await和signal而且可以让线程交叉执行

lock lock = new reantlock()

condition condition =lock.newCondition

condition.await()

condition.signal()

 

synchronized加在普通方法锁的是当前的对象,即方法的调用者,锁的执行顺序是先拿到锁先执行,如果新建两个对象调用锁的方法那么默认先按顺序 如果一个阻塞了则先执行另一个。

synchronized锁的是静态方法时锁的是class对象,一个类只能有一个class对象,如果说既调用了锁的静态方法又调用了普通锁方法,不管哪个在前哪个在后一定会先执行静态的那

个 ,如果一个锁的静态方法被两个对象调用了,那么默认先按顺序 如果一个阻塞了则先执行另一个。

synchronized方法体比较便捷,就是想锁普通就锁普通,想锁类就锁类

 

3. copyonwritearraylist是线程安全的,和vector区别在于它是用lock锁实现的,Vector 添加,查询的方法都添加了同步锁,在写操作时,其他线程是不能读的,效率很低。

但是在操作时读操作是比较多的,但是读不会改变数据,一次只允许一个线程读数据。所以我们需要一个更好的集合来提升并发时读的效率。选择了copyonwritearraylist

适用于读多写少的场景

 

 

4. callable可以返回值也可以抛出异常,call方法代替run方法。

和继承runable接口一样,先实现callable接口并且重写call方法(call方法中可以return一个值),runablefuture接口下有一个实现类叫做FutureTask,runablefuture接口继承了runable接口和future接口(future是用来在主线程中开辟一个新的线程来处理耗时的业务的,这里先不研究)而FutureTask有一个参数类型为callable的构造方法,new thread中的参数为new Runable就等价于new FutureTask,那么futuretask就可以充当runable当作参数传递到new thread中,就可以调用参数类型为callable的了,调用futuretask中的get方法可以获取到返回值,get可能会阻塞。

ThreadMythread这个类中继承了callable接口。

ThreadMythread myThread = new ThreadMythread();

FutureTask task =new FutureTask(myThread);

new Thread(task,"线程一").start();

system.out.println(task.get());


5. CountDownLatch(其实作用和join差不多,都是等待当前线程执行完毕再执行另一个线程,只不过countdownlatch适用于等待某些线程执行完毕,不能循环)

public class CountDownLatchDemo {
public static void main(String[] args) {
extendImplH();
}

private static void extendImplH() {
CountDownLatch countdownlatch = new CountDownLatch(7);//从7开始

for (int i = 0; i < 7; i++) {

new Thread(() -> {
try {
System.out.println("线程" + Thread.currentThread().getName());

} catch (Exception e) {
e.printStackTrace();
} finally {
countdownlatch.countDown();//代表数量减1
}
}, String.valueOf(i)).start();
}


int i = 1;
int b = 2;
int c = i + b > 3 ? i++ : b++;
System.out.println(i + "--" + b);

try {
countdownlatch.await();//意思是当数量减为0的时候下面线程被唤醒继续执行
} catch (InterruptedException e) {
e.printStackTrace();
}

//保证最后三行一定是在最后执行
System.out.println("最后执行");
System.out.println("测试");
System.out.println("lalal1");
}
}

 

6. CyclicBarrier(每执行一个线程都会等待,给定的参数parties 是4,那么只有当等待的线程个数为4的时候冲破栅栏接着执行其它线程(CyclicBarrier构造函数的lambda函数可以当作此处所说的其他线程,注意它不在等待队列中),可以循环,如果4后面还有四个线程执行,可以再冲破一次栅栏,如果不满足4个线程则会阻塞,CyclicBarrier构造函数的lambda函数每冲破一次栅栏都会执行一次,但是不保证执行顺序)

public class CyclicBarrierDemo {
public static void main(String[] args) {

// 创建一个CyclicBarrier,指定为5个参与者,以及当所有线程到达屏障时要执行的任务
CyclicBarrier barrier = new CyclicBarrier(5, () -> {
System.out.println("所有运动员已到达起点,比赛开始!");
});

// 模拟每个运动员线程
for (int i = 1; i <= 5; i++) {
int athleteNumber = i;
new Thread(() -> {
try {
System.out.println("运动员 " + athleteNumber + " 准备就绪...");
// 等待其他运动员
barrier.await();
// 所有运动员到达后执行的动作
System.out.println("运动员 " + athleteNumber + " 开始跑步!");
} catch (InterruptedException | BrokenBarrierException e) {
System.err.println("运动员 " + athleteNumber + " 被中断或屏障被破坏");
}
}).start();
}
}
}
运行结果:且运行结束没有阻塞

已连接到目标 VM, 地址: ''127.0.0.1:50756',传输: '套接字''
运动员 2 准备就绪...
运动员 4 准备就绪...
运动员 3 准备就绪...
运动员 1 准备就绪...
运动员 5 准备就绪...
所有运动员已到达起点,比赛开始!
运动员 5 开始跑步!
运动员 1 开始跑步!
运动员 4 开始跑步!
运动员 3 开始跑步!
运动员 2 开始跑步!
与目标 VM 断开连接, 地址为: ''127.0.0.1:50756',传输: '套接字''

进程已结束,退出代码为 0

 7. Semaphore(可以用来限流,每次只可以执行指定数量的线程),它通过维护若干个许可证来控制线程对共享资源的访问。 如果许可证剩余数量大于零时,线程则允许访问该共享资源;如果许可证剩余数量为零时,则拒绝线程访问该共享资源。 Semaphore所维护的许可证数量就是允许访问共享资源的最大线程数量。 所以,线程想要访问共享资源必须从Semaphore中获取到许可证。

      内部有acquire方法和release方法。 当调用acquire方法时线程就会被阻塞(new Semaphore时指定了他的许可证的初始值,每调用一次acquire,如果acquire值大0的话许可证就会减1,需要调用release方法释放许可证后,许可证就会加1,才会继续执行其他线程),直到Semaphore中可以获得到许可证为止,然后线程再获取这个许可证。 当调用release方法时将向Semaphore中添加一个许可证,如果有线程因为获取许可证被阻塞时,它将获取到许可证并被释放;如果没有获取许可证的线程,Semaphore只是记录许可证的可用数量。

      Semaphore内部主要通过AQS(AbstractQueuedSynchronizer)实现线程的管理。Semaphore在构造时,需要传入许可证的数量,它最后传递给了AQS的state值。线程在调用acquire方法获取许可证时,如果Semaphore中许可证的数量大于0,许可证的数量就减1,线程继续运行,当线程运行结束调用release方法时释放许可证时,许可证的数量就加1。如果获取许可证时,Semaphore中许可证的数量为0,则获取失败,线程进入AQS的等待队列中,等待被其它释放许可证的线程唤醒。

      默认是非公平锁,所以不会按照顺序交替实现,如果需要用到公平锁,就和reentrylock一样调用对应的构造方法传入true。

     下面这个代码模拟的是当前有4个车位,只有当有人离开了才可以进来新车。

public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(4);
for (int i = 0; i < 9; i++) {
new Thread(() -> {
try { 
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "进来车位");
TimeUnit.SECONDS.sleep(2);//模仿停车时间
System.out.println(Thread.currentThread().getName() + "离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();

}
}
}


8.  ReadWriteLock(读写锁)

ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
readWriteLock.readLock().lock();//加读锁
readWriteLock.readLock().unlock();//释放读锁
readWriteLock.writeLock().lock();//加写锁
readWriteLock.writeLock().unlock();//释放写锁


读-读(不阻塞)

读-写(阻塞)

写-写(阻塞)

 

9. 阻塞队列(BlockingQueue)适用于生产者消费者

一个线程将会持续生产新对象并将其插入到队列之中,直到队列达到它所能容纳的临界点。
也就是说,它是有限的。如果该阻塞队列到达了其临界点,负责生产的线程将会在往里边插
入新对象时发生阻塞。它会一直处于阻塞之中,直到负责消费的线程从队列中拿走一个对象。
负责消费的线程将会一直从该阻塞队列中拿出对象。如果消费线程尝试去从一个空的队列中
提取对象的话,这个消费线程将会处于阻塞之中,直到一个生产线程把一个对象丢进队列。不能插入null会空指针异常

Queue队列是collection下的一个接口,BlockingQueue继承了Queue

ArrayBlockingQueue<String> objects = new ArrayBlockingQueue<>(3);//3是队列中线程的容量

四组api:

方式 抛出异常 有返回值,不抛出异常 阻塞,等待(一直等待) 超时等待(等待超过指定时间没有执行退出)
添加 add offer() put() offer(参数,参数,参数)
移除/取元素 remove poll() take() poll(参数,参数)
检测队首元素 element peek() - -

 

LinkedBlockingQueue<Object> linkedBlockingQueue= new LinkedBlockingQueue<>();//可以调用无参默认为Integer.MAX_VALUE约为21亿,也可以调用有参传指定队列的大小

 

10. 同步队列SynchronousQueue

BlockingQueue<String> synchronoousQueue =new SynchronousQueue<>();//每次只能放一个数据,直到取出才能放下一数据
synchronoousQueue.put();//放数据
synchronoousQueue.take();//取数据

11. 线程池

线程池的好处:1.降低资源的消耗 2.提高响应速度 3.方便管理

阿里巴巴开发手册:线程池不允许使用Excutors创建,而是通过ThreadPoolExcutor的方式。

execute和submit都可以执行线程,但是推荐下submit,它既可以执行不带返回值的线程,也可以执行带返回值的,而execute只能执行不带参数的

原因:

newFixedThreadPool 和 newSingleThreadExcutor : 允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。

newCachedThreadPool 和 newScheduledThreadPool : 允许的最大创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM 。


newFixedThreadPool: 可以给定一个线程池中线程数量,线程数量是固定的

newSingleThreadExcutor: 和fix差不多,只不过这里的线程数量已经确定为1不需要给定参数,线程数量固定为1

newCachedThreadPool: 线程数量是弹性的根据当前线程多少来确定,最大为Integer.MAX()大约为21亿

newScheduledThreadPool : 可以设置定时任务,返回的是ScheduledExcutorService对象,可以指定线程的数量,但是最大线程数量也是21亿

newSingleThreadScheduledExecutor(): 定时任务的单线程池,可以创建定时任务

newWorkStealingPool(): 根据cpu来创建线程池的

 

举例:

newSingleThreadScheduledExecutor():
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
System.out.println("执行任务的时间:"+LocalDateTime.now());
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("执行任务:"+ LocalDateTime.now());
}
},2, TimeUnit.SECONDS);

newWorkStealingPool():
ExecutorService executor = Executors.newWorkStealingPool();
for (int i = 0; i < 100; i++) {
final int t = i;
executorService.submit(() -> {
System.out.println("任务:" + t + "线程名:" + Thread.currentThread().getName());
});
}
while (!executorService.isTerminated()) {
}

 

 自定义线程池ThreadPoolExecutor:

ThreadPoolExecutor七大参数

1.corePoolSize : 核心线程数,默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
2.maxinumPoolSize : 线程池能容纳的最大线程数,当活跃线程数达到该数值后,后续的新任务将会阻塞。
线程池的的最大线程(maxinumPoolSize)怎么设置
* 1.根据cpu密集型:cpu是几核就是几核,不能写死,Runtime.getRuntime().availableProcessors()
* 2.根据io密集型:判断程序中十分耗io的线程,最大值设置为它的两倍

3.keepAliveTime : (maxinumPoolSize - corePoolSize) 的线程存活时长,线程闲置超时时长。如果超过该时长,非核心线程就会被回收。
4.unit : 存活时长的时间单位//一般写作TimeUnit.seconds
5.workQueue : 任务阻塞队列

ArrayBlockingQueue:构造函数一定要传大小(有界)
LinkedBlockingQueue:构造函数不传参队列大小会默认为(Integer.MAX_VALUE ),当大量请求任务时,容易造成 内存耗尽。newFixedThreadPool和newSingleThreadExcutor 没有带参所以大(有界)
SynchronousQueue:同步队列,一个没有存储空间的阻塞队列 ,将任务同步交付给工作线程。newCachedThreadPool是这个,
PriorityBlockingQueue : 优先队列 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。newScheduledThreadPool用的是这个。

    LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
    LinkedTransferQueue: 它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为 一致,但是是无界的阻塞队列。

注意有界队列和无界队列的区别:如果使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略;而如果使用无界队列,因为任务队列永远都可以添加任务,所以设置 maximumPoolSize 没有任何意义。



6.threadFactory : 用于指定为线程池创建新线程的方式。线程工厂一般默认;Executors.defaultThreadFactory()也可以不写

7.handler : 拒绝执行策略处理器,当达到最大线程数时需要执行的饱和策略。也可以不传默认的就是AbortPolicy()


拒绝策略(方法)有4种:new ThreadPoolExecutor.AbortPolicy()

AbortPolicy: 当任务数超出线程池处理数,就会抛出该异常
DiscardPolicy: 当任务数超出线程池处理数,它会将后续提交的任务进行丢弃
DiscardOldestPolicy: 当任务数超出线程池处理数,它会将阻塞队列中第一个位置的元素弹出(和第一个竞争cpu资源),抛弃最早的未处理任务。
CalleRunsPolicy: 当任务数超出线程池处理数,它会将任务抛给调用者线程进行执行
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

举例:

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 5, 3,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
try {
for (int i = 1; i < 12; i++) {
threadPoolExecutor.execute(() -> System.out.println(Thread.currentThread().getName()));
}
}catch(Exception e){
e.printStackTrace();
} finally {
threadPoolExecutor.shutdown();
}
}

 

12. 四大原生接口函数(一个两个泛型一个参数有返回值的函数,一个有一个泛型有一个参数和返回值的函数,一个有一个泛型没有参数有返回值的函数,一个有一个泛型有一个参数没有返回值的参数)

      任意的接口函数都可以改用lambda函数进行改写

1. function函数(传入T类型函数,返回一个R类型的函数)apply方法

 

 

Function<String, Integer> stringIntegerFunction = new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.valueOf(s);
}
};
System.out.println(stringIntegerFunction.apply("20"));
lambda改写:
Function<String, Integer> stringIntegerFunction = (String s) -> {
return Integer.valueOf(s);
};
System.out.println(stringIntegerFunction.apply("20"));

2. predicate(断定型接口)传一个值,返回值为boolean,重写的是test方法

 

 

Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.isEmpty();
}
};
System.out.println(predicate.test(""));

3. Supplier(供给型函数,给定什么泛型就返回什么类型的结果,不带参数,可以在get方法中实现最后是泛型类型的业务)

 

 

Supplier<String> stringSupplier = new Supplier<String>() {
@Override
public String get() {
String arr = "liuliu";
return arr;
}
};
System.out.println(stringSupplier.get());

 

4. Consumer(消费型接口,没有返回值,只有一个泛型类型参数,给定一什么泛型类型就用这个泛型类型做一些业务,没有返回值解释为消耗掉了,重写accept方法)

 

 

ArrayList<String> list = new ArrayList<>();
Consumer<String> stringConsumer = new Consumer<String>() {
@Override
public void accept(String s) {
list.add(s);
}
};
stringConsumer.accept("liuliu");
ListIterator<String> stringListIterator = list.listIterator();
while (stringListIterator.hasNext()) {
String t = stringListIterator.next();
System.out.println(t);
}

__EOF__

本文作者liuliu的小家
本文链接https://www.cnblogs.com/liu-jin/p/17268755.html
关于博主:hello~好久不见,喜欢的话点个赞吧
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   Bepowerful  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示