3.7. Lambda表达式
-
为什么要使用lambda表达式
- 避免匿名内部类定义过多;
- 可以让代码看起来更简洁;
- 去掉一堆没有意义的代码,只留下核心逻辑
-
属于函数式编程的概念,格式
- (params) -> expression [表达式]
- (params) -> statement [语句]
- (params) -> {statements}
-
函数式接口
- 任何接口,只有唯一一个抽象方法,就是一个函数式接口
- 对于函数式接口,可以通过Lambda表达式来创建该接口的对象
-
测试
TestLambda.java package com.hzs.basic.multithread; /** * @author Cherist Huan * @version 1.0 */ public class TestLambda { // 2、静态内部类 static class Like2 implements Ilike{ @Override public void lambda() { System.out.println("Like-->2"); } } public static void main(String[] args) { Ilike like = null; // 1、外部实现类1 like = new Like(); like.lambda(); // 2、静态内部类 like = new Like2(); like.lambda(); //3、局部内部类 class Like3 implements Ilike{ @Override public void lambda() { System.out.println("Like-->3"); } } like = new Like3(); like.lambda(); //4、匿名内部类,没有类的名称,必须借助接口或父类 like = new Ilike() { @Override public void lambda() { System.out.println("Like-->4"); } }; like.lambda(); //5、用Lambda表达式 like = ()->{ System.out.println("Like-->5"); }; like.lambda(); } } // 1、定义一个函数式接口 interface Ilike{ void lambda(); } // 2、外部实现类1 class Like implements Ilike{ @Override public void lambda() { System.out.println("Like-->1"); } }
-
a
3.8. 线程的状态(5种)
线程工包括5中状态:
-
新建状态(New):线程对象被创建后,就进入了新建状态。例如:,Thread thread = new Thread().
-
就绪状态(Runnable):也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如:thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
-
运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
-
阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
阻塞的情况分3种:
- 等待阻塞:通过调用线程的wait()方法,让线程等待某工作的完成;
- 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程所占用),它会进入同步阻塞状态;
- 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
-
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
3.9.线程同步
-
同步方法
-
synchronized关键字,它包括synchronized方法和synchronized块
同步方法: public synchronized void method(int args){}
-
synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,指导该方法返回是释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
-
缺陷:若将一个大的方法声明为synchronized将会影响效率
-
-
同步块
-
同步块:synchronized (Obj){}
-
Obj称之为同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class(反射);
-
同步监视器的执行过程:
- 1.第一个线程访问,锁定同步监视器,执行其中代码;
- 2.第二个线程访问,发现同步监视器被锁定,无法访问;
- 3.第一个线程访问完毕,解锁同步监视器;
- 4.第二个线程访问,发现同步监视器没有锁,然后锁定并访问。
-
锁Obj例子
UnsafeList.java package com.hzs.basic.multithread; import java.util.ArrayList; import java.util.List; /** * @author Cherist Huan * @version 1.0 */ public class UnsafeList { public static void main(String[] args) { List<String> list = new ArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ synchronized (list){ list.add(Thread.currentThread().getName()); } }).start(); } // 获取线程数目 boolean flag = true; while(flag){ if(Thread.activeCount() <= 2) { flag = false; System.out.println(list.size()); } } } }
-
3.10.死锁
产生死锁的4个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用;
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺;
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
避免死锁,只要破坏上面至少一个条件即可。
3.11.Lock锁
-
格式
class A{ private final ReentrantLock lock = new ReentrantLock(); public void method(){ lock.lock(); try{ //保证线程安全的代码 }finally{ lock.unlock(); } } }
-
例子
package com.hzs.basic.multithread; import java.util.concurrent.locks.ReentrantLock; /** * @author Cherist Huan * @version 1.0 */ public class TestLock { public static void main(String[] args) { Ticket ticket = new Ticket(); new Thread(ticket).start(); new Thread(ticket).start(); new Thread(ticket).start(); } } class Ticket implements Runnable{ private int ticketNum = 10; private final ReentrantLock reentrantLock = new ReentrantLock(); @Override public void run() { while(true) { // 加锁 reentrantLock.lock(); try { if(ticketNum > 0) { Thread.sleep(100); System.out.println(ticketNum--); }else{ break; } } catch (InterruptedException e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } } } }
-
synchronized与Lock的对比
-
Lock是显示锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放;
-
Lock只有代码块锁,synchronized有代码块锁和方法锁;
-
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(有很多子类,例如:可重入锁 ReentrantLock);
-
优先使用顺序:
Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(方法体之外)
-
3.12.线程通信
- Java提供了几个方法解决线程之间的通信问题
- wait():表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁;
- wait(long timeout):指定等待的毫秒数;
- notify():唤醒一个处于等待状态的线程;
- notifyAll():唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度。
- 注意:这些均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException
2. 等待唤醒机制:这是多个线程间的一种协作机制。
-
谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。
-
在一个线程满足某个条件时,就进入等待状态(wait() / wait(time)), 等待其他线程执行完他们的指定代码过后再将其唤醒(notify());
-
或可以指定wait的时间,等时间到了自动唤醒;
-
在有多个线程进行等待时,如果需要,可以使用 notifyAll()来唤醒所有的等待线程。wait/notify 就是线程间的一种协作机制。
- 解决方式
- 方式一:并发协作模型“生产者/消费者模式”-------->管程法,采用数据缓冲区
- 方式二:并发协作模型“生产者/消费者模式”-------->信号灯法
- 为什么要处理线程间的通信
-
当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些通信机制,可以协调它们的工作,以此实现多线程共同操作一份数据。(在同步的基础之上解决通信的问题)
-
比如:线程A用来生产包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,此时B线程必须等到A线程完成后才能执行,那么线程A与线程B之间就需要线程通信,即—— 等待唤醒机制。
3.13.线程池
-
例子
package com.hzs.basic.multithread; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @author Cherist Huan * @version 1.0 * @note 线程池的使用 */ public class TestThreadPool { public static void main(String[] args) { // 1、创建 ExecutorService executorService = Executors.newFixedThreadPool(10); // 2、执行 executorService.execute(new MyThread()); executorService.execute(new MyThread()); executorService.execute(new MyThread()); // 3、关闭 executorService.shutdown(); } } class MyThread implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } } 输出: pool-1-thread-2 pool-1-thread-1 pool-1-thread-3
2、线程池工作原理图
3、线程池执行流程
- 判断核心线程数
- 判断任务能否加入到任务队列
- 判断最大线程数量
- 根据线程池的饱和策略除了任务(是否丢弃任务)