Java多线程

简介

程序:是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。

进程:执行程序的一次执行过程,它是一个动态的概念,是系统资源分配的单位。

线程:通常在一个进程中可以包含若干个进程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。

多线程:多条执行路径,主线程和子线程并行交替执行。很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。

  • 线程就是独立的执行路径
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程(负责垃圾回收)
  • main()称之为主线程,为系统的入口,用于执行整个程序
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统密切相关的,先后顺序是不能人为干预的
  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制(如抢票问题,需要让线程排队)
  • 线程会带来额外的开销,如CPU调度时间,并发控制开销
  • 每个线程在自己的工作内存交互,内存控制不会造成数据不一致

线程

线程创建

方法一: Thread class -> 继承Thread类

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程

    不建议使用:避免OOP单继承局限性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 线程的创建方式一:继承Thread类,重写run()方法,调用start开启线程
// 总结:线程开启不一定立即执行,由CPU调度器执行
public class Test15 extends Thread {

@Override
public void run() {
for(int i = 0; i < 200; i++) {
System.out.println("看书" + i);
}
}

public static void main(String[] args) {
// 创建一个线程对象
Test15 test15 = new Test15();
test15.start();

for(int i = 0; i < 200; i++) {
System.out.println("刷剧" + i);
}
}
}

方法二: Runnable接口 -> 实现Runnable接口

  • 定义MyRunnable类实现Runnable接口
  • 实现run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程

    推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 创建线程方式2:实现Runnable接口,重写run()方法,执行线程需要丢入runnable接口实现类,调用start()方法。
public class Test16 implements Runnable {

@Override
public void run() {
for(int i = 0; i < 200; i++) {
System.out.println("看书" + i);
}
}

public static void main(String[] args) {
// 创建Runnable接口的实现类对象
Test16 test16 = new Test16();

// 创建线程对象,通过线程对象来开启线程,代理
//Thread t = new Thread(test16);
//t.start();

// 等价于上一种写法
new Thread(test16).start();

for(int i = 0; i < 200; i++) {
System.out.println("追剧" + i);
}
}
}

方法三: Callable接口 -> 实现Callable接口

  • 实现Callable接口,需要返回值类型
  • 重写call方法,需要抛出异常
  • 创建目标对象
  • 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
  • 提交执行:Futureresult1 = ser.submit(t1);
  • 获取结果:boolean r1 = result1.get();
  • 关闭服务:ser.shutdownNow();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 线程创建方式三:实现Callable接口
/*
callable的好处:
1. 可以定义返回值
2. 可以抛出异常
*/
public class Test17 implements Callable<Boolean> {
private String url;
private String name;

public Test17(String url, String name) {
this.url = url;
this.name = name;
}

@Override
public Boolean call() throws Exception {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("下载图片名为:" + name);
return true;
}

public static void main(String[] args) throws ExecutionException, InterruptedException {
Test17 t1 = new Test17("http://kmlerc.coding-pages.com/images/head.JPG", "1.jpg");
Test17 t2 = new Test17("https://www5.jiumodiary.com/images/front/eleps.png", "2.png");
Test17 t3 = new Test17("https://pic.cnblogs.com/avatar/1569506/20190731001716.png", "3.png");

// 创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(1);

// 提交执行
Future<Boolean> result1 = ser.submit(t1);
Future<Boolean> result2 = ser.submit(t2);
Future<Boolean> result3 = ser.submit(t3);

// 获取结果
boolean r1 = result1.get();
boolean r2 = result2.get();
boolean r3 = result3.get();

System.out.println(r1 + " " + r2 + " " + r3);

// 关闭服务
ser.shutdownNow();
}
}

// 下载器
class WebDownloader {
// 下载方法
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题");
}
}
}

线程状态分析

  1. Thread t = new Thread() 线程对象一旦创建就进入到新生状态
  2. 当调用start()方法,线程立即进入就绪状态,但不意味着立即调度执行
  3. 进入运行状态,线程才真正执行线程体的代码块
  4. 当调用sleep,wait或同步锁定时,线程进入阻塞状态,就是代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待cpu调度执行
  5. 线程中断或者结束,一旦进入死亡状态,就不能再次启动

线程方法

方法 说明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 在指定毫秒数内让当前正在执行的线程休眠
void join() 等待该线程终止
static void yield() 暂停当前正在执行的线程对象,并执行线程其他线程
void interrupt() 中断线程,别用这个方式
boolean isAlive() 测试线程是否处于活动状态

线程停止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 线程停止
/*
1. 建议让线程正常停止
2. 建议使用标志
3. 不要使用stop或destroy等过时的不推荐使用的方法
*/
public class Test20 implements Runnable {
private boolean flag = true;

@Override
public void run() {
int i = 0;
while(flag) {
System.out.println("run...Thread" + ++i);
}
}

public void stop() {
this.flag = false;
}

public static void main(String[] args) {
Test20 test20 = new Test20();

new Thread(test20).start();

for(int i = 0; i < 1000; i++) {
System.out.println("main" + i);
if(i == 999){
// 修改标志位,让线程停止
test20.stop();
System.out.println("线程停止了");
}
}
}
}

线程休眠

  • sleep(时间)指定当前线程阻塞的毫秒数
  • sleep存在异常InterruptedException
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,slepp不会释放锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 线程睡眠:打印时间
public class Test21 {
public static void getTime() throws InterruptedException {
Date date = new Date(System.currentTimeMillis());

while(true) {
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
Thread.sleep(1000);
date = new Date(System.currentTimeMillis());
}
}

public static void main(String[] args) throws InterruptedException {
getTime();
}
}

线程礼让

  • 让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让cpu重新调度,礼让不一定成功!看cpu心情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 线程礼让
public class Test22 {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield, "a").start();
new Thread(myYield, "b").start();
}
}

class MyYield implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始运行");
Thread.yield(); // 礼让
System.out.println(Thread.currentThread().getName() + "停止运行");
}
}

线程强制执行(插队)

  • Join合并线程,待此线程完成后,再执行其他线程,其他线程阻塞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 线程强制执行
public class Test23 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("vip线程" + i);
}
}

public static void main(String[] args) throws InterruptedException {
Test23 test23 = new Test23();
Thread thread = new Thread(test23);
thread.start();

// 主线程
for(int i = 0; i < 500; i++) {
if(i == 200) {
thread.join();
}
System.out.println("main" + i);
}
}
}

线程状态观测

Thread.State: 线程状态

  • NEW 尚未启动的线程处于此状态
  • RUNNABLE 在Java虚拟机中执行的线程处于此状态
  • BLOCKED 被阻塞等待监视器锁定的处于此状态
  • WAITING 正在等待另一个线程执行特定动作的线程处于此状态
  • TIMED_WAITING 正在等待另一个线程执行达到指定等待时间的线程处于此状态
  • TERMINATED 已退出的线程处于此状态

一个线程可以在给定时间点处于一个状态。这些状态是不反应任何操作系统线程状态的虚拟机状态。

线程的优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度那个线程来执行
  • 线程的优先级用数字表示,范围从1-10
    • Thread.MIN_PRIORITY = 1
    • Thread.MAX_PRIORITY = 10
    • Thread.NORM_PRIORITY = 5
  • 使用 getPriority() 获取优先级
  • 使用 setPriority(int x) 改变优先级

守护(daemon)线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不必等待守护线程执行完毕
  • 如:后台记录操作日志,监控内存,垃圾回收等等…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 守护线程
public class Test24 {
public static void main(String[] args) {
God god = new God();
People people = new People();

Thread thread = new Thread(god);
thread.setDaemon(true); // 设置为守护线程
thread.start();

new Thread(people).start();

// 当people结束,守护线程也就自动结束了。。
}
}

// 上帝线程
class God implements Runnable {
@Override
public void run() {
while(true) {
System.out.println("上帝保佑人类");
}
}
}

// 人类线程
class People implements Runnable {
@Override
public void run() {
for(int i = 0; i < 30000; i++) {
System.out.println("人类开心的活着");
}
System.out.println("Goodbye, world!");
}
}

线程同步(多个线程操作同一个资源)

处理多线程问题时,多个线程访问同一个对象(并发问题),并且某些线程还想修改这个对象,这时候就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起
  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

同步方法及同步块

由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块。

  • 同步方法:public synchronized void method(int args){}

synchronized方法控制对”对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞。方法一旦执行,就独占该锁,直到方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。

  • 缺陷:若将一个大的方法声明为synchronized将会影响效率

同步块:synchronized(Obj) {}
Obj称之为同步监视器

  • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class

同步监视器的执行过程

  1. 第一个线程访问,锁定同步监视器,执行其中代码
  2. 第二个线程访问,发现同步监视器被锁定,无法访问
  3. 第一个线程访问完毕,解锁同步监视器
  4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 买票测试synchronized方法
public class Test25 {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket, "小米").start();
new Thread(buyTicket, "小明").start();
new Thread(buyTicket, "小红").start();
}
}

class BuyTicket implements Runnable {
private int ticketNums = 100;
boolean flag = true;

@Override
public void run() {
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

// 同步方法
private synchronized void buy() throws InterruptedException {
if(ticketNums <= 0) {
flag = false;
return ;
}
//Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "抢到" + ticketNums--);
}

// 同步块
class BuyTicket1 implements Runnable {
private int ticketNums = 100000;
boolean flag = true;

@Override
public void run() {
while (flag)
buy();
}

private void buy() {
synchronized (this) { // 同步块,可以锁任何对象,默认为this
if(ticketNums <= 0) {
flag = false;
return ;
}
//Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "抢到" + ticketNums--);
}
}
}
}

死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生”死锁“的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 死锁:多个线程互相抱着对方需要的线程,形成僵持
public class Test26 {
public static void main(String[] args) {
Makeup q1 = new Makeup(0, "小明");
Makeup q2 = new Makeup(1, "小红");
q1.start();
q2.start();
}
}

class Gun {}
class Dog {}

class Makeup extends Thread {
// 需要的资源只有一份,用static来保证只有一份
static Gun gun = new Gun();
static Dog dog = new Dog();

int choice;
String name;

Makeup(int choice, String name) {
this.choice = choice;
this.name = name;
}

@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

private void makeup() throws InterruptedException {
if (choice == 0) { // 小明想要枪,他本来有狗
synchronized(gun) {
System.out.println(this.name + "获得枪的锁");
Thread.sleep(1000);
/* 发生死锁
synchronized(dog) {
System.out.println(this.name + "获得狗的锁");
}
*/
}
synchronized(dog) { // 放到外面即可解决死锁问题
System.out.println(this.name + "获得狗的锁");
}
} else if (choice == 1) { // 小红想要狗,她本来有枪
synchronized(dog) {
System.out.println(this.name + "获得狗的锁");
Thread.sleep(2000);
/* 发生死锁
synchronized(gun) {
System.out.println(this.name + "获得枪的锁");
}
*/
}
synchronized(gun) { // 放到外面即可解决死锁问题
System.out.println(this.name + "获得枪的锁");
}
}
}
}

死锁产生的四个必要条件(只需要破解其中一个即可解决死锁问题):

  1. 互斥条件:一个资源每次只能被一个进程使用
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
  3. 不剥夺条件:进程以获得的资源,在未使用完之前,不能强行剥夺
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

Lock锁

  • Lock是显式锁(手动开启和关闭锁),synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少时间来调度线程,性能更好,并且具有更好的拓展性(提供更多子类)
  • 优先使用顺序:Lock > 同步代码块 > 同步方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Lock锁
/* 格式:
class A {
private final ReentrantLock lock = new ReentrantLock();
public void m() {
lock.lock();
try {
// 保证线程安全的代码
} finally {
lock.unlock();
}
}
}
*/
public class Test27 {
public static void main(String[] args) {
BuyTicket1 buyTicket1 = new BuyTicket1();
new Thread(buyTicket1).start();
new Thread(buyTicket1).start();
new Thread(buyTicket1).start();
}
}

class BuyTicket1 implements Runnable {
int TicketNums = 10;

// 定义lock锁
private final ReentrantLock lock = new ReentrantLock();

@Override
public void run() {
while (true) {
try {
lock.lock(); // 加锁
if(TicketNums > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(TicketNums--);
} else {
break;
}
} finally {
lock.unlock(); // 解锁
}
}
}
}

静态代理模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 静态代理方法
/* 总结:
真实对象和代理对象都要实现同一个接口
代理对象要代理真实角色
*/
/* 优点:
代理对象可以做很多真实对象做不了的事情
真实对象可以专心做自己的事情
*/
public class Test18 {
/* 举例模拟结婚:
你只需要负责结婚,其他的事情都由婚庆公司(代理)帮你包办即可。
*/
public static void main(String[] args) {
new Thread( () -> System.out.println("我爱你") ).start();
new Company(new You()).HappyMarry();
}
}

interface Marry {
void HappyMarry();
}

// 真实角色,即你自己
class You implements Marry {
@Override
public void HappyMarry() {
System.out.println("新郎结婚中。。。");
}
}

// 代理角色,即婚庆公司
class Company implements Marry {
// 要结婚的对象
private Marry target;

public Company(Marry target) {
this.target = target;
}

@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}

public void before() {
System.out.println("布置场地");
}

public void after() {
System.out.println("收拾场地");
}
}

生产者消费者模式

应用场景:生产者和消费者问题

  • 假设仓库只能存放一件商品,生产者将生产出来的产品放入仓库,消费者将仓库中产品消费取走。
  • 如果仓库没有商品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
  • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止

这是一个线程同步问题,生产者和消费者共享一个资源,并且生产者和消费者之间相互依赖,互为条件。

  • 对于生产者,没有生产产品之前,要通知消费者等待。而生产之后,又需要通知消费者消费
  • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费
  • 在生产者消费者问题中,仅有synchronized是不够的
    • synchronized可以阻止并发更新同一个共享资源,实现了同步
    • synchronized不能用来实现不同线程之间的消息传递(通信)

Java提供了几个方法解决线程通信问题

方法名 作用
wait() 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
wait(long timeout) 指定等待的毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度

注意:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException

管程法

消费者不能直接使用生产者的数据,他们之间有个“缓冲区”
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// 测试:生产者消费者模型 --> 利用缓冲区解决:管程法
public class Test28 {
public static void main(String[] args) {
SynContainer container = new SynContainer();

new Productor(container).start();
new Consumer(container).start();
}
}

// 生产者
class Productor extends Thread {
SynContainer container;

public Productor(SynContainer container) {
this.container = container;
}

@Override
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println("生产了" + i + "个产品");
try {
container.push(new Product(i));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

// 消费者
class Consumer extends Thread {
SynContainer container;

public Consumer (SynContainer container) {
this.container = container;
}

@Override
public void run() {
for(int i = 0; i < 100; i++) {
try {
System.out.println("消费了-->" + container.pop().id + "产品");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

// 产品
class Product {
public int id;

public Product(int id) {
this.id = id;
}
}

// 缓冲区
class SynContainer {
// 需要一个容器大小
Product[] products = new Product[10];
// 容器计数器
int count = 0;

// 生产者放入产品
public synchronized void push(Product product) throws InterruptedException {
// 如果容器满了,就需要等待消费者消费
if(count == products.length) {
// 通知消费者消费,等待生产
this.wait();
}

// 如果没有满,我们就需要丢入产品
products[count] = product;
count++;

// 通知生产者消费
this.notifyAll();
}

// 消费者消费商品
public synchronized Product pop() throws InterruptedException {
// 判断能否消费
if(count == 0) {
// 等待生产者生产,消费者等待
this.wait();
}

// 如果可以消费
count--;
Product product = products[count];

// 消费空了,通知生产者生产
this.notifyAll();

return product;
}
}

线程池

背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。
可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具。

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间后会终止
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 测试线程池
public class Test29 {
public static void main(String[] args) {
// 创建服务,创建线程池
// newFixedThreadPool参数为线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);

// 执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());

// 关闭链接
service.shutdown();
}
}

class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}

lambda表达式

为什么要使用lambda表达式:

  • 避免匿名内部类定义过多
  • 让代码更加简洁
  • 去掉了没有意义的代码,只留下核心逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// lambda表达式
public class Test19 {
// 静态内部类
static class Like2 implements ILike {
@Override
public void lambda() {
System.out.println("I like lambda2");
}
}

public static void main(String[] args) {
// 实现类
ILike like = new Like1();
like.lambda();

// 静态内部类
like = new Like2();
like.lambda();

// 局部内部类
class Like3 implements ILike {
@Override
public void lambda() {
System.out.println("I like lambda3");
}
}
like = new Like3();
like.lambda();

// 匿名内部类,没有类的名称,必须借助父类或接口
like = new ILike() {
@Override
public void lambda() {
System.out.println("I like lambda4");
}
};
like.lambda();

// lambda表达式
like = () -> {
System.out.println("I like lambda5");
};
like.lambda();

ILove love = null;

// lambda表达式2
love = (a, b) -> System.out.println("I like lambda " + a + " "+ b);
love.lambda(20, 10);


/* 总结:
1. lambda表达式只能在一行代码的情况下简化成一行,否则必须用代码块包装。
2. 使用的前提是接口必须为函数式接口且只有一个函数。
3. 可以都去掉参数类型,且只有一个参数时可以不加括号。
*/
}
}

// 函数式接口
interface ILike {
void lambda();
}

// 接口实现类
class Like1 implements ILike {
@Override
public void lambda() {
System.out.println("I like lambda1");
}
}

interface ILove {
void lambda(int a,int b);
}
作者

Benboby

发布于

2020-07-28

更新于

2021-01-28

许可协议

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×