赞
踩
hi,大家好,此篇笔记是作者通过观看狂神说JUC并且自己查阅一些资料编辑完成的JUC学习笔记,大家可以观看目录查找自己想要了解的问题,会不定时更新补充,欢迎大家阅读收藏!!
java真的可以开启线程吗?不可以
public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } //native 本地方法,调用底层的c++来操作硬件。java无法直接操作硬件 private native void start0();
//获取cpu的核数
System.out.println(Runtime.getRuntime().availableProcessors());
并发编程的本质:充分利用CPU的资源
public enum State { //创建态 NEW, //运行态 RUNNABLE, //阻塞态 BLOCKED, //等待态,会一直等下去 WAITING, //超时等待,等一段时间 TIMED_WAITING, //终止态 TERMINATED; }
1、来自不同的类
wait => Object
sleep => Thread
2、关于锁的释放
wait 会释放锁
sleep 不会释放
3、使用的范围是不同的
wait 必须在同步代码块中使用
sleep 可以在任何地方使用
//基本的卖盘例子 /* * 公司开发中,降低耦合性 * 线程就是一个单独的资源类,没有任何附属的操作!!! * 1、属性、方法 * */ public class SaleTicketDemo01 { public static void main(String[] args) { Ticket ticket=new Ticket(); new Thread(()->{ for (int i=0;i<60;i++){ ticket.sale(); } },"A").start(); new Thread(()->{ for (int i=0;i<60;i++) { ticket.sale(); } },"B").start(); new Thread(()->{ for (int i=0;i<60;i++) { ticket.sale(); } },"C").start(); } } //资源类 oop编程 class Ticket{ //属性 方法 private int num=50; //synchronized 本质:队列,锁 public synchronized void sale(){ if(num>0){ System.out.println(Thread.currentThread().getName()+"卖出了第"+num--+"张票,剩余:"+num+"张票"); System.out.println(); } } }
是一个接口,具有三个实现类
ReentrantLock 可重入锁(最常用的)
ReadLock 读锁
WriteLock 写锁
ReentrantLock
public ReentrantLock() {
//如果无参就为非公平锁
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
//如果为true,则为公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁:先来先服务
非公平锁:可以抢占资源(默认)
public class SaleTicketDemo02 { public static void main(String[] args) { Ticket1 ticket=new Ticket1(); new Thread(()->{ for (int i=0;i<60;i++){ ticket.sale(); } },"A").start(); new Thread(()->{ for (int i=0;i<60;i++) { ticket.sale(); } },"B").start(); new Thread(()->{ for (int i=0;i<60;i++) { ticket.sale(); } },"C").start(); } } //资源类 oop编程 class Ticket1{ private int num=50; Lock lock = new ReentrantLock(true); public void sale(){ try{ lock.lock(); if(num>0){ System.out.println(Thread.currentThread().getName()+"卖出了第"+num--+"张票,剩余:"+num+"张票"); System.out.println(); } }finally { lock.unlock(); } } }
1、Synchronized 内置的 java 关键字,Lock锁是一个类
2、Synchronized 无法判断获取锁的状态,Lock 锁可以判断是否获取到了锁
3、Synchronized 会自动释放锁,lock 必须要手动释放锁!!如果不释放锁,会造成死锁问题
4、Synchronized 线程1(获得锁)的话,线程2会一直等待,如果线程1(阻塞),线程2还是会一直等待;lock锁就不一定会等待下去
lock.tryLock();
5、Synchronized 可重入锁,不可中断,非公平的;lock,可重入锁,可以判断锁,可以自己设置公平锁。
Lock lock = new ReentrantLock(true);//公平锁
Lock lock = new ReentrantLock();//非公平锁
6、Synchronized 适合锁少量的代码同步问题,lock 锁适合锁大量的同步代码。
public class A { public static void main(String[] args) { Data data=new Data(); new Thread(()->{ for (int i=0;i<10;i++){ try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"生产者").start(); new Thread(()->{ for (int i=0;i<10;i++){ try { data.reduce(); } catch (InterruptedException e) { e.printStackTrace(); } } },"消费者1").start(); new Thread(()->{ for (int i=0;i<10;i++){ try { data.reduce(); } catch (InterruptedException e) { e.printStackTrace(); } } },"消费者2").start(); new Thread(()->{ for (int i=0;i<10;i++){ try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"生产者2").start(); } } class Data{ private int number=0; public synchronized void increment() throws InterruptedException { if(number != 0){ this.wait(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); this.notifyAll(); } public synchronized void reduce() throws InterruptedException { if(number == 0){ this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); this.notifyAll(); } }
上述代码A、B、C、D四个线程,会产生虚假唤醒
为了解决这个问题,我们需要将if判断改为while
class Data{ private int number=0; public synchronized void increment() throws InterruptedException { while (number != 0){ this.wait(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); this.notifyAll(); } public synchronized void reduce() throws InterruptedException { while (number == 0){ this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); this.notifyAll(); } }
1、synchronized与lock 对线程阻塞与唤醒的不同
synchronized:
wait()//阻塞当前资源
notify()//随机唤醒一个资源
notifyAll()//唤醒所有资源
Lock:
await()//阻塞当前资源
signal()//唤醒当前资源
signalAll()//唤醒所有资源
public class B { public static void main(String[] args) { Data1 data=new Data1(); new Thread(()->{ for (int i=0;i<10;i++){ try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"生产者").start(); new Thread(()->{ for (int i=0;i<10;i++){ try { data.reduce(); } catch (InterruptedException e) { e.printStackTrace(); } } },"消费者1").start(); new Thread(()->{ for (int i=0;i<10;i++){ try { data.reduce(); } catch (InterruptedException e) { e.printStackTrace(); } } },"消费者2").start(); new Thread(()->{ for (int i=0;i<10;i++){ try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"生产者2").start(); } } class Data1{ private int number=0; Lock lock=new ReentrantLock(); Condition condition = lock.newCondition(); public void increment() throws InterruptedException { try { lock.lock(); while (number != 0){ condition.await(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); condition.signalAll(); }finally { lock.unlock(); } } public void reduce() throws InterruptedException { try { lock.lock(); while (number == 0){ condition.await(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); condition.signalAll(); }finally { lock.unlock(); } } }
上述代码执行出的结果是随机的,如果想要让进程进行有序的执行的话可以使用Condition 进行精准的通知和唤醒线程。
具体操作:
public class C { public static void main(String[] args) { //A 执行完调用B,B执行完调用C,C执行完调用A Data2 data=new Data2(); new Thread(()->{ for (int i=0;i<10;i++){ data.printA(); } },"A").start(); new Thread(()->{ for (int i=0;i<10;i++){ data.printB(); } },"B").start(); new Thread(()->{ for (int i=0;i<10;i++){ data.printC(); } },"C").start(); } } class Data2{ private int number=1; private Lock lock=new ReentrantLock(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); //业务:判断-> 执行-> 通知 public void printA(){ try { lock.lock(); while (number!=1){ //等待 try { condition1.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"=>AAAA"); //唤醒指定的人B number=2; condition2.signal(); }finally { lock.unlock(); } } public void printB(){ try { lock.lock(); while (number!=2){ try { condition2.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"=>BBBB"); number=3; condition3.signal(); }finally { lock.unlock(); } } public void printC(){ try { lock.lock(); while (number!=3){ try { condition3.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"=>CCCC"); number=1; condition1.signal(); }finally { lock.unlock(); } } }
运行结果:
如何判断锁的是谁!!知道什么是锁,锁的是谁。
通过以下问题了解到什么是锁。
public class A { /* 这个代码运行结果是 1、发短信 2、打电话 原因并不是因为因为 A先执行的,而是因为锁的存在 */ public static void main(String[] args) { Phone phone=new Phone();// 由于两个方法用的同一个锁,所以谁先拿到谁先执行 new Thread(()->{ phone.senSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1);//阻塞一秒钟 } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone.call(); },"B").start(); } } class Phone{ // synchronized 锁的对象是方法的调用者 public synchronized void senSms(){ System.out.println("发短信"); } public synchronized void call(){ System.out.println("打电话"); } }
public class A { /* 这个代码运行结果是 1、hello 2、发短信 原因:hello方法不是同步方法,不受锁的影响 */ public static void main(String[] args) { Phone phone=new Phone(); new Thread(()->{ phone.senSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone.hello(); },"B").start(); } } class Phone{ // synchronized 锁的对象是方法的调用者 public synchronized void senSms(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信"); } public synchronized void call(){ System.out.println("打电话"); } //没有锁,不是同步方法,不受锁的影响 public void hello(){ System.out.println("hello"); } }
public class A { /* 这个代码运行结果是 1、打电话 2、发短信 原因:由于两个方法用的不是同一个锁 */ public static void main(String[] args) { //两个对象,两个调用者,两把锁 Phone phone1=new Phone(); Phone phone2=new Phone(); new Thread(()->{ phone1.senSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone2.call(); },"B").start(); } } class Phone{ // synchronized 锁的对象是方法的调用者 public synchronized void senSms(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"发短信"); } public synchronized void call(){ System.out.println(Thread.currentThread().getName()+"打电话"); } //没有锁,不是同步方法,不受锁的影响 public void hello(){ System.out.println(Thread.currentThread().getName()+"hello"); } }
public class B { public static void main(String[] args) { //一个对象 Phone1 phone1=new Phone1(); new Thread(()->{ phone1.senSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone1.call(); },"B").start(); } } class Phone1{ //static 类一加载就有了,锁的是Class模板 //因为两个方法都被static修饰了,所以使用的是同一个锁 public static synchronized void senSms(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"发短信"); } public static synchronized void call(){ System.out.println(Thread.currentThread().getName()+"打电话"); } }
public class B { public static void main(String[] args) { //两个对象,两个调用者,两把锁, //因为是使用的static方法,所以使用的都是一个Class对象,所以先执行A,在B Phone1 phone1=new Phone1(); Phone1 phone2=new Phone1(); new Thread(()->{ phone1.senSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone2.call(); },"B").start(); } } class Phone1{ //static 类一加载就有了,锁的是Class模板 //因为两个方法都被static修饰了,所以使用的是同一个锁 public static synchronized void senSms(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"发短信"); } public static synchronized void call(){ System.out.println(Thread.currentThread().getName()+"打电话"); } }
public class C { public static void main(String[] args) { //此方法类似于创建两个对象,使用不同的锁,所以是b先运行,a最后 Phone2 phone1=new Phone2(); new Thread(()->{ phone1.senSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone1.call(); },"B").start(); } } class Phone2{ //static 类一加载就有了,锁的是Class模板 //一个静态synchronized锁,一个普通的synchronized锁 public static synchronized void senSms(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"发短信"); } public synchronized void call(){ System.out.println(Thread.currentThread().getName()+"打电话"); } }
public class C { public static void main(String[] args) { //因为是使用的static方法,所以使用的都是一个Class对象 //而因为call方法没有使用static,所以锁所属对象不是Class,所以B先执行 Phone2 phone1=new Phone2(); Phone2 phone2=new Phone2(); new Thread(()->{ phone1.senSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone2.call(); },"B").start(); } } class Phone2{ //static 类一加载就有了,锁的是Class模板 //一个静态synchronized锁,一个普通的synchronized锁 public static synchronized void senSms(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"发短信"); } public synchronized void call(){ System.out.println(Thread.currentThread().getName()+"打电话"); } }
public class C { public static void main(String[] args) { //因为是使用的static方法,所以使用的都是一个Class对象,所以先执行A,在B Phone2 phone1=new Phone2(); Phone3 phone2=new Phone3(); new Thread(()->{ phone1.call(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone2.call(); },"B").start(); } } class Phone2{ public synchronized void call(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"打电话"); } } class Phone3{ public static synchronized void call(){ System.out.println(Thread.currentThread().getName()+"打电话"); } }
new this 具体的一个类,也就是说你创建多个对象的话,每个对象的锁互不相关,对象在调用同步方法时,并不会阻塞其他对象调用其他方法。
static Class 唯一的一个模板,如果在同步方法上添加static字段,这个静态同步方法在被调用时,会自己通过反射调用一个Class模板,生成一个当前类static同步方法通用的锁。
public class List1 {
//java.util.ConcurrentModificationException 并发修改异常!!
//当我们直接使用ArrayList进行并发操作时,会产生并发修改异常。
public static void main(String[] args) {
//在并发下,ArrayList是不安全的
List<Object> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
为了解决 ArrayList 的线程不安全问题,我们有以下解决方法:
public class List1 { //java.util.ConcurrentModificationException 并发修改异常!! public static void main(String[] args) { //在并发下,ArrayList是不安全的 /* * 解决方案: * 1、使用Vector 去解决并发安全问题 * List<Object> list = new Vector<>(); * 2、通过使用Collections工具类,使ArrayList变的安全 * List<Object> list = Collections.synchronizedList(new ArrayList<>()); * 3、使用JUC包下的CopyOnWriteArrayList<>(); * List<Object> list = new CopyOnWriteArrayList<>(); * CopyOnWrite 写入时复刻 COW 计算机程序设计领域的一种优化 * 原理: * 多个线程调用的时候,list在读取的时候是固定的,在写入的时候会覆盖 * 而COW在写入的时候避免覆盖造成数据问题,在写入的时候复制一份, * 复制后给调用者,调用者写完之后再给数据放回去 * * 为什么不使用vector而使用CopyOnWriteArrayList? * 1、vector 使用的是synchronized方法,而使用synchronized方法效率很低 * 2、CopyOnWriteArrayList使用的是lock锁 * * */ // List<Object> list = new Vector<>(); // List<Object> list = new ArrayList<>(); // List<Object> list = Collections.synchronizedList(new ArrayList<>()); List<Object> list = new CopyOnWriteArrayList<>(); for (int i = 0; i < 10; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(list); },String.valueOf(i)).start(); } } }
public class Set1 {
//ConcurrentModificationException 并发修改异常!!
public static void main(String[] args) {
Set<String> set=new HashSet<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
为了使set变为安全的,我们可以使用以下方法:
public class Set1 { //ConcurrentModificationException 并发修改异常!! public static void main(String[] args) { // Set<String> set=new HashSet<>(); // Set<String> set= Collections.synchronizedSet(new HashSet<>()); // Set<String> set=new CopyOnWriteArraySet<>(); //方法原理与list相同 Set<String> set=new CopyOnWriteArraySet<>(); for (int i = 0; i < 10; i++) { new Thread(()->{ set.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(set); },String.valueOf(i)).start(); } } }
public class Map1 { //ConcurrentModificationException 并发修改异常!! public static void main(String[] args) { //map 是这样用吗? 不是,工作中不使用HashMap //默认等价于什么? // Map<Object, Object> map = new HashMap<>(16,0.75f);//加载因子,初始化容量 Map<Object, Object> map = new HashMap<>(); for (int i = 0; i < 10; i++) { new Thread(()->{ map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5)); System.out.println(map); },String.valueOf(i)).start(); } } }
为了使Map变的更安全,我们需要使用以下方法。
public class Map1 { //ConcurrentModificationException 并发修改异常!! public static void main(String[] args) { //map 是这样用吗? 不是,工作中不使用HashMap //默认等价于什么? // Map<Object, Object> map = new HashMap<>(16,0.75f);//加载因子,初始化容量 // Map<Object, Object> map = new HashMap<>(); // 方法: // Collections.synchronizedMap(new HashMap<>()); // new ConcurrentHashMap<>();//使用并发的hashmap Map<Object, Object> map = new ConcurrentHashMap<>(); for (int i = 0; i < 10; i++) { new Thread(()->{ map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5)); System.out.println(map); },String.valueOf(i)).start(); } } }
1、可以抛出异常
2、可以有返回值
3、方法不同,run()/call()
public class Callable1 { public static void main(String[] args) { // new Thread(new Runnable()).start(); // new Thread(new FutureTask<V>()).start(); // new Thread(new FutureTask<V>(Callable)).start(); MyTh myTh=new MyTh(); FutureTask futureTask=new FutureTask(myTh);//适配类 new Thread(futureTask,"A").start(); try { Integer o = (Integer) futureTask.get();//获取callable的call方法的返回值 //这个get方法会产生阻塞!!!,把他放到最后,或使用异步通信解决 System.out.println(o); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } class MyTh implements Callable<Integer>{ //这里继承什么属性,call方法的返回值就必须是什么属性 @Override public Integer call() throws Exception { System.out.println(Thread.currentThread().getName()+"call"); return 121; } }
如果创建多个多线程同时调用同一个callable
public class Callable1 { public static void main(String[] args) throws ExecutionException, InterruptedException { // new Thread(new Runnable()).start(); // new Thread(new FutureTask<V>()).start(); // new Thread(new FutureTask<V>(Callable)).start(); MyTh myTh=new MyTh(); FutureTask<Integer> futureTask=new FutureTask<>(myTh); FutureTask<Integer> task[] = new FutureTask[10]; for (int i = 0; i < 10; i++) { task[i]=new FutureTask<Integer>(myTh); new Thread(task[i],String.valueOf(i)).start(); } // new Thread(futureTask,"A").start(); // new Thread(futureTask,"B").start();//结果会被缓存提高效率 int num = 0; while(num<10){ try { System.out.println(task[num].get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } num++; } //这个get方法会产生阻塞!!!,把他放到最后 } } class MyTh implements Callable<Integer>{ //这里继承什么属性,call方法的返回值就必须是什么属性 @Override public Integer call() throws Exception { System.out.println(Thread.currentThread().getName()+"call"); return 121; } }
main线程里,有三个线程,三个线程独立,task.get() 只有等待对应线程完成后才会执行、所以最后可以打印出10个结果。而且,效率明显比串行执行10次循环效率高。
闭锁,减法计数器
public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { //总数是6,必须要去执行任务的时候,再使用 CountDownLatch countDownLatch=new CountDownLatch(6); for (int i = 1; i < 7; i++) { new Thread(()->{ System.out.println(Thread.currentThread().getName()+"go"); countDownLatch.countDown(); },String.valueOf(i)).start(); } countDownLatch.await();//等待计数器归零,然后再向下执行 // countDownLatch.countDown();//-1 System.out.println("关门"); } }
原理:
countDownLatch.countDown();//计数器数量-1
countDownLatch.await();//等待计数器归零,然后再执行下面的代码
每次有线程调用countDown();数量-1,假设计数器数量变为0,await();就会被唤醒,然后继续执行
java中关于线程的计数器,加法计数器
public class CyclicBarrierDemo { public static void main(String[] args) { CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{ System.out.println("成功实现"); }); for (int i = 1; i <= 7; i++) { final int temp=i; new Thread(()->{ System.out.println(Thread.currentThread().getName()+"完成第"+temp+"个线程"); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } },String.valueOf(i)).start(); } } }
上述CyclicBarrier需要产生出7个线程后才会执行其中runnable的方法,如果设置为8个,如果只运行了7个线程,则会一直陷入阻塞状态,等待线程达到8个才会运行runnable的方法。
信号量
public class SemaphoreDemo { 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); } catch (InterruptedException e) { e.printStackTrace(); }finally { semaphore.release();//释放资源 } // acquire() 得到 // release() 释放 },String.valueOf(i)).start(); } } }
原理:
semaphore.acquire();//拿到通行证/信号量,如果信号量已经被拿空了,则等待,直到释放其他线程信号量
semaphore.release();//释放通行证/信号量
作用:多个共享资源互斥的使用!并发限流,控制最大的线程数。保证服务器的安全与高可用
读可以被多线程同时读,写的时候只能有一个线程去写
/* * 独占锁(写锁) 一次只能被一个线程占有 * 共享锁(读锁) 多个线程可以同时占有 * */ public class ReadWriteLockDemo { public static void main(String[] args) { MyCache2 myCache = new MyCache2(); for (int i = 0; i < 5; i++) { final int temp=i; new Thread(()->{ myCache.put(temp+"",Thread.currentThread().getName()); },String.valueOf(i)).start(); } for (int i = 0; i < 5; i++) { final int temp=i; new Thread(()->{ myCache.get(temp+""); },String.valueOf(i)).start(); } } } //自定义缓存 加锁的 class MyCache2{ private volatile Map<String,Object> map=new HashMap<>(); private ReadWriteLock lock=new ReentrantReadWriteLock(); //存,在写入时,只希望同时只有一个线程可以写 public void put(String key,Object o){ lock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName()+"写入"+key); map.put(key,o); System.out.println(Thread.currentThread().getName()+"写入完毕"); } catch (Exception e){ e.printStackTrace(); } finally { lock.writeLock().unlock(); } } //取,在读取时,所有人都可以读 public void get(String key){ lock.readLock().lock(); try { System.out.println(Thread.currentThread().getName()+"读取"+key); map.get(key); System.out.println(Thread.currentThread().getName()+"读取完毕"); } catch (Exception exception) { exception.printStackTrace(); } finally { lock.readLock().unlock(); } } }
结果:在写入时,并没有任何插队,而在读取时,有插队现象
队列采用先进先出的方式存取数据
阻塞队列:BlockingQueue
阻塞队列与List、Set是同级关系
什么时候我们需要使用阻塞队列?
多线程并发处理,线程池!
学会使用队列
添加、移除
四组API
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时退出等待 |
---|---|---|---|---|
添加 | add() | off() | put() | off() |
移除 | remove() | poll() | take() | poll() |
检测队首元素 | element() | peek() | 空 | 空 |
方式一:抛出异常
public class BlockingQueueDemo { public static void main(String[] args) { /* Collection: List Set BlockingQueue */ test1(); } /* 抛出异常 */ public static void test1(){ //队列需要参数,参数是当前队列的大小 ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3); System.out.println(arrayBlockingQueue.add("a")); System.out.println(arrayBlockingQueue.add("b")); System.out.println(arrayBlockingQueue.add("c")); // 队列满的时候再添加会抛出异常: Queue full // System.out.println(arrayBlockingQueue.add("d")); //element():返回并查看队首的元素 System.out.println(arrayBlockingQueue.element()); //当队列为空时,再弹出队列内容,会报错: NoSuchElementException System.out.println(arrayBlockingQueue.remove()); System.out.println(arrayBlockingQueue.remove()); System.out.println(arrayBlockingQueue.remove()); // System.out.println(arrayBlockingQueue.remove()); //如果队列元素为空,则出现异常:NoSuchElementException System.out.println(arrayBlockingQueue.element()); } }
方式二:有返回值,不抛出异常
public class BlockingQueueDemo { public static void main(String[] args) { test2(); } public static void test2(){ //队列需要参数,参数是当前队列的大小 ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3); System.out.println(arrayBlockingQueue.offer("a")); System.out.println(arrayBlockingQueue.offer("b")); System.out.println(arrayBlockingQueue.offer("c")); //如果队列满的时候再添加,会返回false并且添加失败 // System.out.println(arrayBlockingQueue.offer("d")); //peek():返回并查看队首的元素 System.out.println(arrayBlockingQueue.peek()); //poll方法:空参为按队列从头部依次弹出 System.out.println(arrayBlockingQueue.poll()); System.out.println(arrayBlockingQueue.poll()); System.out.println(arrayBlockingQueue.poll()); //如果队列空的时候再取出返回null,不会报错 System.out.println(arrayBlockingQueue.poll()); //如果队列空的时候再查看队首返回null,不会报错 System.out.println(arrayBlockingQueue.peek()); } }
方式三:阻塞等待
/* * 等待,阻塞(一直等待) * */ public static void test3() throws InterruptedException { //队列需要参数,参数是当前队列的大小 ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3); arrayBlockingQueue.put("a"); arrayBlockingQueue.put("b"); arrayBlockingQueue.put("c"); //队列没有位置了,就会一直阻塞 // arrayBlockingQueue.put("d"); System.out.println(arrayBlockingQueue.take()); System.out.println(arrayBlockingQueue.take()); System.out.println(arrayBlockingQueue.take()); //队列空了,会一直阻塞,直到队列中有元素 System.out.println(arrayBlockingQueue.take()); }
方式四: 超时退出等待
/* * 等待,阻塞(等待超时) * */ public static void test4() throws InterruptedException { //队列需要参数,参数是当前队列的大小 ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3); System.out.println(arrayBlockingQueue.offer("a")); System.out.println(arrayBlockingQueue.offer("b")); System.out.println(arrayBlockingQueue.offer("c")); //阻塞2秒钟之后结束 System.out.println(arrayBlockingQueue.offer("d", 2, TimeUnit.SECONDS)); System.out.println(arrayBlockingQueue.poll()); System.out.println(arrayBlockingQueue.poll()); System.out.println(arrayBlockingQueue.poll()); //等待2秒后结束 System.out.println(arrayBlockingQueue.poll(2, TimeUnit.SECONDS)); }
同步队列没有容量
进去一个元素,必须等待取出来之后,才能再往里面放一个元素!!
public class SynchronizedQueueDemo { public static void main(String[] args) { BlockingQueue<String> blockingQueue = new SynchronousQueue<>();//同步队列 new Thread(()->{ try { System.out.println(Thread.currentThread().getName()+"put 1"); blockingQueue.put("1"); System.out.println(Thread.currentThread().getName()+"put 2"); blockingQueue.put("2"); System.out.println(Thread.currentThread().getName()+"put 3"); blockingQueue.put("3"); } catch (InterruptedException e) { e.printStackTrace(); } },"T1").start(); new Thread(()->{ try { TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName()+": "+blockingQueue.take()); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName()+": "+blockingQueue.take()); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName()+": "+blockingQueue.take()); } catch (InterruptedException e) { e.printStackTrace(); } },"T2").start(); } }
输出结果:
T1put 1
T2: 1
T1put 2
T2: 2
T1put 3
T2: 3
池化技术
程序的运行,本质:占用系统的资源!优化CPU资源的使用!
线程池、连接池、内存池、对象池… 创建、销毁线程池十分浪费资源
池化技术:事先准备好一些资源,有人要用,就来我这里拿,用完之后归还资源。
线程池的好处:
1、降低资源的消耗
2、提高响应的速度
3、方便管理
线程复用、可以控制最大并发数、管理线程
线程池具有:三大方法、7大参数、4种拒绝策略
Executors.newSingleThreadExecutor();//单个线程
Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小
Executors.newCachedThreadPool();//可伸缩的
//Executors 工具类,有三大方法 //使用线程池之后,要使用线程池来创建线程 public class Demo1 { 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 < 10; i++) { //使用线程池之后,要使用线程池来创建线程 threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+" OK"); }); } } finally { //线程池用完需要关闭线程池 threadPool.shutdown(); } } }
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } 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>()); } //本质:ThreadPoolExecutor() :7大参数 public ThreadPoolExecutor(int corePoolSize,//核心线程池大小 int maximumPoolSize,//最大线程池大小 long keepAliveTime,//存活的时间,超时了没有人调用就会释放 TimeUnit unit,//超时的单位 BlockingQueue<Runnable> workQueue,//阻塞队列 ThreadFactory threadFactory,//线程工程:创建线程的,一般不用动 RejectedExecutionHandler handler) {//拒绝策略 if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
银行实例演示线程池:
手动创建一个线程池
public class Demo1 { public static void main(String[] args) { ExecutorService threadPool =new ThreadPoolExecutor( 2,//核心线程池大小 5,//最大线程池大小 3,//存活的时间,超时了没有人调用就会释放 TimeUnit.SECONDS,//超时的单位 new LinkedBlockingDeque<>(3),//阻塞队列,3个队列 Executors.defaultThreadFactory(),//线程工程:创建线程的,一般不用动 new ThreadPoolExecutor.AbortPolicy()); //默认的拒绝策略: 队列满了,如果还有线程进入,则抛出异常 try { //最大承载Deque+max,如果超出最大承载,会报异常 for (int i = 0; i < 8; i++) { //使用线程池之后,要使用线程池来创建线程 threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+" OK"); }); } } finally { //线程池用完需要关闭线程池 threadPool.shutdown(); } } }
new ThreadPoolExecutor.AbortPolicy(): 默认的拒绝策略,队列满了,如果还有线程进入,则抛出异常
new ThreadPoolExecutor.CallerRunsPolicy():哪来的回哪去 会让main线程执行
new ThreadPoolExecutor.DiscardPolicy():队列满了,如果还有线程进入,会丢掉任务,不会抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy():队列满了,会尝试竞争第一个线程,看是否还在运行,如果失败了就没了,如果成功就执行,也不会抛出异常。
任务的分类:
池的最大的大小如何去设置
任务的分类:
1、CPU 密集型
既然名字里带有CPU了,说明其消耗的主要资源就是 CPU 了。
电脑是8核的,最多支持8条线程同时执行,所以我们在定义线程池参数时,定义maximumPoolSize时,我们有几核,最多也就能定义几个。
几核就选几这样效率最高
设置线程数时,针对单台机器,最好就是有几个 CPU ,就创建几个线程,然后每个线程都在执行这种任务,永不停歇。
Runtime.getRuntime().availableProcessors();//获取核数
2、IO 密集型
既然名字里带有IO了,说明其消耗的主要资源就是 IO 了。
判断你的程序中十分耗IO的线程的数量,我们需要设置的数量必须大于消耗资源的数量
我们所接触到的 IO ,大致可以分成两种:磁盘 IO和网络 IO。
磁盘 IO ,大多都是一些针对磁盘的读写操作,最常见的就是文件的读写,假如你的数据库、 Redis 也是在本地的话,那么这个也属于磁盘 IO。
网络 IO ,这个应该是大家更加熟悉的,我们会遇到各种网络请求,比如 http 请求、远程数据库读写、远程 Redis 读写等等。
IO 操作的特点就是需要等待,我们请求一些数据,由对方将数据写入缓冲区,在这段时间中,需要读取数据的线程根本无事可做,因此可以把 CPU 时间片让出去,直到缓冲区写满。
lambda表达式的使用举例:
/* Lambda表达式的使用 1.举例:(o1,o2) -> Integer.compare(o1,o2); 2.格式: -> : Lambda操作符 或 箭头操作符 ->左边:Lambda形参列表 (其实就是接口中的抽象方法中的形参列表) ->右边:Lambda体 (其实就是重写的抽象方法的方法体) 3.Lambda表达式的使用:(分为六种情况) 总结: ->左边:Lambda形参列表的参数类型可以省略(类型推断); 如果Lambda形参列表只有一个参数,其一对()也可以省略 ->右边:Lambda体应该使用一对{}包裹; 如果Lambda体只有一条执行语句(可能是return语句),可以省略这一对{}和 return关键字 4.Lambda表达式的本质:作为函数式接口的实现 5.如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口 接口上面声明注解@FunctionalInterface */ public void test1(){ //使用lambda表达式之前 Runnable r1=new Runnable() { @Override public void run() { System.out.println("我爱北京"); } }; r1.run(); System.out.println("**********************************"); //使用lambda表达式之后 Runnable r2=() -> {System.out.println("我爱故宫");} //使用lambda表达式之后 Runnable r2=() -> System.out.println("我爱故宫"); r2.run(); }
/* 方法引用的使用 1.使用情景:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用! 2.方法引用,本质上就是Lambd表达式,而Lambda表达式作为函数式接口的实例。 使用方法引用,也是函数式接口的实例 3.使用格式: 类(或对象) :: 方法名 4.具体分为如下三种情况: 对象 :: 非静态方法 类 :: 静态方法 类 :: 非静态方法 5.方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与 方法引用的方法的形参列表和返回值类型相同!(针对于情况1与情况2) */
情况一:对象::实例方法名
//情况一:对象::实例方法名 //Consumer中的void accept(T t) //PrintStream中的void println(T t) @Test public void test1(){ Consumer<String> con=(s)-> System.out.println(s); con.accept("北京"); System.out.println("*************************"); PrintStream ps = System.out; Consumer<String> con1=ps :: println; con1.accept("beijing"); } //Supplier中的T get() //Employee中的String getName() @Test public void test2(){ Supplier<String> sup=new Supplier<String>() { @Override public String get() { return null; } }; Employee emp=new Employee(1001,"Tom",23,5600); Supplier<String> sup1=() -> emp.getName(); String s = sup1.get(); System.out.println(s); Supplier<String> sup2= emp::getName; String s1 = sup2.get(); System.out.println(s1); }
情况二:类 :: 静态方法:
//情况二:类 :: 静态方法 //Comparator中的int compare(T t1,T t2) //Integer中的int compare(T t1,T t2) @Test public void test3(){ Comparator comparator=new Comparator() { @Override public int compare(Object o1, Object o2) { return 0; } }; Comparator<Integer> com1=(t1,t2) -> Integer.compare(t1,t2); System.out.println(com1.compare(12, 21)); Comparator<Integer> com2=Integer::compare; System.out.println(com2.compare(21, 2)); } //Function中的R apply(T t) //Math中的Long round(Double d) @Test public void test4(){ Function<Double,Long> function=new Function<Double, Long>() { @Override public Long apply(Double o) { return Math.round(o); } }; System.out.println(function.apply(12.1)); Function<Double,Long> f1=d ->Math.round(d); System.out.println(f1.apply(13.9)); Function<Double,Long> f2=Math::round; System.out.println(f2.apply(13.2)); }
情况三:类 :: 实例方法:
//情况三:类 :: 实例方法 //Comparator中的int compare(T t1,T t2) //String中的int t1.compareTo(t2) @Test public void test5(){ Comparator<String> comparator=new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } }; Comparator<String> com1=(s1,s2) -> s1.compareTo(s2); System.out.println(com1.compare("abc", "abd")); Comparator<String> com2=String::compareTo; System.out.println(com2.compare("acs", "sadasd")); } //BiPredicate中的boolean test(T t1,T t2); //String中的boolean t1.equals(t2) @Test public void test6(){ BiPredicate biPredicate=new BiPredicate() { @Override public boolean test(Object o, Object o2) { return o.equals(o2); } }; System.out.println(biPredicate.test("s", "s")); BiPredicate b1=(o1,o2)-> o1.equals(o2); System.out.println(b1.test("a", "c")); BiPredicate b2=Object::equals; System.out.println(b2.test("a", "a")); } //Functiono中的R apply(T t) //Employee中的String getName(); @Test public void test7(){ Employee employee=new Employee(123,"Tom",12,123); Function<Employee,String> function=new Function<Employee, String>() { @Override public String apply(Employee employee) { return employee.getName(); } }; Function<Employee,String> f1=s -> s.getName(); System.out.println(f1.apply(employee)); Function<Employee,String> f2=Employee::getName; System.out.println(f2.apply(employee)); }
我们需要掌握的技术:lambda表达式、链式编程、函数式接口、Stream流式计算
只有一个方法的接口被称为函数式接口@FunctionalInterface
//java具有超级多的@FunctionalInterface应用
//简化编程模型,在新版本的框架底层大量应用!!!
//例如:foreach(消费者类型的函数式接口为参数);
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
/*
java内置的4大核心函数式接口
消费型接口 Consumer<T> void accept(T t)
供给型接口 Supplier<T> T get()
函数型接口 Function<T,R> R apply(T t)
断定型接口 Predicate<T> boolean test(T t)
*/
@Test public void test1(){ happyTime(500, new Consumer<Double>() { @Override public void accept(Double aDouble) { System.out.println("学习太累了,去天上人间消费了"+aDouble); } }); happyTime(500, money -> System.out.println("学习太累了,去天上人间喝了口水消费了"+money)); } public void happyTime(double money, Consumer<Double> con){ con.accept(money); }
@Test public void test2(){ List<String> list= Arrays.asList("北京","天津","南京","河南"); List<String> list1 = filterString(list, new Predicate<String>() { @Override public boolean test(String s) { return s.contains("京"); } }); System.out.println(list1); List<String> list2 = filterString(list, s -> s.contains("京")); System.out.println(list2); } //根据给定的规则,过滤集合中的字符串。此规则由Predicate的方法决定 public List<String> filterString(List<String> list, Predicate<String> pre){ ArrayList<String> filterList=new ArrayList<>(); for(String s:list){ if(pre.test(s)){ filterList.add(s); } } return filterList; }
/* * Function 函数型接口,有一个输入参数,有一个输出 * 只要是 函数式接口 可以用lambda表达式简化 * */ public class Demo1 { public static void main(String[] args) { Function<String,Object> function1= new Function<String,Object>() { @Override public Object apply(String o) { return o; } }; System.out.println(function1.apply("hhh")); //使用lambda表达式简化 Function function2 = (str)->{ return str; }; //使用lambda表达式简化 Function function3 = str-> str; } }
/* * 断定型接口:有一个输入参数,返回值只能是 布尔值!! * 只要是 函数式接口 可以用lambda表达式简化 */ public class Demo2 { public static void main(String[] args) { Predicate<String> predicate = new Predicate<String>() { @Override public boolean test(String str) { return str.isEmpty(); } }; System.out.println(predicate.test("asd")); //使用lambda表达式简化 Predicate<String> predicate1 = (str)->{ return str.isEmpty(); }; //使用lambda表达式简化 Predicate<String> predicate2 = str-> str.isEmpty(); } }
/* * Consumer 消费型接口只有输入,没有返回值 * */ public class Demo3 { public static void main(String[] args) { Consumer<String> consumer1 = new Consumer<String>() { @Override public void accept(String str) { System.out.println(str); } }; consumer1.accept("hehe"); //使用lambda表达式简化 Consumer<String> consumer2 =(str)->{ System.out.println(str); }; //使用lambda表达式简化 Consumer<String> consumer3 =str-> System.out.println(str); } }
/* * 供给型接口 没有参数,只有返回值 * */ public class Demo4 { public static void main(String[] args) { Supplier<String> supplier1 = new Supplier<String>() { @Override public String get() { return "hhh"; } }; System.out.println(supplier1.get()); //使用lambda表达式 Supplier<String> supplier2 =()->{ return "hehe"; }; } }
/* 1.Stream关注的是对数据的运算,与CPU打交道 集合关注的是数据的存储,是与内存打交道的 2. stream自己不会存储元素。 stream不会改变源对象。相反,他们会返回一个持有结果的新Stream。 Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。 3.Stream 执行流程 ① Stream的实例化 ② 一系列操作(过滤,映射、...) ③ 终止操作 4.说明: 4.1 一个中间操作链,对数据源的数据进行处理 4.2 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用 */
stream使用案例
/* * 现在有五个用户!筛选: * 1、ID 必须是偶数 * 2、年龄必须大于23岁 * 3、用户名转为大写字母 * 4、用户名字母倒着排序 * 5、只输入一个用户 * */ public class Demo1 { 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(5,"e",25); User u6=new User(6,"f",26); //集合就是存储 List<User> users = Arrays.asList(u1, u2, u3, u4, u5,u6); //计算交给stream流 //使用技术:lambda表达式、链式编程、函数式接口、stream流式计算 users.stream().filter(user -> {return user.getId()%2==0;}) //返回由与此给定谓词匹配的此流的元素组成的流。 .filter(user-> user.getAge()>23) //map返回由给定函数应用于此流的元素的结果组成的流。 .map(user->{return user.getName().toUpperCase(Locale.ROOT);}) .sorted((user1,user2)->{return user2.compareTo(user1);}) //返回由此流的元素组成的流,截短长度不能超过 maxSize 。 .limit(1) .forEach(System.out::println); } }
@Test public void test1(){ List<Employee> list=EmployeeData.getEmployees(); // filter(Predicate p):接收Lambda ,从流中排除某些元素 Stream<Employee> stream = list.stream(); System.out.println(); //练习:查询员工表中薪资大于4000的员工信息 stream.filter(e -> e.getSalary() >4000).forEach(System.out::println); System.out.println(); // limit(long maxsize):截断流,使其元素不超过给定数量 list.stream().limit(3).forEach(System.out::println); System.out.println(); // skip(long n):跳过元素,返回—个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。 // 与limit(n)互补 System.out.println(); list.stream().skip(3).forEach(System.out::println); // distinct():筛选,通过流所生成元素的hashCode()和equals()去除重复元素 System.out.println(); list.add(new Employee(1010,"刘强东",40,8000)); list.add(new Employee(1010,"刘强东",40,8000)); list.add(new Employee(1010,"刘强东",40,8000)); list.stream().distinct().forEach(System.out::println); }
@Test public void test2(){ //map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 List<String> list = Arrays.asList("aa", "bb", "cc", "dd"); list.stream().map(str -> str.toUpperCase()).forEach(System.out::println); System.out.println(); //练习:获取员工姓名长度大于3的员工的姓名 List<Employee> employees = EmployeeData.getEmployees(); Stream<String> stringStream = employees.stream().map(Employee::getName); stringStream.filter(name->name.length()>3).forEach(System.out::println); System.out.println(); //练习2: Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest1::fromStringToStream); streamStream.forEach(s ->{ s.forEach(System.out::println); }); System.out.println(); //flatMap(Function f) //接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 Stream<Character> characterStream = list.stream().flatMap(StreamAPITest1::fromStringToStream); characterStream.forEach(System.out::println); } //将字符串中的多个字符构成的集合转换成为对应的stream的实例 public static Stream<Character> fromStringToStream(String str){ ArrayList<Character> list=new ArrayList<>(); for(Character c:str.toCharArray()){ list.add(c); } return list.stream(); }
//排序 @Test public void test4(){ //sorted()---自然排序 List<Integer> list = Arrays.asList(12, 13, 45, 6454, 21, 42); list.stream().sorted().forEach(System.out::println); //抛异常,原因:Employee没有实现Comparable接口 // List<Employee> employees = EmployeeData.getEmployees(); // employees.stream().sorted().forEach(System.out::println); //sorted(Comparatoe com)--定制排序 List<Employee> employees = EmployeeData.getEmployees(); employees.stream().sorted((e1,e2)-> { int value= Integer.compare(e1.getAge(),e2.getAge()); if(value!=0){ return value; }else { return Double.compare(e1.getSalary(),e2.getSalary()); } } ).forEach(System.out::println); }
//匹配与查找 @Test public void test1(){ List<Employee> employees = EmployeeData.getEmployees(); // allMatch(Predicate p)——检查是否匹配所有元素。 //练习:是否所有员工的年龄都大于18 boolean b = employees.stream().allMatch(e -> e.getAge() > 18); System.out.println(b); //anyMatch(Predicate P)——检查是否至少匹配一个元素。 // 练习:是否存在员工的工资大于6000 boolean b1 = employees.stream().anyMatch(e -> e.getSalary() > 6000); System.out.println(b1); //noneMatch(Predicate p)——检查是否没有匹配的元素。 // 练习:是否存在员工姓“叶” boolean b2 = employees.stream().noneMatch(e -> e.getName().startsWith("叶")); System.out.println(b2); //findFirst——返回第一个元素 Optional<Employee> first = employees.stream().findFirst(); System.out.println(first); //findAny——返回当前流中的任意元素 Optional<Employee> any = employees.parallelStream().findAny(); System.out.println(any); //count——返回流中元素的总个数 long count = employees.stream().filter(e -> e.getSalary()>5000).count(); System.out.println(count); } @Test public void test2(){ List<Employee> employees = EmployeeData.getEmployees(); //max(Comparator c)——返回流中的最大值 //练习:返回最高的工资 Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary()); Optional<Double> max = salaryStream.max(Double::compare); System.out.println(max); //min(Comparator c)——返回流中的最小值 //练习:返回最低的工资 Stream<Double> salaryStream1 = employees.stream().map(e -> e.getSalary()); Optional<Double> min = salaryStream1.min(Double::compare); System.out.println(min); Optional<Employee> min1 = employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())); System.out.println(min1); //forEach(Consumer c)——内部迭代 employees.stream().forEach(System.out::println); //使用集合的遍历操作 employees.forEach(System.out::println); }
//归约 @Test public void test3(){ //reduce(T iden, BinaryOperator b) //可以将流中元素反复结合起来,得到一个值。返回T //练习1:计算1-10的自然数的和 List<Integer> list= Arrays.asList(1,2,3,4,5,6,7,8); Integer reduce = list.stream().reduce(0, Integer::sum); System.out.println(reduce); //reduce(BinaryOperator b) //可以将流中元素反复结合起来,得到一个值。返回OptionalT> //练习2:计算公司所有员工工资的总和 List<Employee> employees = EmployeeData.getEmployees(); Stream<Double> salaryStream = employees.stream().map(Employee::getSalary); // Optional<Double> reduce1 = salaryStream.reduce(Double::sum); Optional<Double> reduce1 = salaryStream.reduce((d1,d2)->d1+d2); System.out.println(reduce1.get()); }
//收集 @Test public void test4(){ //collect(Collector c) //将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法 // Collector接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map)。 // 另外,Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表: //练习1:查找工资大于6000的员工,结果返回为一个List或Set List<Employee> employees = EmployeeData.getEmployees(); List<Employee> list = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList()); list.forEach(System.out::println); System.out.println(); List<Employee> employees1 = EmployeeData.getEmployees(); Set<Employee> collect = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet()); collect.forEach(System.out::println); }
Fork Join 在JDK1.7,并行执行任务!提高效率。大量数据!
大数据:Map Reduce (把大任务拆分成小任务)
Fork Join特点:工作窃取
在b线程执行完之后,会将a线程中的任务拿过来执行,提高工作效率!
public class Test1 { public static void main(String[] args) throws ExecutionException, InterruptedException { // test1();//Sum=500000000500000000 时间:5879 // test2();//Sum= 500000000500000000 时间:4190 test3();//Sum= 500000000500000000 时间:201 } //普通求和方法 public static void test1(){ 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)); } //使用ForkJoin调优 public static void test2() throws ExecutionException, InterruptedException { long start = System.currentTimeMillis(); ForkJoinPool forkJoinPool=new ForkJoinPool(); ForkJoinTask<Long> task = new ForkJoinDemo1(1L,10_0000_0000L); ForkJoinTask<Long> submit = forkJoinPool.submit(task);//提交任务 Long sum = submit.get(); long end = System.currentTimeMillis(); System.out.println("Sum= "+sum+" 时间:"+(end-start)); } //使用stream并行流,最推荐使用的 public static void test3(){ long start = System.currentTimeMillis(); long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum); long end = System.currentTimeMillis(); System.out.println("Sum= "+sum+" 时间:"+(end-start)); } }
ForkJoin锁创建的类
/* * 求和计算的任务 * * 数据量大的话可以使用ForkJoin/Stream并行流 * * 如何使用ForkJoin * 1、ForkJoin通过ForkJoinPool来执行 * 2、计算任务通过 ForkJoinPool.execute(ForkJoinTask<?> task) * 3、计算类要继承RecursiveTask<?> * * */ public class ForkJoinDemo1 extends RecursiveTask<Long> { private Long start; private Long end; //临界值 private Long temp=10000L; public ForkJoinDemo1(Long start, Long end) { this.start = start; this.end = end; } @Override protected Long compute() { Long sum=0L; if((end-start)<temp){ for (Long i = start; i <= end; i++) { sum+=i; } }else { //分支合并计算 Long middle = (end + start) / 2; ForkJoinDemo1 task1 = new ForkJoinDemo1(start, middle); task1.fork();//拆分任务,把任务压入队列 ForkJoinDemo1 task2 = new ForkJoinDemo1(middle+1,end); task2.fork(); sum = task1.join() + task2.join(); } return sum; } }
ForkJoin原理是通过递归不断调用自身进行计算,节省时间。
Future 设计的初衷:对将来的某个事件的结果进行建模
使用CompletableFuture来进行异步回调
没有返回值的runAsync异步回调
public class Demo1 { public static void main(String[] args) throws ExecutionException, InterruptedException { //因为类似于ajax,所以可以有返回值,也可以没有返回值 //没有返回值的话,泛型类就选Void //发起一个请求 CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); }); System.out.println("11"); completableFuture.get();//获取执行结果 //由于请求在执行时被阻塞,所以11先执行 } }
有返回值的异步回调
public class Demo1 { public static void main(String[] args) throws ExecutionException, InterruptedException { //有返回值的异步回调 CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{ System.out.println(Thread.currentThread().getName()+"completableFuture"); return 111; }); System.out.println(completableFuture.whenComplete((t, u) -> { System.out.println("T->" + t);//T 正常运行时的返回结果 System.out.println("U->" + u);//U 是一个错误的信息 //java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero }).exceptionally((e) -> { e.getMessage(); return 404; }).get()); } }
volatile是 java 虚拟机提供的轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排
什么是JMM?
JMM:java内存模型,不存在的东西,是一个概念,一种约定!!
关于JMM的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷会主存。
2、线程加锁前,必须读取主存中的最新值到工作内存中!
3、加锁和解锁,必须是同一把锁!!!
JMM规定了内存主要划分为主内存和工作内存两种。此处的主内存和工作内存跟JVM内存划分(堆、栈、方法区)是在不同的层次上进行的,如果非要对应起来,主内存对应的是Java堆中的对象实例部分,工作内存对应的是栈中的部分区域,从更底层的来说,主内存对应的是硬件的物理内存,工作内存对应的是寄存器和高速缓存。
JVM在设计时候考虑到,如果JAVA线程每次读取和写入变量都直接操作主内存,对性能影响比较大,所以每条线程拥有各自的工作内存,工作内存中的变量是主内存中的一份拷贝,线程对变量的读取和写入,直接在工作内存中操作,而不能直接去操作主内存中的变量。但是这样就会出现一个问题,当一个线程修改了自己工作内存中变量,对其他线程是不可见的,会导致线程不安全的问题。因为JMM制定了一套标准来保证开发者在编写多线程程序的时候,能够控制什么时候内存会被同步给其他线程。
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
JMM对这八种指令的使用,制定了如下规则:
JMM对这八种操作规则和对volatile的一些特殊规则就能确定哪里操作是线程安全,哪些操作是线程不安全的了。但是这些规则实在复杂,很难在实践中直接分析。所以一般我们也不会通过上述规则进行分析。更多的时候,使用java的happen-before规则来进行分析
问题:当我们在main线程修改过主内存中的值后,但是由于JMM规范,导致运行中的线程并不知道主线程的值已经被main线程修改,还会继续使用运行时的值,就会产生阻塞问题。
public class JMMDemo {
private static int num = 1;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{ //此线程对主内存的变化是不知道的
while (num==1){
}
}).start();
TimeUnit.SECONDS.sleep(1);
num=0;
System.out.println(num);
}
}
volatile是 java 虚拟机提供的轻量级的同步机制
public class VolatileDemo {
private volatile static int num = 1;
//不加volatile程序就会死循环
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (num==1){
}
}).start();
TimeUnit.SECONDS.sleep(1);
num=0;
System.out.println(num);
}
}
//不保证原子性 public class VDemo2 { private volatile static int num = 0; public static void add(){ num++; } //理论上,num结果应该为2w,实际结果达不到2w public static void main(String[] args) { for (int i = 1; i <= 20 ; i++) { new Thread(()->{ for (int j = 1; j <= 1000; j++) { add(); } },String.valueOf(i)).start(); } while (Thread.activeCount()>2){ Thread.yield(); } System.out.println(num); } }
如果不加 Lock锁 和 synchronized,怎么样保证原子性?
通过指令:javap -c 当前文件名.class 进行反编译查看底层代码
我们可以使用原子类解决原子性问题,非常高效,底层都和操作系统挂钩,直接再内存中修改值!!Unsafe类是一个很特殊的存在
//使用原子类解决原子性问题 public class VDemo2 { //原子类的int private volatile static AtomicInteger num = new AtomicInteger(0); public static void add(){ // num++; num.getAndIncrement();//+1 方法,使用的底层的CAS,效率极高 } //num结果为2w public static void main(String[] args) { for (int i = 1; i <= 20 ; i++) { new Thread(()->{ for (int j = 1; j <= 1000; j++) { add(); } },String.valueOf(i)).start(); } while (Thread.activeCount()>2){ Thread.yield(); } System.out.println(num); } }
什么是指令重排?
你写的程序,计算机并不是按照你写的那样去执行的。
源代码–>编译器优化的重排–>指令并行也可以会重排–>内存系统也会重排–>执行
int x = 1;//1
int y = 2;//2
x = x + 5;//3
y = x * x;//4
//我们所期望的执行步骤是 1、2、3、4、5
处理器在进行指令重排的时候,会考虑数据之间的依赖性!!!
volatile可以避免指令重排:
内存屏障。CPU指令。作用:
1、保证特定的操作的执行顺序!
2、可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)
上述存在线程不安全的问题,我们可以使用DCL懒汉式
public class SingleTest { Order order=Order.getInstance(); } class Order{ private Order() { } private static Order instance=null; public static Order getInstance(){ // 方式二:效率比较高 双重检测锁模式的 懒汉式单例 DCL懒汉式 if(instance==null){ synchronized (Order.class) { if(instance==null){ instance=new Order(); /** 1、分配内存空间 2、执行构造方法,初始化对象 3、把这个对象指向这个空间 我们期待的是123这样顺序执行, 但是有可能执行的是132 如果是132的话, 当a线程进入并且执行到3的时候,会将对象指向这个空间,突然来了一个b线程, b线程会发现instance!=null,这样就直接return b线程中的结果, 但是此时instance还未进行构造,会产生问题。 为了解决上述问题,我们必须在声明对象时,加上volatile属性。 */ } } } return instance; } }
但是DCL懒汉式还是存在着原子性问题。
使用volatile解决DCL懒汉式中存在的原子性问题
public class SingleTest { Order order=Order.getInstance(); } class Order{ private Order() { } private volatile static Order instance=null; public static Order getInstance(){ // 方式二:效率比较高 双重检测锁模式的 懒汉式单例 DCL懒汉式 if(instance==null){ synchronized (Order.class) { if(instance==null){ instance=new Order(); } } } return instance; } }
静态内部类实现懒汉式
//静态内部类实现
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER=new Holder();
}
}
但是我们会发现,我们可以通过反射来破坏这种单例模式,为了解决这种异常,我们可以通过以下方法
public class LazyMan { private LazyMan(){ synchronized (LazyMan.class){ if (lazyMan!=null){ throw new RuntimeException("不要试图使用反射破坏异常"); } } } private volatile static LazyMan lazyMan; public static LazyMan getInstance(){ if(lazyMan==null){ synchronized (LazyMan.class){ if(lazyMan==null){ lazyMan=new LazyMan(); } } } return lazyMan; } }
通过设置字段方式,防止反射破坏单例结构,但是还是不够,如果被获取到自定义的字段名,反射还能继续进行破坏
public class LazyMan { //红绿灯 private static boolean zhangsan = false; private LazyMan(){ //三重反射 synchronized (LazyMan.class){ if(zhangsan==false){ zhangsan=true; }else { throw new RuntimeException("不要试图使用反射破坏异常"); } } } private volatile static LazyMan lazyMan; public static LazyMan getInstance(){ if(lazyMan==null){ synchronized (LazyMan.class){ if(lazyMan==null){ lazyMan=new LazyMan(); } } } return lazyMan; } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // LazyMan instance = LazyMan.getInstance(); Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); LazyMan lazyMan = declaredConstructor.newInstance(); LazyMan lazyMan2 = declaredConstructor.newInstance(); System.out.println(lazyMan); System.out.println(lazyMan2); } }
为了防止反射破坏单例模式,我们最终只能使用枚举来组织反射。
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumSingle enumSingle = declaredConstructor.newInstance();
System.out.println(enumSingle);
}
}
我们通过探究源码发现,反射中说明枚举不可以通过反射进行创建对象
Cannot reflectively create enum objects
我们发现在通过idea或者javap进行反编译后中,enum也是一个继承了Enum类的java类,并且拥有空参构造器,但是实际使用时,却发现,空参构造器无法获取,于是我们使用jad进行反编译,发现实际使用的是继承Enum中父类的构造器参数,string与int,于是通过反射进行获取,发现无法获取对象。
单例模式感觉不怎么用到,实际的应用场景有哪些呢?以下,我将列出一些就在咱们周边和很有意义的单例应用场景。
Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
网站的计数器,一般也是采用单例模式实现,否则难以同步。
应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.
总结以上,不难看出:
单例模式应用的场景一般发现在以下条件下:
(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。
public class CasDemo1 { //CAS 就是这个缩写compareAndSet 比较并且交换当前工作内存中的值和主内存中的值, //如果这个值是期望的,那么则执行!如果不就一直循环 public static void main(String[] args) { AtomicInteger atomicInteger=new AtomicInteger(2021); //compareAndSet(int expect, int update) 期望 更新 //如果我期望的值达到了就更新,否则就不更新 CAS 是CPU的并发原语 System.out.println(atomicInteger.compareAndSet(2021, 2022)); System.out.println(atomicInteger.get()); atomicInteger.getAndIncrement(); System.out.println(atomicInteger.compareAndSet(2021, 2022)); System.out.println(atomicInteger.get()); } }
缺点:
1、循环会耗时间
2、一次只能保证一个共享变量的原子性
3、会产生ABA问题
java无法操作内存,但是java可以通过调用C++ 使用native字段,C++可以操作内存,使用unsafe就相当于java的后门,可以通过这个类操作内存。
public class CasDemo1 { //CAS 就是这个缩写compareAndSet 比较并且交换 public static void main(String[] args) { AtomicInteger atomicInteger=new AtomicInteger(2021); //对于我们我们平时写的sql来说,我们使用的是乐观锁 //compareAndSet(int expect, int update) 期望 更新 //如果我期望的值达到了就更新,否则就不更新 CAS 是CPU的并发原语 //===========捣乱的线程================ System.out.println(atomicInteger.compareAndSet(2021, 2022)); System.out.println(atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(2022, 2021)); System.out.println(atomicInteger.get()); //=============期望的线程================== System.out.println(atomicInteger.compareAndSet(2021, 2022)); System.out.println(atomicInteger.get()); } }
为了解决这个ABA问题,我们需要使用原子引用,与乐观锁类似
解决这个ABA问题,我们需要使用原子引用,与乐观锁类似
public class CasDemo1 { //CAS 就是这个缩写compareAndSet 比较并且交换 public static void main(String[] args) { // AtomicInteger atomicInteger=new AtomicInteger(2021); //对于我们我们平时写的sql来说,我们使用的是乐观锁 //AtomicStampedReference 注意,如果泛型是一个包装类,要注意对象的引用问题 AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1); //在正常业务操作中,这里面比较的都是一个个对象 new Thread(()->{ int stamp = atomicStampedReference.getStamp();//获得版本号 System.out.println("a1->"+stamp); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)); System.out.println("a2->"+atomicStampedReference.getStamp()); System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)); System.out.println("a3->"+atomicStampedReference.getStamp()); },"a").start(); new Thread(()->{ int stamp = atomicStampedReference.getStamp();//获得版本号 System.out.println("b1->"+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("b2->"+atomicStampedReference.getStamp()); },"b").start(); } }
结果:
a1->1
b1->1
true
a2->2
true
a3->3
true
b2->4
Integer使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间;
Lock lock1 = new ReentrantLock();//非公平锁
Lock lock2 = new ReentrantLock(true);//公平锁
公平锁:顾名思义非常公平,采用先来先服务策略,
//如果参数为true,则为公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
非公平锁:可以允许插队执行,类似短作业优先策略
//非公平锁:
public ReentrantLock() {
sync = new NonfairSync();
}
可重入锁(递归锁)
synchronized版本的可重用锁
public class Lock2 { 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()+": 发信息"); call();//这里会获得当前的锁,也就是可重用锁 } public synchronized void call(){ System.out.println(Thread.currentThread().getName()+": 打电话"); } }
Lock版本的可重用锁:具有细节问题
public class Lock3 { public static void main(String[] args) { MobilePhone mobilePhone=new MobilePhone(); new Thread(()->{ mobilePhone.sms(); },"A").start(); new Thread(()->{ mobilePhone.sms(); },"B").start(); } } class MobilePhone{ Lock lock =new ReentrantLock(); public void sms(){ lock.lock(); //锁必须配对,否则容易死锁,必须 死锁 配对 开锁 try { System.out.println(Thread.currentThread().getName()+": 发信息"); call();//这里会获得当前的锁,也就是可重用锁 }finally { lock.unlock(); } } public void call(){ lock.lock();//在这里又锁了一次,实际两把锁,而synchronized只有一把锁 try { System.out.println(Thread.currentThread().getName()+": 打电话"); }finally { lock.unlock(); } } }
SpinLock
底层的标准的自旋锁
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
//自旋锁
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
他会不断的尝试,直到成功位置
自定义自旋锁
//自旋锁 public class Lock4 { //int 默认为0 //Thread 默认为null AtomicReference<Thread> atomicReference=new AtomicReference<>(); //加锁 public void myLock(){ Thread thread=Thread.currentThread(); System.out.println(Thread.currentThread().getName()+"==> mylock"); //自旋锁 while (!atomicReference.compareAndSet(null,thread)){ } } //解锁 public void myUnlock(){ Thread thread=Thread.currentThread(); System.out.println(Thread.currentThread().getName()+"==> myUnlock"); atomicReference.compareAndSet(thread,null); } }
测试:
public class LockTest { public static void main(String[] args) throws InterruptedException { // ReentrantLock reentrantLock = new ReentrantLock(); // reentrantLock.lock(); // reentrantLock.unlock(); //底层使用的自旋锁CAS Lock4 lock=new Lock4(); new Thread(()->{ lock.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (Exception a) { a.getMessage(); } finally { lock.myUnlock(); } },"T1").start(); TimeUnit.SECONDS.sleep(1); new Thread(()->{ lock.myLock(); try { TimeUnit.SECONDS.sleep(1); } catch (Exception a) { a.getMessage(); } finally { lock.myUnlock(); } },"T2").start(); } }
t2会等t1解锁后,才能解锁
public class DeathDemo { public static void main(String[] args) { String locka="LockA"; String lockb="LockB"; new Thread(new A(locka,lockb),"T1").start(); new Thread(new A(lockb,locka),"T2").start(); } } class A implements Runnable{ private String lockA; private String lockB; public A(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(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB){ System.out.println(Thread.currentThread().getName()+"lock"+lockB+"get->"+lockA); } } } } } } }}
死锁测试,怎么排除死锁?
1、使用 jps -l 定位进程号
2、使用 jstack 进程号 找到死锁问题
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。