1.多线程
1.1线程简介
-
任务:
-
程序:程序是指令和数据的有序集合,本身无运行含义,是静态概念。
-
进程(Process):执行程序的一次全部过程,是动态概念。系统资源分配的单位。
-
线程(Thread):一个进程中至少有一个线程,否则进程无效。是CPU调度和执行的单位。独立执行路径。每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
-
多线程:一个进程开辟了多个线程其运行有调度器CPU安排,调度器与操作系统相关,对一份资源操作时,会存在资源抢夺的问题,需要并发控制。线程会带来额外的开销,如CPU调度问题,需要加入并发控制;
-
多线程是模拟出来的,真正多线程是有多个CPU(核心)。一个CPU只能模拟多线程,在同一时间点,CPU只能执行一个线程代码,CPU在多线程中切换较快,才有同时执行的感觉
-
java主线程main为主线程是系统入口(用户线程),用于执行整个程序和GC回收线程(JVM提供,守护线程)
-
计算机用纳秒为单位。
-
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
1.2 线程实现:线程开启不一定执行,由CPU调度执行
- Thread class 继承Thread 类 Thread 实现了Runnable 接口
- Runnable 接口实现
- Callable接口实现
1.2.1 方式一
- 自定义线程类继承Thread类;
- 重写run()方法,编写执行体;
- 创建自定义线程类对象,调用start()方法启动线程。
public class CalcThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("run方法执行!"+i);
}
}
//main方法主线程
public static void main(String[] args) {
CalcThread calcThread = new CalcThread();
//calcThread.run(); //先执行run方法里的方法体,后执行main方法体
calcThread.start(); //同时执行run()和main()方法体
for (int i = 0; i < 1000; i++) {
System.out.println("main方法执行"+i);
}
}
}
1.2.2 方式二:推荐,单继承性,方便同一个对象被多个线程使用
- 定义MyRunnable 类实现Runnable接口
- 重写run()方法,编写执行体;
- 创建自定义线程类对象,作为参数传给new Thread(X)对象,调用start()方法启动线程。
ThreadDemo02 threadDemo02 = new ThreadDemo02();
//静态代理
new Thread(threadDemo02).start();
//龟兔赛跑
public class Race implements Runnable{
//胜利者
private static String winner;
//设置赛道
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
//模拟兔子睡觉
if("兔子".equals(Thread.currentThread().getName()) && (i%10==0)){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//是否比赛结束
boolean flag = gameOver(i);
//比赛结束了
if (flag) {
break;
}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"米");
}
}
//游戏结束
public static boolean gameOver(int i){
if (winner != null) {
return true;
}else {
if (i >= 100) {
winner = Thread.currentThread().getName();
System.out.println("龟兔赛跑胜利者是:"+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"乌龟").start();
new Thread(race,"兔子").start();
}
}
1.2.3 方式三:实现Callable接口 java.util.concurrent.Callable并发编程
- 实现Callable接口,需要返回值类型:class CallableDemo implements Callable
- 重写call方法。需抛出异常
- 创建目标对象:CallableDemo a = new CallableDemo();
- 创建执行服务;ExecutorService ser = Executors.newFixedThreadPool(n);
- 提交执行:Future res1 = ser.submit(a);
- 获取结果:Boolean r1 = res1.get();
- 关闭服务:ser.shutdown();
public class CallableDemo implements Callable<Boolean> {
private String url;
private String name;
public CallableDemo(String url,String name) {
this.url = url;
this.name = name;
}
//调用call方法执行后结果有异常
@Override
public Boolean call() {
downUtil(url,name);
System.out.println(("下载的文件名为:" + name));
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableDemo a = new CallableDemo("https://profile.csdnimg.cn/1/4/0/1_weixin_42045639", "a.jpg");
CallableDemo b = new CallableDemo("https://dss2.bdstatic.com/5bVYsj_p_tVS5dKfpU_Y_D3/res/r/image/2022-3-15/93.jpg", "b.jpg");
CallableDemo c = new CallableDemo("https://dgss0.baidu.com/6ONWsjip0QIZ8tyhnq/it/u=348777446,169159264&fm=30&app=106&f=JPEG?w=312&h=208", "c.jpg");
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> res1 = ser.submit(a);
Future<Boolean> res2 = ser.submit(b);
Future<Boolean> res3 = ser.submit(c);
//获取结果call执行后结果有异常
Boolean r1 = res1.get();
Boolean r2 = res2.get();
Boolean r3 = res3.get();
//关闭服务
ser.shutdown();
}
public static void downUtil(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO下载异常");
}
}
}
public class TestCallableDemo2 {
public static void main(String[] args) {
FutureTask<String> futureTask = new FutureTask<String>(new CallableDemo2());
new Thread(futureTask).start();
try {
String s = futureTask.get();
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class CallableDemo2 implements Callable<String>{
@Override
public String call() throws Exception {
return "callable调用使用";
}
}
1.2.4 静态代理:
- 真实对象和代理对象都要实现同一个接口
- 代理对象代理真实对象
- 代理对象可以做很多真实对象做不到的事
- 真实对象专注与做自己的事务,不用做太多的事,(自己调方法,每个都写一遍)
public class StaticProxy {
public static void main(String[] args) {
new Toys(new You()).palying(); //run
//对比线程实现
new Thread(()-> System.out.println("Runnable接口的实现类的Lambda表达式")).start();
}
}
interface Play{
//
void palying();
}
//真实对象
class You implements Play{
@Override
public void palying() {
System.out.println("本人在玩耍!");
}
}
//代理对象,代理你玩
class Toys implements Play{
private You target;
public Toys(You target) {
this.target = target;
}
@Override
public void palying() {
before();
this.target.palying();
after();
}
static void before(){
System.out.println("初始化!");
}
static void after(){
System.out.println("资源清理!");
}
}
1.3 线程状态:五大状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fx2VQo93-1649344639512)(E:\zkNote\2.语言学习\java\线程状态.png)]
- 创建状态:new Thread
- 就绪状态:start()
- 运行状态:线程真正执行代码
- 阻塞状态:sleep,wait或同步锁定,线程进入阻塞状态,阻塞事件解除后,重新进入就绪状态,等待CPU调度执行。
- 死亡状态:线程中断或结束,不能再次启动
1.3.1 常用方法
void setPriority(int newPriority) //更改线程优先级
static void sleep(long millis)
void join() //等待线程终止
static void yield() //线程礼让
void interrupt() //中断这个线程
boolean isAlive() //测试这个线程是否还活着
1.3.2 停止线程
- 不推荐使用JDK提供的stop()/destory()方法,已废弃
- 让线程字节停下来
- 使用标志位进行终止变量,flag=false,线程终止。
/**
* 1.建议线程正常停止--->利用次数,不建议使用循环,使用循环要要延时
* 2.建议使用标志位--->如下
* 3.不适用stop和destory等过时或jdk不建议使用的方法
*/
public class TestStop implements Runnable{
private boolean flag = true;
@Override
public void run() {
int i=0;
while (flag){
System.out.println("线程运行次数:"+i++);
}
}
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 100; i++) {
System.out.println("外部运行环境:"+i);
if (i == 80) {
testStop.stop();
System.out.println("线程终止条件触发");
}
}
}
}
1.3.3 线程休眠
- sleep(时间) 指定当前线程阻塞的毫秒数 1s=1000
- 如果任何线程中断当前线程。 当抛出此异常时,当前线程的中断状态将被清除
- 时间结束后线程进入就绪状态
- 模拟网络延时(放大问题的发生性,抢票例子,存在线程不安全),倒计时
- 每个对象有个锁,sleep不会释放
//倒计时,并打印当前系统时间
public class TestTime {
public static void main(String[] args) throws InterruptedException {
/*try {
todo();
} catch (InterruptedException e) {
e.printStackTrace();
}*/
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
while (true){
System.out.println(sdf.format(date));
Thread.sleep(1000);
date = new Date(System.currentTimeMillis());
}
}
public static void todo() throws InterruptedException {
int num = 10;
while (true){
System.out.println(num--);
Thread.sleep(1000);
}
}
}
1.3.4 线程礼让yield
- 让当前运行的线程暂停,但不阻塞;Thread.yield()
- 让线程从运行转为就绪状态,重新和另一线程竞争;
- CPU重新调度,礼让不一定success.
1.3.5 线程插队Join
- Join合并线程,待此插进来的线程执行完成后,再执行原来的其他线程,其他线程阻塞。
//join合并实际为插队
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("贵宾:"+i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 200; i++) {
if(i == 100){
thread.join(); //插队
}
System.out.println("主线程:"+ i);
}
}
}
1.3.6 线程状态Thread.State
-
new 尚未启动的线程
-
Runnable在虚拟机中执行的线程出于此状态
-
blocked被阻塞等待监视器锁定的线程出于此状态
-
waiting等待另一个线程执行特定动作的线程出于此状态
-
Timed_Waiting等待另一个线程执行动作达到指定等待时间的线程出于此状态
-
Terminated已退出的线程处于此状态,不能在启动。
-
一个线程可以在给定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("============结束=======");
});
Thread.State state = thread.getState();
System.out.println(state);
thread.start();
state = thread.getState();
System.out.println(state);
while (state != Thread.State.TERMINATED) {
Thread.sleep(200);
state = thread.getState();
System.out.println(state);
}
}
}
1.3.7 线程优先级:范围1~10。先设置优先级t.setPriority()在启动线程t.start()。
- java提供线程调度器来监控程序启动后进入就绪状态的线程,调度器按优先级来调度那个线程执行,但实际是有cpu决定的。优先级高的权重就大。
- Thread.MIN_PRIORITY = 1
- Thread.NORM_PRIORITY = 5
- Thread.MAX_PRIORITY = 10
- 性能倒置,先执行优先级低的,高的等待的情况,一半优先级高的也是起作用的。t.getPriority()
1.3.8 守护daemon线程
- 线程分为用户线程和守护线程
- jvm必须确保用户线程执行完毕,但不用等待守护线程执行完毕
- 守护线程:后台记录操作日志,垃圾回收,监控内存。
void setDaemon(boolean on) 将此线程标记为daemon线程或用户线程,默认为false表示用户线程,正常的线程都是用户线程,除非加此方法设置
boolean isDaemon() 是否是daemon线程
public class TestDaemon {
public static void main(String[] args) {
DaemonThread daemonThread = new DaemonThread();
Thread thread = new Thread(daemonThread);
thread.setDaemon(true);
thread.start();
UserThread userThread = new UserThread();
new Thread(userThread).start();
}
}
class DaemonThread implements Runnable{
@Override
public void run() {
while (true) {
System.out.println("守护线程");
}
}
}
class UserThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 90; i++) {
System.out.println("User Thread Running");
}
System.out.println("User Thread Dead!");
}
}
1.4 线程同步:多线程操作同一资源,并发,需要排队,队列
- 多线程访问同一对象,对对象进行CURD这时候就需要同步。线程同步等待机制,将多个同时访问的对象的线程进入对象的等待池线程池等待前面的线程使用完,下一个线程在使用。
- 形成条件:队列和锁:解决线程安全性。
- 牺牲性能获得安全,线程带来额外开销,cpu调度时间和并发控制开销。
1.4.1 同进程的多个线程使用同一块存储空间,方便的同时也带来访问冲突,为保证数据的正确性,在访问中加入锁机制synchronized进行并发控制,一个线程获得对象的排它锁,不释放前其他线程必须等待。
- 某线程持有锁会导致其他需要此锁的线程挂起
- 多线程竞争时,锁,释放会导致上下文切换和调度延时,存在性能问题。
- 优先级高的等待低的线程释放锁,导致优先级导致,引起性能问题。
- 多线程访问值时将值拷贝取到自己的工作内存中,内存控制不当出现负数问题。
1.4.2 同步方法:synchronized悲观锁
- 用private保证数据对象只被方法访问到,只需要对方法提出一套机制,即synchronized,包括synchronized方法和synchronized块:队列+锁
- synchronized作为修饰符作用在方法名上
- synchronized方法控制对象的访问,每个对象有一把锁,每个synchronized必须获得其锁才能执行,否则线程就会阻塞,方法一旦执行,就会独占锁,直至方法返回释放锁,后面的线程才会获得这个锁继续执行。
- 将大方法声明为synchronized会影响效率。
- 方法里面有修改内容才需要锁,锁太多,浪费资源-synchronized块
1.4.3 同步块
- synchronized(obj){} ,obj为同步监视器
- obj可以是任对象,推荐使用共享资源为同步监视器
- 同步方法无需指定obj,其同步监视器是this对象本身,或者是class(反射)
- 读锁时不能写,只能读,叫共享锁;写锁其他不能写,排它锁。
1.4.4 同步监器
- 线程1访问,锁定同步监视器,执行代码
- 线程2访问,同步监视器被锁定,无法访问
- 线程1访问完毕释放同步监视器
- 线程2访问,发现监视器被被释放,锁定执行代码
1.4.5 乐观锁(Optimistic Lock)乐观锁不能解决脏读的问题。乐观锁不是数据库自带的,自己去实现
- 每次获取数据的时候,都不会担心数据会被修改,所以每次获取数据时都不会进行加锁。
- 但是在更新数据的时候,需要判断该数据是否被别人修改过,如果数据被其他线程修改过,则不进行数据更新。
- 如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。
1.4.6 悲观锁(Pessimistic Lock)
- 每次获取数据的时候,都会担心数据会被修改,所以每次获取数据的时候都会进行加锁,确保在自己使用的过程中数据不被别人修改,使用完后进行数据解锁。由于数据会进行加锁,期间对该数据进行读写和其他线程都会进行等待。
- 悲观锁涉及到的另外两个锁概念就出来了,它们就是共享锁与排它锁。共享锁和排它锁是悲观锁的不同的实现,它俩都属于悲观锁的范畴。
1.4.7 适用场合
- 乐观锁:比较适合读取操作比较频繁的场合。
- 如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。
- 悲观锁:比较适合写入操作比较频繁的场合。
- 如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
- 总结:两种锁各有各的优点,读取频繁使用乐观锁,写入频繁使用悲观锁。
1.4.8 JUC-java util concurrent java并发编程包 同Callable
-
//线程安全的list CopyOnWriteArrayList<String> cowaList = new CopyOnWriteArrayList<>();
-
//线程不安全的list List<String> arrlist = new ArrayList<>();
1.4.9 List接口下面有两个实现,一个是ArrayList,另外一个是vector。从源码的角度来看,因为Vector的方法前加了,synchronized 关键字,这个ArrayList比线程安全的Vector效率高。
- 使用synchronized关键字
- 使用Collections.synchronizedList();
List<Map<String,Object>>data=new ArrayList<Map<String,Object>>();
了解决这个线程安全问题你可以这么使用Collections.synchronizedList(),如:
List<Map<String,Object>> data=Collections.synchronizedList(newArrayList<Map<String,Object>>());
(为了避免线程安全,以上采取的方法,特别是第二种,其实是非常损耗性能的)。
1.4.10 死锁
- 多线程相互占用对方所需的共享资源的,导致互相等待僵持的情况,某一同步块具有“两个以上对象锁”时就会发生“死锁的问题”。
- 条件1:互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞是,对已获得的资源不释放
- 不剥夺条件:进程获得资源,未使用完前,不能强行剥夺。
- 循环等待条件:若干进程之间形成头尾衔接的循环等待资源关系。
public class TestLockDemo {
public static void main(String[] args) {
new GetLock(0,"张三").start();
new GetLock(1,"李四").start();
}
}
class A {}
class B {}
class GetLock extends Thread{
static A a = new A();
static B b = new B();
private int num;
private String name;
public GetLock(int num,String name) {
this.num = num;
this.name = name;
}
@Override
public void run() {
try {
getLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void getLock() throws InterruptedException {
if(num == 0){
synchronized (a){ //程序执行完锁释放
System.out.println(name+"拿到了锁a");
Thread.sleep(1000);
}
synchronized (b){
System.out.println(name+"拿到了锁b");
}
}else {
synchronized (b){
System.out.println(name+"拿到了锁b");
Thread.sleep(2000);
}
synchronized (a){
System.out.println(name+"拿到了锁a");
}
}
}
}
1.4.11 Lock(锁)
- jdk 5.0开始提供线程同步机制-通过显式定义同步锁对象Lock实现同步。
- java.util.concurrent.locks.Lock;接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只有一个Thread对Lock对象加锁,现场开始访问共享资源前要获得Lock对象
- class ReentrantLock(可重用锁) implements Lock具有和synchronized(隐式锁,锁谁不清楚)相同的并发性和内存语义,实现线程安全控制中,常用此类,显式加锁,释放锁。
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (flag){
//这块睡眠即为让别的线程获得对象锁,即为并发
try {
Thread.sleep(1000);
lock.lock(); //加锁
buyTicket();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock(); //释放锁
}
}
}
1.4.12 Lock(锁)和synchronized对比
- Lock显式锁(手动开启和关闭锁),synchronized隐士锁,出作用域自动释放
- Lock只有代码块锁(补充)
- Lock锁。jvm会用较少的时间来调度线程,性能好。可以扩展,提供其他子类
- 优先使用顺序:Lock>同步代码块(已经进入方法体,分配了资源)>同步方法
1.5 线程通信问题-协同
1.5.1 生产者消费者问题
- 仓库只能放一件产品,生产者生产出来的产品放到仓库,消费者将仓库中产品取走消费。
- 若仓库中没有产品,则生产者将产品放到仓库,否则停止生产并等待,直至仓库中产品被消费者取走为止
- 若仓库中有产品,则消费者可以将产品取走,否则停止消费并等待,直至仓库中产品被再次放入为止
- 两线程共享同一个资源,并且相互依赖,互为条件。
- 生产者生产前通知消费者等待,生产后通知消费
- 消费者消费后通知生产者已经结束消费,需生产新产品让消费
- 仅有synchronized不够,其阻止并发更新同一共享资源,实现了同步,但不能实现线程间通信(消息传递)。
1.5.2 解决方式
- java提供的几个方法解决,都是Object类的方法,只能定义在同步方法或同步代码块中,否则会抛出IllegalMonitorStateException
public final void wait() //一直等待,直到其他线程通知,与sleep不同会释放锁
public final native void wait(long timeoutMillis)
public final native void notify() //唤醒一个处于等待的线程
public final native void notifyAll() //唤醒同一个对象上调用wait()处于等待的线程,优先级高的先调度
1.5.3 解决方式1:并发协作模型:管程法
- 生产者:生产数据模块(方法,对象,线程,进程)
- 消费者:处理数据模块(方法,对象,线程,进程)
- 缓冲区:消费者不能直接使用生产者的数据,之间有“缓存区”
public class TestContainer {
public static void main(String[] args) {
Container container = new Container();
new P(container).start();
new C(container).start();
}
}
class P extends Thread {
Container container;
public P(Container container){
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new S(i));
System.out.println("制造了"+i+"个S");
}
}
}
class C extends Thread{
Container container;
public C(Container container){
this.container = container;
}
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println("消耗了"+container.pop().id+"个S");
}
}
}
class S {
int id;
public S(int id) {
this.id = id;
}
}
class Container {
static S[] sarr = new S[10];
int count = 0;
//制造
public synchronized void push(S s){
if(count == sarr.length){
//容器满,.通知P等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//往容器中加S
sarr[count] = s;
count++;
this.notifyAll();
}
//消耗
public synchronized S pop(){
if(0 == sarr.length){
//容器满,通知C等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
S si = sarr[count];
this.notifyAll();
return si;
}
}
1.5.4 解决方式2:并发协作模型:信号灯法
-
标志位:
public class TestFlag { public static void main(String[] args) { CC cc = new CC(); new AA(cc).start(); new BB(cc).start(); } } //AA制作 class AA extends Thread { CC cc; public AA(CC cc){ this.cc = cc; } @Override public void run() { for (int i = 0; i < 30; i++) { if(i%2==0){ cc.push("蝙蝠侠!"); }else { cc.push("小丑!"); } } } } //BB消耗 class BB extends Thread { CC cc; public BB(CC cc){ this.cc = cc; } @Override public void run() { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i < 30; i++) { cc.pop(); } } } //CC产出 class CC { String film; boolean flag = true; //AA生产 false:BB消耗 public synchronized void push(String film){ if (!flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.film = film; System.out.println("AA制作了"+film); this.notifyAll(); this.flag = !this.flag; } public synchronized void pop(){ if (flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("BB消耗了"+film); this.notifyAll(); this.flag = !this.flag; } }
1.6 高级主题
1.6.1 线程池
- 线程经常创建和销毁,使用量特别大的资源,高并发时,对性能影响大。
- 提前创建多个线程放入线程池,使用时获取,用完放回池中。
- 提高响应速度,减少创建线程时间
- 降低资源消耗,重复利用池中线程
- 便于线程管理;池子大小、线程存活时间、最大线程数
1.6.2 使用
- jdk 1.5提供ExecutorService(线程池接口)和Executors(线程池创建工厂)
- ExecutorService子类如:ThreadPoolExecutor
- Executors:工具类。线程池创建工厂,创建并返回不同类型的线程池。
- public void execute(Runnable command) //执行任务无返回值,用来执行Runnable
- Future submit(Callable task) //执行任务有返回值,用来执行Callable
- void shutdown()
1.8 lambda表达式
- 避免匿名内部类过多:外部类(只用一次)->内部类->匿名内部类
- 实质是函数式编程
- 让代码简介,去掉冗余代码,保留核心逻辑
(params)->expression{表达式}
(params)->statement{表达式}
(params)->{statements}
1.8.1 函数式接口(Functional Interface)是java8核心;
- 概念:任何接口值包含唯一一个抽象方法,就是函数式接口
- 对于函数式接口,可以通过lambda表达式来创建该接口的对象
//推导过程
public class TestLambda {
public static void main(String[] args) {
LambdaDemo lambdaI = new LambdaI();
lambdaI.show();
lambdaI = new LambdaI2();
lambdaI.show();
//局部内部类
class LambdaI3 implements LambdaDemo{
@Override
public void show() {
System.out.println("Lambda推导3");
}
}
lambdaI = new LambdaI3();
lambdaI.show();
//匿名内部类,通过接口实现
lambdaI = new LambdaDemo() {
@Override
public void show() {
System.out.println("Lambda推导4");
}
};
lambdaI.show();
//Lambda方式
lambdaI = ()->{
System.out.println("Lambda推导5");
};
lambdaI.show();
}
//静态内部类
static class LambdaI2 implements LambdaDemo{
@Override
public void show() {
System.out.println("Lambda推导2");
}
}
}
interface LambdaDemo{
abstract void show();
}
//外部类
class LambdaI implements LambdaDemo{
@Override
public void show() {
System.out.println("Lambda推导1");
}
}
-
Lambda方式后简化
-
简化掉参数类型:多个参数都去时,要加小括号
-
简化掉参数小括号
-
简化掉方法大括号:Lambda方法只有一行代码