java之并发编程(下)
9、线程的创建
-传统的创建线程:
使用Runnable接口
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//普通方法创建线程
A a=new A();
new Thread(a).start();
}
class A implements Runnable{
@Override
public void run() {
System.out.println("开始行动了");
}
}
-新技术:使用Callable接口
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//新方法:使用FutureTask 包装Runnable或Callable
// FutureTask实现了Runnable
// 1、new FutureTask(Callable) 或 new FutureTask(Runnable,返回给定结果)
B b=new B();
FutureTask futureTask = new FutureTask(b);
new Thread(futureTask).start();//结果会被缓存,提高效率
//futureTask.get()获取返回结果
System.out.println(futureTask.get());//可能会阻塞,最好放在最后面
}
}
class B implements Callable<String>{
@Override
public String call() throws Exception {
return "ok";
}
}
-比较
Runnable:没有返回值,不需要抛出异常
Callable:有返回值,需要抛出异常
10、JUC的辅助类
-CountDownLatch:
减法计数器 ====无法重置计数
辅助理解:就像一个教室,所有同学都走了才能关门
CountDownLatch( int count):指定线程数量
countdown()方法: 执行减一操作,不会阻塞
await()方法: 当线程数量减为0,才释放
jdk文档描述:
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。
CountDownLatch 是一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用 countDown() 的线程打开入口前,所有调用 await 的线程都一直在入口处等待。用 N 初始化的 CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。
CountDownLatch 的一个有用特性是,它不要求调用 countDown 方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个 await。
//减法计数器
public class CountDown {
public static void main(String[] args) throws InterruptedException {
//参数是减的总次数,用在需要执行某个特定任务时使用
CountDownLatch count=new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"出去了");
count.countDown();// -1 每执行完一个线程就减一,该方法不会阻塞
},String.valueOf(i)).start();
}
count.await();//等待计数器归0后,await才会被唤醒,执行后面的操作
/*就像一个教室,所有同学都走了才能关门
* 10个Thread同学都走了,主线程才关门
* */
System.out.println("我关门了");
}
}
-CyclicBarrier:加法计数器
辅助记忆:集齐七颗龙珠召唤神龙
public CyclicBarrier(int parties, Runnable barrierAction)
parties: 线程数
barrierAction: 突破障碍后最终执行的动作(由最后到达的一个线程执行)
await():让线程等待,直到等待的线程数为parties,才释放
一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。
CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。
//加法计数器
public class Cyclicbarrier {
public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
//集齐七颗龙珠召唤神龙
// CyclicBarrier(线程数,突破障碍后最终执行的动作(由最后到达的一个线程执行))
CyclicBarrier barrier=new CyclicBarrier(7,()->{
System.out.println(Thread.currentThread().getName()+"召唤神龙");
});
for (int i = 1; i < 8; i++) {
new Thread(() -> {
System.out.println("集齐了" + Thread.currentThread().getName());
try {
//+1操作直到7,才会释放
barrier.await();//等待,直到7个线程都到达才唤醒
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
-Semaphore信号量
通常用于限流,限制本地内容访问
Semaphore(int permits, boolean fair):permits:许可的线程数量,
fair:1、true 公平的,可以保证先进先出==== 优点:确保所有线程都可以访问 2、false 非公平的,
new Semaphore(int permits);默认是非公平的
semaphore.acquire():获得许可,当数量已满时,其他线程只能等待
semaphore.release():信号量+1,然后唤醒等待的线程
//Semaphore信号量
public class SemaPhore {
public static void main(String[] args) {
/*new Semaphore(int permits, boolean fair)
*permits:许可的线程数量,
* fair:1、true 公平的,可以保证先进先出==== 优点:确保所有线程都可以访问
* 2、false 非公平的,
* new Semaphore(int permits);默认是非公平的
*/
Semaphore semaphore=new Semaphore(5,true);
for (int i =1; i <=20; i++) {
new Thread(()->{
try {
semaphore.acquire();//获得许可,最多停5个车
System.out.println(Thread.currentThread().getName()+"获得车位");
TimeUnit.SECONDS.sleep(4);//停车2s
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName()+"========释放车位");
semaphore.release();//释放许可
}
},String.valueOf(i)).start();
}
}
}
11、读写锁
读-读:可以共存
读-写:不能共存,必须同步
写-写:不能共存,必须同步
独占锁(写锁):必须同步
共享锁(读锁):不需要同步
-不安全的读写
//读写锁
public class ReadWritelock {
public static void main(String[] args) throws InterruptedException {
CountDownLatch count=new CountDownLatch(10);
myCache cache=new myCache();
//写入
for (int i =1; i <=10; i++) {
final int temp=i;
new Thread(()->{
cache.put(temp,temp);
count.countDown();
},String.valueOf(i)).start();
}
count.await();
System.out.println(cache.size());
//读取
for (int i =1; i <=10; i++) {
final int temp=i;
new Thread(()->{
cache.get(temp);
},String.valueOf(i)).start();
}
}
}
class myCache{
private volatile Map <Integer,Object>map=new HashMap<Integer,Object>();
//写入
public void put(int i,Object o){
System.out.println(Thread.currentThread().getName()+"--写入");
map.put(i,o);
System.out.println(Thread.currentThread().getName()+"写入ok");
}
//读取
public void get(int s){
System.out.println(Thread.currentThread().getName()+"--读取");
map.get(s);
System.out.println(Thread.currentThread().getName()+"读取ok");
}
public int size(){
return map.size();
}
}
-读取安全:ReadWriteLock
public class ReadWriteLock2 {
public static void main(String[] args) {
myCache2 cache=new myCache2();
//写入
for (int i =1; i <=10; i++) {
final int temp=i;
new Thread(()->{
cache.put(temp,temp);
},String.valueOf(i)).start();
}
//读取
for (int i =1; i <=10; i++) {
final int temp=i;
new Thread(()->{
cache.get(temp);
},String.valueOf(i)).start();
}
}
}
class myCache2{
private volatile Map<Integer,Object> map=new HashMap<Integer,Object>();
//比ReentrantLock()更加细粒度,锁的用法差不多
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
//Lock lock=new ReentrantLock();
//写入
public void put(int i,Object o){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"--写入");
map.put(i,o);
System.out.println(Thread.currentThread().getName()+"写入ok");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
//读取
public void get(int s){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"--读取");
map.get(s);
System.out.println(Thread.currentThread().getName()+"读取ok");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
12、BlockingQueue--阻塞队列
应用:多线程并发,线程池
方式 | 抛出异常 | 特殊值 | 一直阻塞 | 定时阻塞 |
---|---|---|---|---|
添加(队列已满时) | add() | offer() | put() | offer(element,time,timeunit) |
移除(队列为空时) | remove() | poll() | take() | poll(time,timeunit) |
获取队列首元素 | element() | peek() | 不可用 | 不可用 |
抛出异常:BlockingQueue实现了------>Queue接口------>Collecion接口,Collection.add()拒绝添加元素时,必须抛出异常。
特殊值:Queue.offer()会返回插入的成功与否
一直阻塞:BlockingQueue.put()自己新的添加方法
/*
* 抛出异常
* */
public static void demo1(){
BlockingQueue queue=new ArrayBlockingQueue(3);
System.out.println(queue.add("a"));//返回布尔值
System.out.println(queue.add("b"));
System.out.println(queue.add("c"));
System.out.println(queue.element());//查看队首元素
//IllegalStateException 抛出异常
//System.out.println(queue.add("d"));//队列已满,添加元素,就抛出异常
System.out.println("==================");
System.out.println(queue.remove());//返回移除元素
System.out.println(queue.element());//查看队首元素
System.out.println(queue.remove());
System.out.println(queue.remove());
//NoSuchElementException 抛出异常
//System.out.println(queue.remove());////队列为空,移除元素,就抛出异常
}
/*
* 不抛异常
* */
public static void demo2() throws InterruptedException {
BlockingQueue queue=new ArrayBlockingQueue(3);
System.out.println(queue.offer("a"));//返回布尔值
System.out.println(queue.offer("b"));
System.out.println(queue.offer("c"));
System.out.println(queue.peek());//查看队列首部元素
//队列已满,添加元素,就会返回false
//System.out.println(queue.offer("d"));
System.out.println("===============");
System.out.println(queue.poll());//返回移除元素
System.out.println(queue.peek());//查看队列首部元素
System.out.println(queue.poll());
System.out.println(queue.poll());
//System.out.println(queue.poll());//队列为空,移除元素,就返回null
}
/*
* 一直等待、阻塞
* */
public static void demo3() throws InterruptedException {
BlockingQueue queue = new ArrayBlockingQueue(3);
queue.put("a");//无返回值
queue.put("b");
queue.put("c");
//队列已满,添加元素,就一直阻塞,直到有元素出队
//queue.put("d");
System.out.println(queue.take());//返回移除元素
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());//队列为空,移除元素,就一直等待直到
}
/*
* 定时等待
* */
public static void demo4() throws InterruptedException {
BlockingQueue queue=new ArrayBlockingQueue(3);
System.out.println(queue.offer("a"));//返回布尔值
System.out.println(queue.offer("b"));
System.out.println(queue.offer("c"));
//队列已满,添加元素,只等待4s,如果没有出队就停止阻塞,并返回false
System.out.println(queue.offer("d",4, TimeUnit.SECONDS));
System.out.println("===============");
System.out.println(queue.poll());//返回移除元素
System.out.println(queue.poll());
System.out.println(queue.poll());
//队列为空,移除元素,等待4s,如果没有元素入队就停止等待,并返回null
System.out.println(queue.poll(4,TimeUnit.SECONDS));
}
13、同步队列-SynchronousQueue
package com.company.queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
//同步队列,SynchronousQueue 不能存储元素,放入一个必须取出,才能放入下一个
public class synchronousQueue {
public static void main(String[] args) {
BlockingQueue<String> queue= new SynchronousQueue<String>();
new Thread(()->{
try {
queue.put("1");
System.out.println("放了1");
queue.put("2");
System.out.println("放了2");
queue.put("3");
System.out.println("放了3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"a").start();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"===>拿出"+queue.take());
System.out.println(Thread.currentThread().getName()+"===>拿出"+queue.take());
System.out.println(Thread.currentThread().getName()+"===>拿出"+queue.take());;
} catch (InterruptedException e) {
e.printStackTrace();
}
},"b").start();
}
}
14、线程池
三大线程池:Executors.newSingleThreadExecutor();//单一线程池,只能创建一个线程
Executors.newFixedThreadPool(5);//创建固定大小的线程池
Executors.newCachedThreadPool();//缓存线程池,没有固定数量
-Executors创建三大线程池
//线程池的创建,由线程池创建线程
public class threadPool {
public static void main(String[] args) {
//三大线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();//单一线程池,只能创建一个线程
//ExecutorService executorService = Executors.newFixedThreadPool(5);//固定大小的线程池
//ExecutorService executorService = Executors.newCachedThreadPool();//缓存线程池,没有固定数量
try {
for (int i = 0; i <40 ; i++) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"执行===>");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//确保最后一定能关闭
executorService.shutdown();//关闭线程池
}
}
}
-七大参数
源码分析推导七大参数
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//实际线程池创建者
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
//七大参数
public ThreadPoolExecutor(int corePoolSize,//池子的核心线程数
int maximumPoolSize,//池子最大线程数
long keepAliveTime,//在池子中住的时间
TimeUnit unit,//设定时间
BlockingQueue<Runnable> workQueue,//阻塞队列
ThreadFactory threadFactory,//线程工厂
RejectedExecutionHandler handler) {//拒绝执行处理
理解:
//最好的创建线程池的方式
public class threadPool {
public static void main(String[] args) {
/*
* new ThreadPoolExecutor.AbortPolicy());//不处理,抛出异常RejectedExecutionException
* new ThreadPoolExecutor.CallerRunsPolicy());//不处理也不抛出异常,哪里来的回哪里去
* new ThreadPoolExecutor.DiscardPolicy());//直接丢掉任务,不处理,不抛出异常
* new ThreadPoolExecutor.DiscardOldestPolicy());//丢弃最早的任务,让新的任务替代旧任务的位置,不抛异常
* */
ExecutorService executorService=new ThreadPoolExecutor(
3, //核心线程数(一直再内存中,不会被回收),任务来了,核心线程不够时,任务就进入队列,队列也满了就再创建非核心线程,最多6个
6, //最大线程数
2, //如果核心线程超过2s没有任务处理,就关闭3个核心线程。
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(4), //等候室
Executors.defaultThreadFactory(),//创建线程的工厂
new ThreadPoolExecutor.DiscardOldestPolicy());//尝试和最早的竞争,不抛异常
try {
for (int i = 0; i <15 ; i++) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"执行===>");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//确保最后一定能关闭
executorService.shutdown();//关闭线程池
}
}
}
-总结:
lambda表达式,函数式接口,链式编程,stream流式计算====>很重要,必须会
1、三大线程池--->必须会
2、七大参数---->必须会
3、最好使用new ThreadPoolExecutor()创建线程池,不要使用Executors,因为它不安全
问题:线程池的最大线程数应该设置为多少最好?
回答:cpu密集型:=核数,每核分配一个线程,这样效率最高
I/O密集型:>多个程序都要使用I/O线程
15、四大函数式接口(重点)
新时代程序员必会:lambda表达式,函数式接口,链式编程,stream流式计算
函数式接口:只有一个方法的接口。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
/**
* Function 函数型接口:一个输入参数,返回输入参数
* */
public class funtionTest {
public static void main(String[] args) {
// Function f=new Function<String,String>(){
// @Override
// public String apply(String s) {
// return s;
// }
// };
Function f=(str)->{return str;};
System.out.println(f.apply("wq"));
}
}
/**
* Predicate 判定型接口:一个输入参数,返回布尔值
* */
public class predicateTest {
public static void main(String[] args) {
// Predicate <String>predicate=new Predicate<String>() {
// @Override
// public boolean test(String str) {
// System.out.println("wq");
// return str.isEmpty();
// }
// };
Predicate <String>predicate=(str)->{return str.isEmpty();};
System.out.println(predicate.test("wq"));
}
}
/**
* Consumer 消费型接口:一个输入参数,没有返回值。吃掉了一个东西
* */
public class consumerTest {
public static void main(String[] args) {
// Consumer consumer=new Consumer<String>() {
// @Override
// public void accept(String str) {
// System.out.println(str);
// }
// };
Consumer consumer=(str)->{System.out.println(str);};
consumer.accept("wq");
}
}
package com.company.function;
import java.util.function.Supplier;
/**
* Supplier 供给型接口:没有输入参数,有返回值
* */
public class supplyTest {
public static void main(String[] args) {
// Supplier supplier=new Supplier<String>() {
// @Override
// public String get() {
// return new String("wq");
// }
// };
Supplier supplier=()->{return "wq";};
System.out.println(supplier.get());
}
}
16、stream流式计算
链式编程+流式计算
/*
* 流式计算stream
* */
public class streamTest {
public static void main(String[] args) {
User u1=new User(1,"a",23);
User u2=new User(2,"b",42);
User u3=new User(3,"c",30);
User u4=new User(4,"d",28);
User u5=new User(5,"e",26);
User u6=new User(6,"f",24);
List<User> list= Arrays.asList(u1 ,u2, u3,u4,u5,u6);
list.stream()
.filter((u)->{return u.getId()%2==0;}) //过滤出id为偶数的用户
.filter((u)->{return u.getAge()>24;}) //过滤大于40岁的用户
.map((u)->{return u.getName().toUpperCase(Locale.ROOT);})//获取过滤出 的用户的姓名的大写
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);}) //将姓名倒序排序
.limit(1) //输出用户数
.forEach(System.out::println); //打印筛选出的用户的姓名
}
}
17、JMM理解
-计算机cpu,内存分析
问题:什么 是JMM
JMM:java内存模型,屏蔽各系统和硬件之间的差异,让代码在不同的平台达到相同的访问效果。
JMM将内存分为两部分,主内存和工作内存,每个线程都有自己的工作内存。主内存就是物理存储空间,工作内存就是高速缓存和寄存器。他们要将使用的数据从主内存中拷贝一份,最后将数据返回给主内存.
缺陷:当多个线程使用了同一个变量,最后他们返回的数据都不一样,相互之间不能知道变量的赋值情况。
如果每次直接从主内存中读取数据,这样的访问速度是非常慢的,对性能的影响非常大。
所以JMM规定每个线程都有自己的工作内存,JMM规定了一套自己的标准来解决线程之间的问题。
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
-
- lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
- unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
- use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
- assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
- store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
- write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
-
- 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存
线程无法见到主内存的值的变化
//陷入死循环
public class JMMtest {
private static int num=0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while(num==0){
}
}).start();
TimeUnit.SECONDS.sleep(2);
num=1;
System.out.println(num);
}
}
18、异步回调
异步回调的理解
没有返回值的异步回调
//没有返回值 CompletableFuture.runAsync()
CompletableFuture <Void>completableFuture=CompletableFuture.runAsync(()->{
System.out.println("runAsync====void");
});
System.out.println("wq");
completableFuture.get();
有返回值的异步回调
//有返回值 CompletableFuture.supplyAsync()
CompletableFuture <String>completableFuture=CompletableFuture.supplyAsync(()->{
//int p=2/0;
return "123";
});
System.out.println("wq");
completableFuture.whenComplete((t,u)->{//t,u的数据类型取决于CompletableFuture传入的
System.out.println("t====>"+t);//接收顺利执行的返回值
System.out.println("u====>"+u);//接收异常执行的返回值,接收e
});
completableFuture.exceptionally((e)->{//执行异常时 的处理
System.out.println(e.getCause());
return "error";
});
顺利执行:
wq
t====>123
u====>null
执行异常:
wq
t====>null
u====>java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero
19、Volatile
Volatile是java虚拟机提供的轻量级的同步机制。
1、保证可见性
原理:使用Volatile的变量,这个变量的存取不能缓存到寄存器。编译器知道这个变量可能会在外部改变,所以每次存取都会重新从内存中读取,这才保证了每个线程访问到的数据都和内存保持一致。以此实现变量的可见性。
//不加volatile就会陷入死循环
public class JMMtest {
private static volatile int num=0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while(num==0){
}
}).start();
TimeUnit.SECONDS.sleep(2);
num=1;
System.out.println(num);
}
}
2、不保证原子性
原子性:不可分割
就是在执行过程中不能被中断被打扰。
public class atomicTest {
private static volatile int num=0;
public static void add(){
num++;
}
public static void main(String[] args) {
//让十个线程都执行1000次add方法---理论上num=20000,到实际结果少于20000
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 2000; j++) {
add();
}
}).start();
}
if(Thread.activeCount()>2){//默认活动的线程 main,gc
Thread.yield();//让main线程礼让
}
System.out.println("num="+num);
}
}
我们知道使用lock和synchronized能够解决问题。使用原子性的数据类型。
这些原子级的类是最底层的,直接与内存进行取值赋值,Unsafe类很特殊。
package com.company.volatileTest;
import java.util.concurrent.atomic.AtomicInteger;
//原子性测试 AtomicInteger原子级的int
public class atomicTest {
private static volatile AtomicInteger num=new AtomicInteger();
public static void add(){
num.getAndIncrement();//执行num+1操作
}
public static void main(String[] args) {
//num一定=20000
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 2000; j++) {
add();
}
}).start();
}
if(Thread.activeCount()>2){//默认活动的线程 main,gc
Thread.yield();//让main线程礼让
}
System.out.println("num="+num);
}
}
3、禁止指令重排
由于内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度,以上是硬件级别的优化。
编译器优化常用的方法有:将内存变量缓存到寄存器和调整指令顺序充分利用CPU指令流水线等,常见的是重新排序读写指令。对常规内存进行优化的时候,这些优化是透明的,而且效率很好。
例如:
int x=0; 1
int y=1; 2
x=x+1; 3
y=x+y; 4
//我们以为的执行顺序:1234
//但是指令重排可能会变成:1324 2134 最后结果都是一样的
不可能出现 3124,cpu的指令重排前提:要保证数据之间的依赖性
Volatile:1、产生内存屏障(单例模式中使用最多),防止指令重排 2、保证可见性
20、单例模式
单例就是构造器私有,单例模式分为两种:饿汉式和懒汉式。
在java中枚举类就是使用单例模式的。
饿汉式单例
//饿汉式单例
public class Hungry {
//还没使用前就创建了实例加载了内存,如果不使用容易浪费空间
private int[] b=new int[1024];
private int[] c=new int[1024];
private int[] d=new int[1024];
private static Hungry hungry = new Hungry();//饿汉式,提前创建好对象
private Hungry() {
}
public static Hungry getInstance() {
return hungry;
}
}
懒汉式单例DCL
//饿汉式进阶:懒汉式单例DCL
//由于饿汉式对空间的浪费
public class LayzMan {
//volatile+双重检测锁
private static volatile LayzMan layzMan;//volatile防止指令重排
private LayzMan(){
System.out.println(Thread.currentThread().getName()+"----ok");
}
/*
* synchronized可以解决问题,但是存在问题
* new LayzMan()的过程:1、数据加载到内存 2、执行构造方法,初始化对象 3、对象指向内存空间
* 这个过程不是原子性操作,cpu可以执行 123 极端情况下可能指令重排 132
* 所以使用volatile
* */
public static LayzMan getInstance(){
if (layzMan == null) {//双重检测懒汉单例
synchronized (LayzMan.class) {// 1、尝试用synchronized解决并发问题,可以解决
if (layzMan == null) {//需要时在创建
layzMan = new LayzMan();
}
}
}
return layzMan;
}
//多线程并发测试懒汉单例:1,按道理应该只能创建一个懒汉单例,实际不是
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
getInstance();
}).start();
}
}
}
反射破坏懒汉单例
/*按道理,layzMan和layzMan1他们是同一个对象
* 但是实际不是,说明Class可以破坏单例,懒汉式也不是绝对安全的
*
*/
class test{
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
LayzMan layzMan=LayzMan.getInstance();
Constructor<LayzMan> constructor = LayzMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
LayzMan layzMan1 = constructor.newInstance();
System.out.println(layzMan);
System.out.println(layzMan1);
}
}
解决反射破坏问题
private LayzMan() {
synchronized (LayzMan.class) {
if (layzMan!=null){
throw new RuntimeException("不要试图通过反射破坏单例");
}
}
}
// 结果:
Exception in thread "main" java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
at com.company.single.test.main(LayzMan.java:34)
Caused by: java.lang.RuntimeException: 不要试图通过反射破坏单例
at com.company.single.LayzMan.<init>(LayzMan.java:14)
... 6 more
再破坏
/**
** 不去调用getInstance(),直接通过反射创建实例
**/
class test{
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//LayzMan layzMan=LayzMan.getInstance();
Constructor<LayzMan> constructor = LayzMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
LayzMan layzMan = constructor.newInstance();
LayzMan layzMan1 = constructor.newInstance();
System.out.println(layzMan);
System.out.println(layzMan1);
}
}
结果:com.company.single.LayzMan@4eec7777
com.company.single.LayzMan@3b07d329
解决再破坏
private static boolean flag=false;
//volatile+双重检测锁
private static volatile LayzMan layzMan;//volatile防止指令重排
private LayzMan() {
if(flag==false) {
flag = true;
}else {
throw new RuntimeException("不要试图通过反射破坏单例");
}
}
再破坏
class test{
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//LayzMan layzMan=LayzMan.getInstance();
Constructor<LayzMan> constructor = LayzMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
Field field = LayzMan.class.getDeclaredField("flag");
field.setAccessible(true);
LayzMan layzMan = constructor.newInstance();
field.set(layzMan,false);//layzMan初始化后重新设值,破坏
LayzMan layzMan1 = constructor.newInstance();
System.out.println(layzMan);
System.out.println(layzMan1);
}
反射不能破坏枚举
package com.company.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public enum Enumsola {
INSTANCE;
public static Enumsola getInstance(){
return INSTANCE;
}
}
//反射不能破坏枚举
class test0{
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Enumsola instance1 = Enumsola.getInstance();
Constructor<Enumsola> constructor = Enumsola.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
Enumsola instance2 = constructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
结果:Exception in thread "main" java.lang.NoSuchMethodException:
结果说没有这个构造方法,但是源码当中有这个方法,通过命令行编译,无法找到解决问题
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.company.single;
public enum Enumsola {
INSTANCE;
private Enumsola() {
}
public static Enumsola getInstance() {
return INSTANCE;
}
}
jad反编译枚举
经过反编译,再cmd中使用命令,jad Enumsola.class ,会生成一个jad反编文件
惊讶发现: private Enumsola(String s, int i)有参构造,java源码骗了我们
public final class Enumsola extends Enum
{
public static Enumsola[] values()
{
return (Enumsola[])$VALUES.clone();
}
public static Enumsola valueOf(String name)
{
return (Enumsola)Enum.valueOf(com/company/single/Enumsola, name);
}
private Enumsola(String s, int i)
{
super(s, i);
}
解决枚举
package com.company.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public enum Enumsola {
INSTANCE;
public static Enumsola getInstance(){
return INSTANCE;
}
}
//反射不能破坏枚举
class test0{
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Enumsola instance1 = Enumsola.getInstance();
Constructor<Enumsola> constructor = Enumsola.class.getDeclaredConstructor(String.class,int.class);///重点
constructor.setAccessible(true);
Enumsola instance2 = constructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
最后才出现了我们想看到的异常
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
21、CAS理解
知道什么是CAS?
CAS:比较当前值与内存空间是否是期望的值,如果不是就一直循环
public class CASdemo {
//CAS 就是compareAndSet:比较并交换
/**expectedValue 期望值 newValue 新的值
* 如果当前值=expectedValue,就将它的值改为newValue;CAS是CPU的并发原语
* */
public static void main(String[] args) {
AtomicInteger num=new AtomicInteger(2020);
num.compareAndSet(2020, 2021);
System.out.println(num.get());//num=2021
//当前值变为2021,与2020比较,不相等
num.getAndIncrement();//+1
System.out.println(num.compareAndSet(2020, 2021));
System.out.println(num.get());
}
}
getAndIncrement()源码分析
public final int getAndIncrement() {
return U.getAndAddInt(this, VALUE, 1);
}
//Unsafe类
/**offset: o的地址偏移值
*/
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);//获取地址空间的值
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v; //如果o的地址偏移offset后的地址空间的值等于 v,就执行v + delta
}
java无法直接操作内存,需要调用c++,c++可以直接操作内存。
但是Unsafe类是java留的后门,可以直接操作内存,效率很高
CAS缺点:1、循环耗时 2、一次只能保证一个共享变量的原子性 3、ABA问题
22、原子引用
解决ABA问题:使用带版本号的原子类
public class ABAdemo {
//初始值 1 ; 初始版本号 1
static AtomicStampedReference<Integer> num=new AtomicStampedReference(1,1);
public static void main(String[] args) {
new Thread(() -> {
int stamp = num.getStamp();//获取初始版本号
System.out.println(Thread.currentThread().getName()+"===>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//每更新一次,版本号加1,
System.out.println("A=="+num.compareAndSet(1, 2,
num.getStamp(), num.getStamp() + 1));
System.out.println(Thread.currentThread().getName()+"===>"+num.getStamp());
System.out.println("A=="+num.compareAndSet(2, 1,
num.getStamp(), num.getStamp() + 1));
System.out.println(Thread.currentThread().getName()+"===>"+num.getStamp());
},"A").start();
new Thread(() -> {
int stamp = num.getStamp();//获取初始版本号
System.out.println(Thread.currentThread().getName()+"===>"+stamp);
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B=="+num.compareAndSet(1, 6,
num.getStamp(), num.getStamp() + 1));
System.out.println(Thread.currentThread().getName()+"===>"+num.getStamp());
},"B").start();
}
}
23、可重入锁
回顾锁
公平锁:非公平锁:
读锁:写锁:
可重入锁(递归锁):就好比我拥有了家里大门的钥匙,就自动有了里面房间的钥匙
synchronized锁实现可重入
public class syncDemo {
public static void main(String[] args) {
Phone phone=new Phone();
new Thread(()->{
phone.send();
},"A").start();
new Thread(()->{
phone.send();
},"B").start();
}
}
class Phone{
public synchronized void send(){
System.out.println(Thread.currentThread().getName()+"发短信");
call();
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"打电话");
}
}
结果:
A发短信
A打电话
B发短信
B打电话
lock锁实现可重入
public class lockDemo {
public static void main(String[] args) {
Phone1 phone=new Phone1();
new Thread(()->{
phone.send();
},"A").start();
new Thread(()->{
phone.send();
},"B").start();
}
}
class Phone1{
Lock lock=new ReentrantLock();
public void send(){
lock.lock();//注意lock锁必须成对使用 lock.lock() lock.unlock();加几把锁,就要解几把锁
try {
System.out.println(Thread.currentThread().getName()+"发短信");
call();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"打电话");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
24、自旋锁
使用CAS自定义自旋锁
package com.company.returnlock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class spinLock {
//Integer 0
//Thread null
private AtomicReference<Thread> spin=new AtomicReference(null);
public void mylock() throws InterruptedException {
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"进入自旋");
while(!spin.compareAndSet(null,thread)){
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName());
}
}
public void myunlock(){
Thread thread=Thread.currentThread();
spin.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"结束自旋");
}
}
class test{
public static void main(String[] args) {
spinLock lock=new spinLock();
new Thread(()->{
try {
lock.mylock();
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.myunlock();
}
},"A").start();
new Thread(()->{
try {
lock.mylock();
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.myunlock();
}
},"B").start();
}
}
25、死锁
什么是死锁:
因互相抢夺对方的资源,而相互等待
死锁产生的原因
1、对不可剥夺的资源的竞争,才有可能发生死锁。对可剥夺资源不会产生死锁
2、请求和释放资源的顺序不当
死锁案例
package com.company.returnlock;
import java.util.concurrent.TimeUnit;
public class Deadlock implements Runnable{
private String lock1;
private String lock2;
public Deadlock(String i,String j){
this.lock1=i;
this.lock2=j;
}
@Override
public void run() {
synchronized (lock1){
System.out.println(Thread.currentThread().getName()+"lock1==>"+lock2);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2){
System.out.println(Thread.currentThread().getName()+"lock2==>"+lock1);
}
}
}
}
class test2{
static String lock1="lock1";
static String lock2="lock2";
public static void main(String[] args) {
new Thread(new Deadlock(lock1,lock2),"A").start();
new Thread(new Deadlock(lock2,lock1),"B").start();
}
}
死锁排查
工作,面试中,如何排查死锁:
1、日志
2、查看堆栈信息
如何查看java堆栈信息
1、jps: jdk/bin 有jps工具 在ideal终端使用 jps -l 命令可以查看当前java程序运行的进程号
2、jstack 进程号
3、查看信息
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧