1、线程和进程看完狂神的视频 纯属想记录 回顾 狂神哥直链: 遇见狂神说的个人空间_哔哩哔哩_bilibili 没有不会做的事,只有不想做的事。
线程进程是 *** 作系统中的应用程序、是资源分配的基本单位,线程是用来执行具体的任务和功能,是CPU调度和分派的最小单位
一个进程往往可以包含多个线程,至少包含一个
Java默认有几个线程?2个线程! main线程、GC线程
线程的状态
public enum State { //运行 NEW, //运行 RUNNABLE, //阻塞 BLOCKED, //等待 WAITING, //超时等待 TIMED_WAITING, //终止 TERMINATED; }
线程的生命周期
新建、就绪、运行、阻塞、死亡
进程JAVA真的可以开启线程吗? 开不了的!
java是没有权限去开启线程、 *** 作硬件的,这是一个native的一个本地方法,它可以调用底层的c++代码
public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { } } } //这是一个C++底层,Java是没有权限 *** 作底层硬件的 private native void start0();并发
多线程 *** 作同一个资源
- CPU只有一核,模拟出来多个线程,天下武功唯快不破,那么就会造成一个CPU调度速度很快假象同时执行的,实则是交替执行,并发编程的本质就是充分的利用CPU资源
并行:多个人行走
-
CPU多核的情况,多个线程同时执行,我们可以使用线程池!
获取CPU的核数
//获取CPU核数 System.out.println(Runtime.getRuntime().availableProcessors()); }
wait 跟sleep的区别
1、父类不同
wait => Object
sleep => Thread
一般企业中使用的休眠都是:
package com.neihan.dome; import java.util.concurrent.TimeUnit; public class CpuDome { public static void main(String[] args) { TimeUnit.SECONDS.sleep(1); // SEConDS 是休眠的单位 } }
2、关于锁释放
wait: 会释放锁
sleep:不会释放锁
3、使用的范围是不同的
wait:必须在同步代码块中使用
sleep:可以在任意地方使用
Lock公平锁:十分公平,必须先来后到,先进入的线程先执行
不公平锁:十分不公平,可以插队,当前面的线程需要执行的时间很长的时候,后面的线程可以插队**(默认不公平锁)**
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-umyUkvZy-1636546509443)(C:UsersNeihanAppDataRoamingTyporatypora-user-imagesimage-20211110143059977.png)]
Synchronized与Lock的区别
1、Synchronized 是java关键字,Lock是类
2、Synchronized 无法获取判断锁的状态,Lock可以判断
3、Synchronized 会自动释放锁,Lock必须手动加锁释放锁
4、Synchronized 线程 A (获得锁->阻塞)线程 B(等待) Lock 就不一定会等下去,Lock有一个trylock去尝试获得锁,不会造成长久的等待。
4、Synchronized 是可重入锁,不可以中断,非公平的,Lock 可重入,可以判断锁,可以自己设置公平锁非公平锁
5、 Synchronized 适合锁少量代码同步问题,Lock适合锁大量的同步代码
生产者消费者的关系synchronized版本
if 判断有虚假唤醒,处理方法用 while
结论:就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。
这也就是为什么用while而不用if的原因了,因为线程被唤醒后,执行开始的地方是wait之后
package com.neihan.pc; public class ConsumeAndProduct { public static void main(String[] args) { Data data = new Data(); new Thread(() ->{ for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); new Thread(() ->{ for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"B").start(); new Thread(() ->{ for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"C").start(); new Thread(() ->{ for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"D").start(); } } class Data{ Integer number = 0; public synchronized void increment() throws InterruptedException { //判断等待 while (0!= number){ this.wait(); } //操作 number++; System.out.println(Thread.currentThread().getName()+"==>"+number); //通知 this.notifyAll(); } public synchronized void decrement() throws InterruptedException { //判断 while (0== number){ this.wait(); } // *** 作 number--; System.out.println(Thread.currentThread().getName()+"==>"+number); //通知 this.notifyAll(); } }
Lock版本
package com.neihan.pc; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockCAP { public static void main(String[] args) { Data2 data = new Data2(); new Thread(() ->{ for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); new Thread(() ->{ for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"B").start(); new Thread(() ->{ for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"C").start(); new Thread(() ->{ for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"D").start(); } } class Data2{ Integer number = 0; Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); public void increment() throws InterruptedException { //加锁 lock.lock(); try { while (0!= number){ //等待 condition.await(); } //操作 number++; System.out.println(Thread.currentThread().getName()+"==>"+number); //通知 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { //解锁 lock.unlock(); } } public void decrement() throws InterruptedException { //加锁 lock.lock(); try { while (0== number){ //等待 condition.await(); } // *** 作 number--; System.out.println(Thread.currentThread().getName()+"==>"+number); //通知 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { //解锁 lock.unlock(); } } }Condition的优势
指定唤醒线程
精准的通知和唤醒的线程!
如果我们要指定通知的下一个进行顺序怎么办呢? 我们可以使用Condition来指定通知进程
package com.neihan.pc; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class JUCPC { public static void main(String[] args) { Data3 data3 = new Data3(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { data3.printA(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { data3.printB(); } catch (InterruptedException e) { e.printStackTrace(); } } },"B").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { data3.printC(); } catch (InterruptedException e) { e.printStackTrace(); } } },"C").start(); } } class Data3{ private Lock lock = new ReentrantLock(); Condition condition1 = lock.newCondition(); Condition condition2= lock.newCondition(); Condition condition3= lock.newCondition(); Integer number = 1; // 1A 2B 3C public void printA() throws InterruptedException { //加锁 lock.lock(); try { while (number != 1){ //等待 condition1.await(); } number = 2; System.out.println(Thread.currentThread().getName()+"=>AAAAAAAAA"); //唤醒 B condition2.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { //解锁 lock.unlock(); } } public void printB() throws InterruptedException { //加锁 lock.lock(); try { while (number != 2){ //等待 condition2.await(); } number =3 ; //唤醒 C System.out.println(Thread.currentThread().getName()+"=>BBBBBBBBBBBB"); condition3.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { //解锁 lock.unlock(); } } public void printC() throws InterruptedException { //加锁 lock.lock(); try { while (number != 3){ //等待 condition3.await(); } number = 1; //唤醒 A System.out.println(Thread.currentThread().getName()+"=>CCCCCCCCCCCCCC"); condition1.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { //解锁 lock.unlock(); } } }8锁现象
synchronized 锁的是当前的对象
package com.neihan.lock8; import java.util.concurrent.TimeUnit; public class Test1 { public static void main(String[] args) throws InterruptedException { Send send1 = new Send(); Send send2= new Send(); new Thread(()->{ try { send1.sendSms(); } catch (InterruptedException e) { e.printStackTrace(); } },"A").start(); new Thread(()->{ send2.sendEmail(); },"B").start(); } } class Send{ public synchronized void sendSms() throws InterruptedException { //增加延迟 1 s TimeUnit.SECONDS.sleep(1); System.out.println("发送短信"); } public synchronized void sendEmail(){ System.out.println("发送邮箱"); } }
static synchronized(静态同步函数)
静态同步函数锁的是类的 Class
package com.neihan.lock8; import java.util.concurrent.TimeUnit; public class Test2 { public static void main(String[] args) { Send2 send = new Send2(); new Thread(()->{ try { send.sendSms(); } catch (InterruptedException e) { e.printStackTrace(); } },"A").start(); new Thread(()->{ send.sendEmail(); },"B").start(); } } class Send2{ public static synchronized void sendSms() throws InterruptedException { //增加延迟 1 s TimeUnit.SECONDS.sleep(1); System.out.println("发送短信"); } public synchronized void sendEmail(){ System.out.println("发送邮箱"); } }
如果我们使用一个静态同步方法、一个同步方法、一个对象调用顺序是什么?
集合不安全 List(不安全)原因:因为一个锁的是Class类的模板,一个锁的是对象的调用者。所以不存在等待,直接运行。
不安全的 触发 java.util.ConcurrentModificationException 异常 因为底层没有使用 synchronized
解决方案:
1、使用 new Vector<>()
2、Collections.synchronizedList(new ArrayList<>());
3、CopyOnWriteArrayList<>();
CopyonWriteArrayList 比 Vector 好在哪?
Vector 和 CopyonWriteArrayList 的区别
因为 Vector 使用了 synchronized 进行修饰,只要是使用了 synchronized 修饰的效率特别低下。
CopyonWriteArrayList 没有使用 底层是使用的是 Lock锁 写入时复制 效率会更加高效! COW 计算机程序设计领域的一种优化策略
package com.neihan.unsafe; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; public class TestList { public static void main(String[] args) { ListMap(不安全)list = new CopyOnWriteArrayList<>(); for (int i = 1; i <=10; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(list); }).start(); } } }
默认加载因子是0.75,默认的初始容量是16
new HashMap<>(16,0.75);
package com.neihan.unsafe; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; public class TestMap { public static void main(String[] args) { // 默认等价什么?new HashMap<>(16,0.75); //处理方式 Collections.synchronizedMap() //处理方式 new ConcurrentHashMap<>(); MapSet(不安全)map = new ConcurrentHashMap<>(); map.put("A","A"); map.put("S","B"); map.put("B","c"); System.out.println(map); } }
HashSet底层是什么?
hashSet底层就是一个HashMap;
public HashSet() { map = new HashMap<>(); }
package com.neihan.unsafe; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.UUID; import java.util.concurrent.CopyOnWriteArraySet; public class TestSet { public static void main(String[] args) { SetCallable
1、可以有返回值
2、可以抛出异常
3、方法不同 run() / call()
Callable
泛型就是返回值
启动需要 FutureTask 适配器 使得Callable 与 Runnable 有关联
package com.neihan.callable; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class TestCallable { public static void main(String[] args) throws ExecutionException, InterruptedException { MyThread myThread = new MyThread(); //使用 FutureTask 适配器 使得Callable 与 Runnable 有关联 FutureTask futureTask = new FutureTask(myThread); new Thread(futureTask).start(); //输出返回值 可能出现阻塞 可以把获取返回值放到最后一行,或者异步通信, 不阻塞 System.out.println(futureTask.get()); } } class MyThread implements Callable三大工具类 CountDownLatch{ @Override public Integer call() throws Exception { System.out.println("call()"); return 1024; } }
new CountDownLatch(5);
创建的时候 指定初始值
package com.neihan.add; import java.util.concurrent.CountDownLatch; public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { CountDownLatch count = new CountDownLatch(5); for (int i = 1; i <=5; i++) { new Thread(()->{ //减一 count.countDown(); System.out.println(Thread.currentThread().getName()); },String.valueOf(i)).start(); } //等待归零向下执行 count.await(); System.out.println("close"); } }CyclicBarrier
package com.neihan.add; import java.util.Hashtable; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class TestCyclicBarrier { public static void main(String[] args) { CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{ System.out.println("集结龙珠完成,召唤神龙"); }); for (int i = 1; i <=7; i++) { final Integer temp = i; new Thread(()->{ System.out.println("第"+temp+"龙珠"); try { //等待次数到达 cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } },String.valueOf(i)).start(); } } }Semaphore
package com.neihan.add; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; public class TestSemaphore { public static void main(String[] args) { Semaphore semaphore = new Semaphore(3); for (int i = 1; i <=6; i++) { new Thread(()->{ try { semaphore.acquire(); //得到 System.out.println(Thread.currentThread().getName()+"抢到车位"); TimeUnit.SECONDS.sleep(2); //休眠 2s System.out.println(Thread.currentThread().getName()+"离开到车位"); } catch (InterruptedException e) { e.printStackTrace(); }finally { //释放 semaphore.release(); } },String.valueOf(i)).start(); } } }
原理
semaphore.acquire(); 得到了资源,如果资源已经使用完,就等待资释放后再进行使用
semaphore.release(); 释放,会将当前的信号量释放 +1 然后唤醒等待的线程!
作用: 多个共享资源互斥的使用! 并发限流,控制最大的线程数!
读写锁所以如果我们不加锁的情况,多线程的读写会造成数据不可靠的问题。
我们也可以采用synchronized这种重量锁和轻量锁 lock去保证数据的可靠。
但是这次我们采用更细粒度的锁:ReadWriteLock 读写锁来保证
package com.neihan.add; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantReadWriteLock; public class TestReadWriteLock { public static void main(String[] args) { MyCacheLock cache = new MyCacheLock(); for (int i = 1; i <=10; i++) { final Integer temp = i; new Thread(()->{ cache.write(temp+"",temp+""); },String.valueOf(i)).start(); } for (int i = 1; i <=10; i++) { final Integer temp = i; new Thread(()->{ cache.read(temp+""); },String.valueOf(i)).start(); } } } class MyCache{ private volatile Map阻塞队列 BlockQueuemap = new HashMap<>(); public void write(String key,String value){ System.out.println(Thread.currentThread().getName()+"写入"+key); map.put(key,value); System.out.println(Thread.currentThread().getName()+"写入完成"); } public void read(String key){ System.out.println(Thread.currentThread().getName()+"读取"+key); map.get(key); System.out.println(Thread.currentThread().getName()+"读取完成"); } } class MyCacheLock{ private volatile Map map = new HashMap<>(); private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public void write(String key,String value){ //写锁 readWriteLock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName()+"写入"+key); map.put(key,value); System.out.println(Thread.currentThread().getName()+"写入完成"); } catch (Exception e) { e.printStackTrace(); } finally { //释放锁 readWriteLock.writeLock().unlock(); } } public void read(String key){ //读锁 readWriteLock.readLock().lock(); try { System.out.println(Thread.currentThread().getName()+"读取"+key); map.get(key); System.out.println(Thread.currentThread().getName()+"读取完成"); } catch (Exception e) { e.printStackTrace(); } finally { //释放读锁 readWriteLock.readLock().unlock(); } } }
是 Collection 的一个子类
什么情况下使用阻塞队列
多线程并发、线程池 的情况下使用
BlockQueue 有四组Api
1、抛出异常
2、不抛出异常,有返回值
3、一直阻塞
4、等待 超时阻塞
package com.neihan.queue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.TimeUnit; public class TestQueue { public static void main(String[] args) throws InterruptedException { test04(); } public static void test01(){ //队列需要初始化大小 ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); System.out.println(blockingQueue.add("a")); System.out.println(blockingQueue.add("b")); System.out.println(blockingQueue.add("c")); // java.lang.IllegalStateException: Queue full 异常 //System.out.println(blockingQueue.add("c")); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); //java.util.NoSuchElementException 异常 //System.out.println(blockingQueue.remove()); } public static void test02(){ //队列需要初始化大小 ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); System.out.println(blockingQueue.offer("a")); System.out.println(blockingQueue.offer("b")); System.out.println(blockingQueue.offer("c")); System.out.println(blockingQueue.offer("a")); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); } public static void test03() throws InterruptedException { //队列需要初始化大小 ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); //一直阻塞不会返回 blockingQueue.put("a"); blockingQueue.put("b"); blockingQueue.put("c"); //如果队列满了的话此时put不进去值,咋此处一直阻塞,程序不会停止 //blockingQueue.put("d"); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); //如果队列满了的话此时put不进去值,咋此处一直阻塞,程序不会停止 System.out.println(blockingQueue.take()); } public static void test04() throws InterruptedException { //队列需要初始化大小 ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); System.out.println(blockingQueue.offer("a")); System.out.println(blockingQueue.offer("b")); System.out.println(blockingQueue.offer("c")); //等待 两秒 如果队列还是满的话就不再等待 System.out.println(blockingQueue.offer("d",2, TimeUnit.SECONDS)); System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS)); System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS)); System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS)); //取值,两秒没取到就不再等待 System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS)); } }同步队列
同步队列 没有 容量 也可以视为 容量为一的队列;
进去一个元素必须等进去的元素出来其他的元素才能进入
put方法 和 take方法;
SynchronousQueue 的take是使用了lock锁保证线程安全的。
package com.neihan.queue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; public class TestSynchronousQueue { public static void main(String[] args) { SynchronousQueue线程池synchronousQueue = new SynchronousQueue<>(); new Thread(()->{ try { System.out.println(Thread.currentThread().getName()+" put 1"); synchronousQueue.put("1"); System.out.println(Thread.currentThread().getName()+" put 2"); synchronousQueue.put("2"); System.out.println(Thread.currentThread().getName()+" put 3"); synchronousQueue.put("3"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); new Thread(()->{ try { TimeUnit.SECONDS.sleep(3); System.out.println(synchronousQueue.take()+"take()"); TimeUnit.SECONDS.sleep(3); System.out.println(synchronousQueue.take()+"take()"); TimeUnit.SECONDS.sleep(3); System.out.println(synchronousQueue.take()+"take()"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } }
三大方式,七大参数,四种拒绝策略
池化技术
程序的运行,本质:占用系统的资源!我们需要去优化资源的使用 ===> 池化技术
线程池、JDBC的连接池、内存池、对象池 等等。。。。
资源的创建、销毁十分消耗资源
池化技术:事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,以此来提高效率。
线程池的好处1、降低资源的消耗
2、提高响应速度
3、方便管理
实现线程复用、可以控制最大并发数、管理线程
package com.neihan.pool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Dome01 { public static void main(String[] args) { //ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单个线程 //ExecutorService threadPool = Executors.newFixedThreadPool(5); //固定多少个线程 或者说最大多少个线程 ExecutorService threadPool = Executors.newCachedThreadPool(); //遇强则强,遇弱则弱 try { for (int i = 0; i < 100; i++) { //使用线程池之后,使用线程池来创建线程 threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+ " ok"); }); } } catch (Exception e) { e.printStackTrace(); } finally { //使用完线程池一定要关闭 threadPool.shutdown(); } } }线程池的三大方法
- ExecutorService threadPool = Executors.newSingleThreadExecutor(); 单个线程
- ExecutorService threadPool = Executors.newFixedThreadPool(5); //固定多少个线程 或者说最大多少个线程
- ExecutorService threadPool = Executors.newCachedThreadPool(); //遇强则强,遇弱则弱
1、核心线程
2、最大线程
3、阻塞队列
4、线程工厂
5、超时数
6、超时单位
7、拒绝策略
拒绝策略
1. new ThreadPoolExecutor.AbortPolicy(): //该拒绝策略为:银行满了,还有人进来,不处理这个人的,并抛出异常
超出最大承载,就会抛出异常:队列容量大小+maxPoolSize
2. new ThreadPoolExecutor.CallerRunsPolicy(): //该拒绝策略为:哪来的去哪里 main线程进行处理
3. new ThreadPoolExecutor.DiscardPolicy(): //该拒绝策略为:队列满了,丢掉异常,不会抛出异常。
4. new ThreadPoolExecutor.DiscardOldestPolicy(): //该拒绝策略为:队列满了,尝试去和最早的进程竞争,不会抛出异常
如何设置线程池的大小1、CPU密集型:电脑的核数是几核就选择几;选择maximunPoolSize的大小
I/O密集型:
在程序中有15个大型任务,io十分占用资源;I/O密集型就是判断我们程序中十分耗I/O的线程数量,大约是最大I/O数的一倍到两倍之间。
四大函数式接口新时代的程序员:lambda表达式、链式编程、函数式接口、Stream流式计算
Function 函数型接口函数式接口 只有一个方法的接口 并带有 @FunctionalInterface
package com.neihan.function; import java.util.function.Function; public class Demo01 { public static void main(String[] args) { // lambda 表达式 Function断定型接口Predicatefunction = (str)->{ return str; }; System.out.println(function.apply("neihan niu bi lambda")); } }
断定型接口 只能传入一个参数,返回值只能是Boolean
package com.neihan.function; import java.util.function.Predicate; public class Dome02 { public static void main(String[] args) { PredicateSupplier供给型接口predicate = (str)->{ return str.isEmpty();}; System.out.println(predicate.test("")); } }
没有参数 只有返回值
package com.neihan.function; import java.util.function.Supplier; public class Demo03 { public static void main(String[] args) { Supplier supplier = ()->{return "Neihan Niu bi ";}; System.out.println(supplier.get()); } }消费性接口Consumer
消费性接口 只有参数没有返回值
package com.neihan.function; import java.util.function.Consumer; public class Demo04 { public static void main(String[] args) { Consumerstream流试计算stringConsumer = new Consumer (){ @Override public void accept(String o) { System.out.println(o); } }; stringConsumer.accept("neihan "); } }
package com.neihan.function; import java.util.Arrays; import java.util.List; public class testStream { public static void main(String[] args) { Student student1 = new Student(16,"a",1); Student student2 = new Student(26,"b",2); Student student3 = new Student(25,"c",6); Student student4 = new Student(23,"d",4); Liststudents = Arrays.asList(student1, student2, student3, student4); //链式编程 stream流式计算 students.stream() .filter(student ->{return student.getId() % 2 ==0;}) .filter(student -> {return student.getAge() > 23;} ) .map(student -> {return student.getName().toUpperCase();}) //转换大写 .sorted((s1,s2)->{return s2.compareTo(s1);}) //排序 倒叙输出 .limit(1) // 分页,显示的页数 .forEach(System.out::printf); } }
package com.neihan.function; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamHome01 { public static void main(String[] args) { ListForkJoinintegers = Arrays.asList(2,3,4,6,60,7); String collect = integers .stream() .map(Object::toString) .sorted((s1, s2) -> { return s2.compareTo(s1); }) .filter((s) -> { return Integer.parseInt(s) % 2 == 0; }) .collect(Collectors.joining("")); String sz [] = new String[collect.length()]; for (int i = 0; i < collect.length(); i++) { sz[i] = collect.substring(i,i+1); } Arrays.stream(sz) .sorted((i1,i2)->{return i2.compareTo(i1);}) .forEach(System.out::printf); } }
ForkJoin 在JDK1.7,并行执行任务!提高效率~。在大数据量速率会更快!
大数据中:MapReduce 核心思想->把大任务拆分为小任务!
如何使用ForkJoin?- 1、通过ForkJoinPool来执行 - 2、计算任务 execute(ForkJoinTask> task) - 3、计算类要去继承ForkJoinTask; ForkJoin 的计算类
package com.neihan.forkjoin; import lombok.Data; import lombok.NoArgsConstructor; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.RecursiveTask; @Data @NoArgsConstructor public class ForkJoinDemo extends RecursiveTask{ private Long start; private Long end; private Long temp =10000L; //临界值 public ForkJoinDemo(Long start, Long end) { this.start = start; this.end = end; } @Override protected Long compute() { if((end - start ) < temp){ Long sum = 0L; for (Long i = start; i < end; i++) { sum+= i; } return sum; }else{ //使用Forkjoin计算 long middle = (start + end) / 2; ForkJoinDemo forkJoin1 = new ForkJoinDemo(start,middle); //拆分任务 把任务压入队列 forkJoin1.fork(); ForkJoinDemo forkJoin2 = new ForkJoinDemo(middle+1,end); forkJoin2.fork(); return forkJoin1.join() + forkJoin2.join(); } } }
测试类
package com.neihan.forkjoin; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.stream.LongStream; public class TestForkjoin { public static void main(String[] args) throws ExecutionException, InterruptedException { //test01(); 2928 test02(); //4043 //test03(); //198 } private static void test01(){ long start = System.currentTimeMillis(); long sum = 0L; for (Long i = 1L; i <= 10_0000_0000L; i++) { sum+= i; } Long end = System.currentTimeMillis(); System.out.println("sum= "+sum+"时间:"+(end-start)); } public static void test02() throws ExecutionException, InterruptedException { Long start = System.currentTimeMillis(); ForkJoinPool forkJoinPool = new ForkJoinPool(); ForkJoinTask异步回调task = new ForkJoinDemo(0L, 10_0000_0000L); ForkJoinTask submit = forkJoinPool.submit(task);//提交任务 Long sum = submit.get(); Long end = System.currentTimeMillis(); System.out.println("sum="+sum+"时间:"+(end-start)/2); } public static void test03(){ Long start = System.currentTimeMillis(); Long sum = LongStream.range(0L, 10_0000_0000L).parallel().reduce(0, Long::sum); Long end = System.currentTimeMillis(); System.out.println("sum="+sum+"时间:"+(end-start)); } }
Future 设计的初衷:对将来的某个事件结果进行建模!
package com.neihan.future; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public class TestFuture { public static void main(String[] args) throws ExecutionException, InterruptedException { test02(); } public void test01() throws ExecutionException, InterruptedException { CompletableFuturefuture = CompletableFuture.runAsync(() -> { //发起一个异步任务 try { TimeUnit.SECONDS.sleep(2); System.out.println("这是一个异步的消息"); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); }); System.out.println("Main线程"); System.out.println("future.get() = " + future.get()); } public static void test02() throws ExecutionException, InterruptedException { CompletableFuture future = CompletableFuture.supplyAsync(() -> { //发起一个异步任务 try { TimeUnit.SECONDS.sleep(2); System.out.println("这是一个异步的消息"); } catch (InterruptedException e) { e.printStackTrace(); } int i = 10 /0; System.out.println(Thread.currentThread().getName()); return 1024; // 成功的返回值 }); System.out.println(future.whenComplete((t, u) -> { System.out.println(t); // t 是正常返回的结果 System.out.println(u); // u 是错误的信息 }).exceptionally((e -> { System.out.println("e.getMessage() = " + e.getMessage()); return 500; //返回error 的信息 })).get()); } }
但是我们平时都使用CompletableFuture
JMM 什么是JMM?JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定!
关于JMM的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷回主存;
2、线程加锁前,必须读取主存中的最新值到工作内存中;
3、加锁和解锁是同一把锁;
线程中分为 工作内存、主内存
8种 *** 作:
-
Read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用; - load(载入):作用于工作内存的变量,它把read *** 作从主存中变量放入工作内存中;
-
Use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;
-
assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中; -
-
store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用; - write(写入):作用于主内存中的变量,它把store *** 作从工作内存中得到的变量的值放入主内存的变量中; -
-
lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;
-
unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;
- JMM对这8种 *** 作给了相应的规定*:
- - 不允许read和load、store和write *** 作之一单独出现。即使用了read必须load,使用了store必须write- 不允许线程丢弃他最近的assign *** 作,即工作变量的数据改变了之后,必须告知主存- 不允许一个线程将没有assign的数据从工作内存同步回主内存- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store *** 作之前,必须经过assign和load *** 作- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁- 如果对一个变量进行lock *** 作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign *** 作初始化变量的值- 如果一个变量没有被lock,就不能对其进行unlock *** 作。也不能unlock一个被其他线程锁住的变量- 对一个变量进行unlock *** 作之前,必须把此变量同步回主内存
遇到问题:程序不知道主存中的值已经被修改过了!
对Volatile 的理解Volatile是 Java 提供的 轻量级 的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排机制
保证可见性
volatile 没加这个关键字的话主存发生了改变但是Thread()线程没收到通知,还是读取到的是没修改过的数据 一直死循环
package com.neihan.jmm; import java.util.concurrent.TimeUnit; public class JMMDemo01 { private static volatile int sum =1; //volatile 没加这个关键字的话主存发生了改变但是Thread()线程没收到通知,还是读取到的是没修改过的数据 public static void main(String[] args) throws InterruptedException { new Thread(()->{ while (sum==1){ } }).start(); TimeUnit.SECONDS.sleep(1); sum=0; System.out.println("main"); } }
不保证原子性
如果不加lock和synchronized ,怎么样保证原子性?
使用原子类
这些类的底层都直接和 *** 作系统挂钩!是在内存中修改值。
package com.neihan.jmm; import java.util.concurrent.atomic.AtomicInteger; public class VDemo { private static volatile AtomicInteger count = new AtomicInteger(); public static void main(String[] args) { for (int i = 0; i < 20; i++) { new Thread(()->{ for (int j = 0; j < 1000; j++) { add(); } }).start(); } while (Thread.activeCount() > 2 ){ Thread.yield(); } System.out.println(Thread.currentThread().getName()+""+"count="+count); } public static void add(){ count.incrementAndGet(); } }禁止指令重排
什么是指令重排?
我们写的程序,计算机并不是按照我们自己写的那样去执行的
源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行
int x=1; //1 int y=2; //2 x=x+5; //3 y=x*x; //4 //我们期望的执行顺序是 1_2_3_4 可能执行的顺序会变成2134 1324 //可不可能是 4123? 不可能的 1234567
可能造成的影响结果:前提:a b x y这四个值 默认都是0
|线程A|线程B
|------
|x=a|y=b
|b=1|a=2
正常的结果: x = 0; y =0;
|线程A|线程B
|------
|b=1|a=2
|x=a|y=b
可能在线程A中会出现,先执行b=1,然后再执行x=a;
在B线程中可能会出现,先执行a=2,然后执行y=b;
那么就有可能结果如下:x=2; y=1.
volatile可以避免指令重排:
volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。
内存屏障:CPU指令。作用:
1、保证特定的 *** 作的执行顺序;
2、可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)
总结- volatile可以保证可见性;- 不能保证原子性- 由于内存屏障,可以保证避免指令重排的现象产生
面试官:那么你知道在哪里用这个内存屏障用得最多呢?单例模式
饿汉式、DCL懒汉式
饿汉模式单例模式私有构造
饿汉顾名思义 很饿 什么东西都先创建好
package com.neihan.jmm; public class Hungry { //可能会浪费空间 private byte[] data1 = new byte[1024*1024]; private byte[] data2 = new byte[1024*1024]; private byte[] data3 = new byte[1024*1024]; private Hungry(){ } private final static Hungry hungry = new Hungry(); private static Hungry getInstance(){ return hungry; } }DCL懒汉式
package com.neihan.jmm; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; public class LazyMan { private static boolean neihan = false; //红绿灯 private LazyMan(){ synchronized (LazyMan.class){ if(neihan == false){ neihan=true; }else{ throw new RuntimeException("小伙子不要试图使用反射来搞破坏!!!"); } } System.out.println(Thread.currentThread().getName()+"ok"); } private volatile static LazyMan lazyMan; private static LazyMan getInstance(){ if (lazyMan ==null){ synchronized (LazyMan.class){ if(lazyMan==null){ //单线程下是OK 的 但是多线程是不行的 需要加锁 加锁后java中有反射!!!! lazyMan = new LazyMan(); } } } return lazyMan; } public static void main(String[] args) throws Exception { //反射 //LazyMan instance1 = LazyMan.getInstance(); Field neihan = LazyMan.class.getDeclaredField("neihan"); Constructor静态内部类declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); //这个时候无视了私有的构造器,使用反射创建对象 LazyMan instance2 = declaredConstructor.newInstance(); neihan.setAccessible(true); neihan.set(instance2,false); //把字段的值改变又可以了 LazyMan instance1 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }
package com.neihan.jmm; public class Holder { private Holder(){ } public static Holder getInstance(){ return InnerClass.holder; } public static class InnerClass{ private static final Holder holder = new Holder(); } }
单例不安全, 因为反射
枚举
package com.neihan.jmm; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { EnumSingle instance1 = EnumSingle.INSTANCE; ConstructordeclaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); EnumSingle enumSingle = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(enumSingle); } }
使用枚举,我们就可以防止反射破坏了。
枚举类型的最终反编译源码:
public final class EnumSingle extends Enum { public static EnumSingle[] values() { return (EnumSingle[])$VALUES.clone(); } public static EnumSingle valueOf(String name) { return (EnumSingle)Enum.valueOf(com/ogj/single/EnumSingle, name); } private EnumSingle(String s, int i) { super(s, i); } public EnumSingle getInstance() { return INSTANCE; } public static final EnumSingle INSTANCE; private static final EnumSingle $VALUES[]; static { INSTANCE = new EnumSingle("INSTANCE", 0); $VALUES = (new EnumSingle[] { INSTANCE }); } }深入理解CAS 什么是CAS?
大厂必须深入研究底层!!!!修内功! *** 作系统、计算机网络原理、组成原理、数据结构
意思就是比较并交换
package com.neihan.cas; import java.util.concurrent.atomic.AtomicInteger; public class CASDemo { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(1024); //public final boolean compareAndSet(int expect, int update) System.out.println(atomicInteger.compareAndSet(1024, 241)); System.out.println(atomicInteger.get()); } }
Unsafe 类
总结CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行 *** 作!如果不是就一直循环,使用的是自旋锁。
缺点:
- 循环会耗时;- 一次性只能保证一个共享变量的原子性;- 它会存在ABA问题
CAS : ABA 问题 (狸猫换太子)
线程1:期望值是1,要变成2;
线程2:两个 *** 作:
- 1、期望值是1,变成3- 2、期望是3,变成1
所以对于线程1来说,A的值还是1,所以就出现了问题,骗过了线程1;
public class casDemo { //CAS : compareAndSet 比较并交换 public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2020); System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); //boolean compareAndSet(int expect, int update) //期望值、更新值 //如果实际值 和 我的期望值相同,那么就更新 //如果实际值 和 我的期望值不同,那么就不更新 System.out.println(atomicInteger.compareAndSet(2021, 2020)); System.out.println(atomicInteger.get()); //因为期望值是2020 实际值却变成了2021 所以会修改失败 //CAS 是CPU的并发原语 //atomicInteger.getAndIncrement(); //++ *** 作 System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); } }原子引用
解决ABA问题,对应的思想:就是使用了乐观锁~
带版本号的 原子 *** 作!
使用包装类会有对象引用的问题
Integer 使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。
带版本号的原子 *** 作
package com.neihan.cas; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicStampedReference; public class ABADemo { public static void main(String[] args) { //使用包装类会有对象引用的问题 //Integer 使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。 AtomicStampedReference各种锁的理解 公平锁,非公平锁atomicStampedReference = new AtomicStampedReference (1,1); // CAS 比较交换 new Thread(()->{ int stamp = atomicStampedReference.getStamp(); //获得版本号 System.out.println(Thread.currentThread().getName()+"Stamp=>"+stamp); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicStampedReference.compareAndSet( 1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)); System.out.println(Thread.currentThread().getName()+atomicStampedReference.getStamp()); //修改完之后再修改回来,另一条线程并不知情,看看能不能修改成功 System.out.println(atomicStampedReference.compareAndSet( 2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)); System.out.println(Thread.currentThread().getName()+atomicStampedReference.getStamp()); },"A").start(); new Thread(()->{ int stamp = atomicStampedReference.getStamp(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"Stamp"+stamp); System.out.println(Thread.currentThread().getName()+" "+atomicStampedReference.compareAndSet(1, 6, stamp, atomicStampedReference.getStamp() + 1)); System.out.println(Thread.currentThread().getName()+"Stamp"+atomicStampedReference.getStamp()); },"B").start(); } }
1、公平锁:非常公平,不能插队,必须先来后到
public ReentrantLock() { sync = new NonfairSync(); }
2、非公平锁:非常不公平,允许插队,可以改变顺序
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }可重入锁
1、Synchonized 锁
synchronized 版本的可重入锁 自动获得内部锁
package com.neihan.lock; public class Demo01 { public static void main(String[] args) { Phone phone = new Phone(); new Thread(()->{ phone.sms(); },"A").start(); new Thread(()->{ phone.sms(); },"B").start(); } } class Phone{ public synchronized void sms(){ System.out.println(Thread.currentThread().getName()+"==>sms"); call(); //这里也是一把锁 } public synchronized void call(){ System.out.println(Thread.currentThread().getName()+"==>call"); } }Lock版本
Lock 版本的重入锁 Lock 锁一定是配对出现,不然就会出现死锁
package com.neihan.lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Demo02 { public static void main(String[] args) { Phone2 phone2 = new Phone2(); new Thread(()->{ phone2.sms(); },"A").start(); new Thread(()->{ phone2.sms(); },"B").start(); } } class Phone2{ Lock lock = new ReentrantLock(); public void sms(){ try { lock.lock(); System.out.println(Thread.currentThread().getName()+"==>sms"); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } call(); //这里也是一把锁 } public void call(){ try { lock.lock(); System.out.println(Thread.currentThread().getName()+"==>call"); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }自旋锁
1、spinlock
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
2、自定义自旋锁
基于CAS 实现
package com.neihan.lock; import java.util.concurrent.atomic.AtomicReference; public class SpinlockDemo { AtomicReferenceatomicReference = new AtomicReference(); //加锁 public void myLock(){ Thread thread = Thread.currentThread(); System.out.println(thread.getName()+" myLock "); //自旋锁 while (!atomicReference.compareAndSet(null,thread)){ } } public void myUnlock(){ Thread thread = Thread.currentThread(); System.out.println(thread.getName()+"===>myUnlock"); atomicReference.compareAndSet(thread,null); } }
测试类
package com.neihan.lock; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; public class TestSpinLock { public static void main(String[] args) { SpinlockDemo spinlockDemo = new SpinlockDemo(); new Thread(()->{ try { spinlockDemo.myLock(); TimeUnit.SECONDS.sleep(10); } catch (Exception e) { e.printStackTrace(); } finally { spinlockDemo.myUnlock(); } },"A").start(); new Thread(()->{ try { spinlockDemo.myLock(); TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } finally { spinlockDemo.myUnlock(); } },"B").start(); } }死锁
package com.neihan.lock; import java.util.concurrent.TimeUnit; public class Deadlock { public static void main(String[] args) { String lockA = "lockA"; String lockB = "lockB"; new Thread(new MyThread(lockA,lockB),"T1").start(); new Thread(new MyThread(lockB,lockA),"T2").start(); } } class MyThread implements Runnable{ private String lockA; private String lockB; public MyThread(String lockA, String lockB) { this.lockA = lockA; this.lockB = lockB; } @Override public void run() { synchronized (lockA){ System.out.println(Thread.currentThread().getName()+"lock:"+lockA+"=>get "+lockB); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB){ System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===> get"+lockA); } } } }
如何解开死锁
命令:jps -l
1、使用jps定位进程号,jdk的bin目录下: 有一个jps
2、使用jstack 进程进程号 找到死锁信息
一般情况信息在最后:
while (!atomicReference.compareAndSet(null,thread)){
} } public void myUnlock(){ Thread thread = Thread.currentThread(); System.out.println(thread.getName()+"===>myUnlock"); atomicReference.compareAndSet(thread,null); }
}
**测试类** ```java package com.neihan.lock; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; public class TestSpinLock { public static void main(String[] args) { SpinlockDemo spinlockDemo = new SpinlockDemo(); new Thread(()->{ try { spinlockDemo.myLock(); TimeUnit.SECONDS.sleep(10); } catch (Exception e) { e.printStackTrace(); } finally { spinlockDemo.myUnlock(); } },"A").start(); new Thread(()->{ try { spinlockDemo.myLock(); TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } finally { spinlockDemo.myUnlock(); } },"B").start(); } }死锁
package com.neihan.lock; import java.util.concurrent.TimeUnit; public class Deadlock { public static void main(String[] args) { String lockA = "lockA"; String lockB = "lockB"; new Thread(new MyThread(lockA,lockB),"T1").start(); new Thread(new MyThread(lockB,lockA),"T2").start(); } } class MyThread implements Runnable{ private String lockA; private String lockB; public MyThread(String lockA, String lockB) { this.lockA = lockA; this.lockB = lockB; } @Override public void run() { synchronized (lockA){ System.out.println(Thread.currentThread().getName()+"lock:"+lockA+"=>get "+lockB); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB){ System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===> get"+lockA); } } } }
如何解开死锁
命令:jps -l
1、使用jps定位进程号,jdk的bin目录下: 有一个jps
2、使用jstack 进程进程号 找到死锁信息
一般情况信息在最后:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)