1.什么是线程
线程是程序执行的一条路径, 一个进程中可以包含多条线程
多线程并发执行可以提高程序的效率, 可以同时完成多项工作
1.1 多线程的应用场景
迅雷开启多条线程一起下载
QQ同时和多个人一起视频
服务器同时处理多个客户端请求
1.2 多线程并行和并发的区别(了解)
并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两 个任务都在运行。
比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。
如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。
1.3 Java程序运行原理
Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” 然后主线程去调用某个类的 main 方法。
1.4 JVM的启动是多线程的吗 (JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的)
2 多线程程序实现的方式(掌握)
2.1 方式1 继承Thread类
定义类继承Thread
重写run方法
把新线程要做的事写在run方法中
创建线程对象
开启新线程, 内部会自动执行run方法
public class Demo1 { public static void main(String[] args) { myThread my = new myThread(); //4,创建Thread类的子类对象 my.start(); //5,开启线程 for (int i = 0; i < 1000; i++) { System.out.println(i + "AA"); } } } class myThread extends Thread { //1,定义类继承Thread @Override public void run() { // 2,重写run方法 for (int i = 0; i <= 1000; i++) { // 3 将要执行的代码写在run方法中 System.out.println(i + "BB"); } } }
2.2 方式2 实现Runnable接口
定义类实现Runnable接口
实现run方法
把新线程要做的事写在run方法中
创建自定义的Runnable的子类对象
创建Thread对象, 传入Runnable
调用start()开启新线程, 内部会自动调用Runnable的run()方法
public class Demo2 { public static void main(String[] args) { MyRunnable mr = new MyRunnable(); // 4 创建Runnable的子类对象 //Runnable target = new MyRunnable(); Thread t = new Thread(mr); // 5 将其当做参数传递给Thread的构造方法 t.start(); // 6 开启线程 for (int i = 0; i < 1000; i++) { System.out.println(i + " BBBBB"); } } } class MyRunnable implements Runnable { // 1 定义一个类实现Runnable @Override public void run() { // 2,重写run方法 for (int i = 0; i < 1000; i++) { // 3 将要执行的代码写在run方法中 System.out.println(i + " AAAAAAAAAA"); } } }
2.3 实现Runnable的原理
查看源码
1,看Thread类的构造函数,传递了Runnable接口的引用
2,通过init()方法找到传递的target给成员变量的target赋值
3,查看run方法,发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run方法
2.4 查看源码观察两种实现方式的区别:
继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法
实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法
2.5 两种方式优缺点
继承Thread (开发常用)
好处是:可以直接使用Thread类中的方法,代码简单
弊端是:如果已经有了父类,就不能用这种方法
实现Runnable接口
好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂
3 匿名内部类实现线程的两种方式(掌握)
3.1 继承Thread类
public class Demo3_1 { public static void main(String[] args) { new Thread() { // 1 继承Thread类 public void run() { // 2 重写run方法 for (int i = 0; i < 1000; i++) { // 3 将所要执行的代码写到run方法中 System.out.println(i + " AAAAA"); } } }.start(); // 4 开启线程 for (int i = 0; i < 1000; i++) { System.out.println(i + " BBBBB"); } } }
3.2 实现Runnable接口
public class Demo3_2 { public static void main(String[] args) { new Thread(new Runnable() { // 1 将Runnable的子类对象传递给Thread的构造方法 @Override public void run() { // 2 重写run方法 for (int i = 0; i < 1000; i++) { // 3 将要执行代码写在run方法中 System.out.println(i + " AAAAA"); } } }).start();// 4 开启线程 for (int i = 0; i < 1000; i++) { System.out.println(i + " BBBB"); } } }
4 常用方法
4.1 获取名字和设置名字
继承Thread并通过构造方法设置名字 用getName()可以获取名字
public class Demo1_Name {
public static void main(String[] args) {
// 1 通过构造方法设置名字 直接在构造方法里传入一个字符串的参数
new Thread("小白") {
public void run() {
System.out.println(this.getName() + " AAAAAAAAA");
}
}.start();
// 2 通过setName("名字") 方法设置方法
new Thread() {
public void run() {
this.setName("小黑");
System.out.println(this.getName() + " BBBBBBBBB");
}
}.start();
}
}
4.2 获取当前线程的对象 Thread.currentThread()
public class Demo_CurrentThtead { public static void main(String[] args) { // 1 直接继承Thread方式创建线程 new Thread("直接继承Thread方式创建线程") { public void run() { System.out.println(Thread.currentThread().getName()); } }.start(); // 2 实现Runnable方式创建线程 new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName()); } }, "实现Runnable方式创建线程").start(); // 3 获取主函数线程的引用,并改名字 Thread.currentThread().setName("我是主线程"); System.out.println(Thread.currentThread().getName()); } }
4.3 sleep() 休眠线程(掌握)
Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000纳秒 = 1000000000
public class Demo4_Sleep { public static void main(String[] args) throws InterruptedException { // 线程休眠 抛出一个中断异常 for (int i = 20; i > 0; i--) { Thread.sleep(1000); // 主线程每循环一次睡眠一秒 System.out.println("倒计时" + i); } } }
4.4 setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
(比如下象棋,当帅(非守护线程)死了之后,那么这局棋也就结束了 其他小兵也就挂了(守护线程)。)
创建线程t1与t2 正常情况下 t1输出2次AAAAA t2输出50次BBBBB
把t2设置成守护线程之后 只要t1执行完两次AAAAA之后 t2就会自动结束 不会把50次BBBBB执行完
public class Demo4_Daemon { public static void main(String[] args) { // 创建线程t1 输出两次 AAAAA Thread t1 = new Thread(){ public void run(){ for ( int i = 0 ;i<2;i++){ System.out.println(getName()+"AAAAAAAAAA"); } } }; // 创建线程t2 输出50次 BBBBB Thread t2 = new Thread(){ public void run(){ for ( int i = 0 ;i<2;i++){ System.out.println(getName()+"BB"); } } }; // 设置t2为守护线程 当传入true就是意味着设置为守护线程 t2.setDaemon(true); // 启动线程 t1.start(); t2.start(); } }
4.5 join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
join(int), 可以等待指定的毫秒之后继续
创建t1,t2两个线程 分别输出10次 当t2输出两次之后 将t1插入 优先执行
public class Demo5_Join { public static void main(String[] args) { // 创建线程 t1 为了使t1可以给匿名内部类直接调用 将t1设置为final final Thread t1 = new Thread() { public void run() { for (int i = 0; i < 10; i++) { System.out.println(getName() + "AAAAA"); } } }; // 创建线程 t2 Thread t2 = new Thread() { public void run() { for (int i = 0; i < 10; i++) { // 如果 i ==2 将t1线程插入 优先执行 if (i == 2) { try { // 注意 如果在匿名内部类里面直接调用局部变量 那这个局部变量必须是final的 t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(getName() + "BBBBB"); } } }; // 启动线程 t1.start(); t2.start(); } }
4.6 礼让线程(了解) yield()让出cpu
玄学:执行有滞留时间 效果不是很明显
public class Demo_Yield { public static void main(String[] args) { new MyThread().start(); new MyThread().start(); } } class MyThread extends Thread { // 当i能被十整除的时候 让出线程 public void run() { for (int i = 0; i < 100; i++) { if (i % 10 == 0) { Thread.yield(); } System.out.println(getName() + "......" + i); } } }
4.7 setPriority()设置线程的优先级 (最小是1 默认是5 最大是10)
有一点效果 观察不是很明显
public class Demo7_Priority { public static void main(String[] args) { // 创建线程 t1 Thread t1 = new Thread() { public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + ".......AAA"); } } }; // 创建线程 t2 Thread t2 = new Thread() { public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + "....BBB"); } } }; // 设置线程t1的优先级为最小 1 t1.setPriority(Thread.MIN_PRIORITY); // 设置线程t2的优先级为最大 10 t2.setPriority(Thread.MAX_PRIORITY); // 启动线程 t1.start(); t2.start(); } }
5 同步代码块
什么情况下需要同步
当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
同步代码块
使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
5.1 同步代码块 案例示范
没加锁之前print1()里面的打印 有可能没执行完 就会被切换到 print2() 这样子 一句话就乱掉了
加锁之后print2()想要执行 就只能等peint()1执行完毕才能抢到线程了
public class Demo_Synchronized { public static void main(String[] args) { final Printer p1 = new Printer(); new Thread() { public void run() { while (true) { p1.print1(); } } }.start(); new Thread() { public void run() { while (true) { p1.print2(); } } }.start(); } } class Printer { public void print1() { synchronized (this) { //同步代码块,锁机制,锁对象可以是任意的 System.out.print("我"); System.out.print("话"); System.out.print("还"); System.out.print("没"); System.out.print("说"); System.out.print("完\n"); } } public void print2() { synchronized (this) { //锁对象不能用匿名对象,因为匿名对象不是同一个对象 System.out.print("\n请"); System.out.print("不"); System.out.print("要"); System.out.print("切"); System.out.print("换"); System.out.print("线"); System.out.print("程\n"); } } }
5.2 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
疑问 非静态的同步方法的锁对象是什么 (是this)
静态方法的同步方法的锁对象是什么(是该类的字节码对象 类.class)
public class Demo2_Synchronized { public static void main(String[] args) { final Printer2 p1 = new Printer2(); new Thread() { public void run() { while (true) { p1.print1(); } } }.start(); new Thread() { public void run() { while (true) { p1.print2(); } } }.start(); } } class Printer2 { public synchronized void print1() { // 同步方法只需要在方法上加上synchronized关键字即可 System.out.print("我"); System.out.print("话"); System.out.print("还"); System.out.print("没"); System.out.print("说"); System.out.print("完\n"); } public void print2() { synchronized (this){ //锁对象不能用匿名对象,因为匿名对象不是同一个对象 System.out.print("\n请"); System.out.print("不"); System.out.print("要"); System.out.print("切"); System.out.print("换"); System.out.print("线"); System.out.print("程\n"); } } }
6 线程安全问题
多线程并发操作同一数据时, 就有可能出现线程安全问题
使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作
6.1 小练习 需求:铁路售票,一共100张,通过四个窗口卖完. (Thread实现)
注意: 1 因为有四条线程 所以变量(票数)得是静态的共享变量
2 当一条线程对 当前变量进行操作时 得对这些操作加上同步代码块 不然变量会乱掉
3 静态代码块 锁对象必须得是同一个 我们可以传类的字节码对象 字节码对象只有一份
public class Demo_Ticket { public static void main(String[] args) { // 创建4个线程并启动 new Ticket().start(); new Ticket().start(); new Ticket().start(); new Ticket().start(); } } class Ticket extends Thread { // 设置一个静态共享变量 private static int ticket = 100; public void run() { while (true) { // 加一个同步代码块 是当前Ticket只可以被当前线程进行操作 // 注意 锁对象得是同一份 (Ticket.class) 是Ticket类的字节码对象只有一份 synchronized (Ticket.class) { if (ticket <= 0) { break; } try { Thread.sleep(10); // 为了使效果更明显 使线程们睡眠 } catch (InterruptedException e) { e.printStackTrace(); } // 打印票数 System.out.println(getName() + "......这是第" + ticket-- + "号票"); } } } }
6.2 小练习 需求:铁路售票,一共100张,通过四个窗口卖完. (Runnable接口实现)
public class Demo4_Ticket { public static void main(String[] args) { MyTicket mt = new MyTicket(); // 创建四条线程 传入 Runnable 接口的实例 new Thread(mt).start(); new Thread(mt).start(); new Thread(mt).start(); new Thread(mt).start(); } } class MyTicket implements Runnable { private int tickets = 100; @Override public void run() { while (true) { synchronized (MyTicket.class) { if (tickets <= 0) { break; } try { Thread.sleep(10); // 为了使效果更明显 使线程们睡眠 } catch (InterruptedException e) { e.printStackTrace(); } // 打印票数 System.out.println(Thread.currentThread().getName() + "......这是第" + tickets -- + "号票"); } } } }
6.3 死锁(了解)
多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁 (尽量不要嵌套使用)
public class Demo_DeadLock { private static String s1 = "筷子左"; private static String s2 = "筷子右"; public static void main(String[] args) { new Thread() { public void run() { while (true) { synchronized (s1) { System.out.println(getName() + ".....获取" + s1 + "等待" + s2); synchronized (s2) { System.out.println(getName() + "....拿到" + s2 + "开吃"); } } } } }.start(); new Thread() { public void run() { while (true) { synchronized (s2) { System.out.println(getName() + ".....获取" + s2 + "等待" + s1); synchronized (s1) { System.out.println(getName() + "....拿到" + s1 + "开吃"); } } } } }.start(); } }
6.4 回想以前说过的线程安全问题
Vector是线程安全的,ArrayList是线程不安全的
StringBuffer是线程安全的,StringBuilder是线程不安全的
Hashtable是线程安全的,HashMap是线程不安全的
7 线程间的通信
7.1.什么时候需要通信
多个线程并发执行时, 在默认情况下CPU是随机切换线程的
如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印
7.2.怎么通信
如果希望线程等待, 就调用wait()
如果希望唤醒等待的线程, 就调用notify();
这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用
7.3 两个线程间的通信
public class Damo1_Notify { public static void main(String[] args) { Println p = new Println(); new Thread() { public void run() { while (true) { try { // 因为在run方法中 父类没有抛异常 所以子类必须处理 p.print1(); } catch (InterruptedException e) { } } } }.start(); new Thread() { public void run() { while (true) { try { p.print2(); } catch (InterruptedException e) { } } } }.start(); } } class Println { private int flag = 1; // 设置开关 public void print1() throws InterruptedException { // 线程被暂停会抛 InterruptedException异常 synchronized (this) { // 设置同步代码块 锁对象是调用者 if (flag != 1) { // 如果开关不为1 this.wait(); // 线程等待 } // 开关为1或等待完毕 输出“开始” System.out.print("开"); System.out.println("始"); // 把开关设置为 2 flag = 2; // 叫醒线程 this.notify(); } } public void print2() throws InterruptedException { // 线程被暂停会抛 InterruptedException异常 synchronized (this) { // 设置同步代码块 锁对象是调用者 if (flag != 2) { // 如果开关不为2 this.wait(); // 线程等待 } // 开关为2或等待完毕 输出“开始” System.out.print("暂"); System.out.println("停"); // 把开关设置为 2 flag = 1; // 叫醒线程 this.notify(); } } }
7.4 多线程(三个或三个以上间的线程通信)
多个线程通信的问题
notify()方法是随机唤醒一个线程
notifyAll()方法是唤醒所有线程
JDK5之前无法唤醒指定的一个线程
如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件
public class Demo1_Notify { public static void main(String[] arge) { // 实例化 Println2 Println2 p = new Println2(); // 创建3条线程 分别调用 p的Println1() Println2() Println3() // 注意 这里要处理一下异常 new Thread() { public void run() { try { while (true) { p.print1(); } } catch (InterruptedException e) { } } }.start(); new Thread() { public void run() { try { while (true) { p.print2(); } } catch (InterruptedException e) { } } }.start(); new Thread() { public void run() { try { while (true) { p.ptint3(); } } catch (InterruptedException e) { } } }.start(); } } class Println2 { private static int falg = 1; // 设置开关 //注意 在同步代码块中,用哪个对象锁,就用哪个对象调用wait方法 public void print1() throws InterruptedException { synchronized (this) { // 同步锁 传入调用者对象 while (falg != 1) { // 不是第一个则等待 this.wait(); } // 是第一个则输出A1A2后 将开关改为二 然后叫醒所有等待的线程 System.out.print("A1"); System.out.println("A2"); falg = 2; this.notifyAll(); } } public void print2() throws InterruptedException { synchronized (this) { // 同步锁 传入调用者对象 while (falg != 2) { // 不是第二个则等待 this.wait(); } // 是第二个则输出B1B2后 将开关改为三 然后叫醒所有等待的线程 System.out.print("B1"); System.out.println("B2"); falg = 3; this.notifyAll(); } } public void ptint3() throws InterruptedException { synchronized (this) { // 同步锁 传入调用者对象 while (falg != 3) { // 不是第三个则等待 this.wait(); } // 是第三个则输出C1C2后 将开关改为三 然后叫醒所有等待的线程 System.out.print("C1"); System.out.println("C2"); falg = 1; this.notifyAll(); } } }
7.5 疑问
为什么wait方法和notify方法要定义在Object这个类中
因为锁对象可以是任意对象,而Object是所有对象的基类,所以wait方法和notify方法需要定义在Object这个类中
sleep方法和wait方法的区别
sleep方法必须传入参数,参数就是时间,时间到了自动醒来
wait方法可以传入参数也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待。
sleep方法在同步函数或同步代码块中不释放锁,睡着期间也是抱着锁睡
wait方法在同步函数或者同步代码块中,释放锁
8 JDK1.5的新特性互斥锁
1.同步
使用ReentrantLock类的lock()和unlock()方法进行同步
2.通信
使用ReentrantLock类的newCondition()方法可以获取Condition对象
需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class Demo1_Notify { public static void main(String[] arge) { // 实例化 Println2 final Println2 p = new Println2(); // 创建3条线程 分别调用 p的Println1() Println2() Println3() // 注意 这里要处理一下异常 new Thread() { public void run() { try { while (true) { p.print1(); } } catch (InterruptedException e) { } } }.start(); new Thread() { public void run() { try { while (true) { p.print2(); } } catch (InterruptedException e) { } } }.start(); new Thread() { public void run() { try { while (true) { p.ptint3(); } } catch (InterruptedException e) { } } }.start(); } } class Println2 { // 创建一个锁 private ReentrantLock r = new ReentrantLock(); // 创建三个监视器 private Condition c1 = r.newCondition(); private Condition c2 = r.newCondition(); private Condition c3 = r.newCondition(); private static int falg = 1; // 设置开关 public void print1() throws InterruptedException { // 获取锁 r.lock(); if (falg != 1) { // 不是第一个则监视器c1等待 c1.await(); } // 是第一个则输出A1A2后 将开关改为二 然后叫醒c2 System.out.print("A1"); System.out.println("A2"); falg = 2; c2.signal(); // 释放锁 r.unlock(); } public void print2() throws InterruptedException { // 获取锁 r.lock(); if (falg != 2) { // 不是第二个则监视器c2等待 c2.await(); } // 是第二个则输出B1B2后 将开关改为三 然后叫醒c3 System.out.print("B1"); System.out.println("B2"); falg = 3; c3.signal(); // 释放锁 r.unlock(); } public void ptint3() throws InterruptedException { // 获取锁 r.lock(); if (falg != 3) { // 不是第三个则监视器c3等待 c3.await(); } // 是第三个则输出C1C2后 将开关改为三 然后叫醒c1 System.out.print("C1"); System.out.println("C2"); falg = 1; c1.signal(); // 释放锁 r.unlock(); } }
9 线程组的概述和使用
线程组概述
Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
默认情况下,所有的线程都属于主线程组。
public final ThreadGroup getThreadGroup()//通过线程对象获取他所属于的组
public final String getName()//通过线程组对象获取他组的名字
我们也可以给线程设置分组
1,ThreadGroup(String name) 创建线程组对象并给其赋值名字
2,创建线程对象
3,Thread(ThreadGroup?group, Runnable?target, String?name)
4,设置整组的优先级或者守护线程
9.1 线程组的使用,默认是主线程组
public class Demo_ThreadGroup { public static void main(String[] args) { // demo1(); ThreadGroup tg = new ThreadGroup("我是一个新的线程组"); // 创建新的线程组 MyRunnable mr = new MyRunnable(); // 创建Runnable的子类对象 Thread t1 = new Thread(tg, mr, "线程1"); // 将线程t1放在组中 Thread t2 = new Thread(tg, mr, "线程2"); // 将线程t2放在组中 System.out.println(t1.getThreadGroup().getName()); // 获取组名 System.out.println(t2.getThreadGroup().getName()); // 获取组名 } public static void demo1() { MyRunnable mr = new MyRunnable(); Thread t1 = new Thread(mr, "线程1"); Thread t2 = new Thread(mr, "线程2"); ThreadGroup tg1 = t1.getThreadGroup(); ThreadGroup tg2 = t2.getThreadGroup(); System.out.println(tg1.getName()); System.out.println(tg2.getName()); } } class MyRunnable implements Runnable { public void run() { } }
10 线程的五种状态
新建,就绪,运行,阻塞,死亡
11 线程池概述
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
内置线程池的使用概述
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
使用步骤:
创建线程池对象
创建Runnable实例
提交Runnable实例
关闭线程池
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Demo_Executors { public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(2); // 创建线程池 // 匿名内部类创建线程 将线程放到池子里执行 pool.submit(new Thread() { public void run() { System.out.println("AA"); } }); // 匿名内部类创建线程 将线程放到池子里执行 pool.submit(new Thread() { public void run() { System.out.println("BB"); } }); pool.shutdown(); // 关闭线程池 } }