Java多线程笔记
1 定义
- 程序
- 程序是指令和数据的有序集合,没有任何运行的含义,是个静态的概念
- 进程(process)
- 进程是执行程序的一次执行过程,是动态的概念
- 进程是系统资源分配的单位
- 线程(thread)
- 一个进程中可以包含若干个线程
- 线程是CPU调度和执行的单位
- 多线程
- 真正的多线程是指有多个CPU,即多核
- 在一个CPU的情况下,在一个时间点,CPU只能执行一个代码,只是切换很快,有同时进行的错觉
2 线程创建
2.1 继承Thread类
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
public class TestThread1 extends Thread {
//run方法线程体
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("the number in sub thread is " + i);
}
}
//main线程,主线程
public static void main(String[] args) {
//创建一个子线程
TestThread1 testThread = new TestThread1();
//调用start方法开启线程
testThread.start();
for (int i = 0; i < 20; i++) {
System.out.println("the number in main thread is " + i);
}
}
}
运行之后,我们能看到两个线程的输出是交错的,说明两个线程在同时运行。如果不明显,可以将循环次数调大
如果在testThread直接调用run方法,那么不会出现多线程的情况,输出结果是由上至下依次输出的。调用start方法才会产生多线程的情况
另外,线程开启后不一定立即执行,由CPU调度执行
实现多线程同步下载图片
使用commons.io包
public class TestThread2 extends Thread {
private String url; //网络文件地址
private String fileName; //保存的文件
public TestThread2(String url, String fileName) {
this.url = url;
this.fileName = fileName;
}
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.download(url, fileName);
System.out.println("over, and the file is " + fileName);
}
public static void main(String[] args) {
TestThread2 t1 = new TestThread2("https://img1.doubanio.com/view/photo/raw/public/p1413828179.jpg", "1.jpg");
TestThread2 t2 = new TestThread2("https://img9.doubanio.com/view/photo/raw/public/p2162992026.jpg", "2.jpg");
TestThread2 t3 = new TestThread2("https://img1.doubanio.com/view/photo/raw/public/p1413828209.jpg", "3.jpg");
t1.start();
t2.start();
t3.start();
}
}
//下载器
class WebDownloader {
//下载方法
public void download(String url, String fileName) {
try {
FileUtils.copyURLToFile(new URL(url), new File(fileName));
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.2 实现Runnable接口
- 定义线程类实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
public class TestThread3 implements Runnable {
//run方法线程体
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("the number in sub thread is " + i);
}
}
//main线程,主线程
public static void main(String[] args) {
//创建Runnable接口的实现类对象
TestThread3 testThread = new TestThread3();
//创建线程对象,通过线程对象开启线程
//Thread thread = new Thread(testThread);
//thread.start();
new Thread(testThread).start();
for (int i = 0; i < 20; i++) {
System.out.println("the number in main thread is " + i);
}
}
}
建议使用实现Runnable接口的做法,避免单继承的局限性,方便一个对象被多个线程使用
多个线程操作同一个对象
public class TestThread4 implements Runnable {
private int ticketNum = 10;
@Override
public void run() {
while (true) {
if (ticketNum <= 0)
break;
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNum-- + "张票");
}
}
public static void main(String[] args) {
TestThread4 thread = new TestThread4();
new Thread(thread, "test1").start();
new Thread(thread, "test2").start();
new Thread(thread, "test3").start();
}
}
输出结果显示会产生数据紊乱,不符合常理的问题,多个线程操作同一个资源的情况下,线程不安全
其实,new Thread(thread).start() 这种方式是静态代理实现的,Thread类也是实现了Runnable接口,把实现了Runnable接口的thread对象作为参数传进去,就是静态代理的过程
此外,对于thread对象可以用匿名内部类代替,比如
new Thread(testThread).start();
其中,testThread实现了Runnable接口,可以写为
new Thread(new Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("the number in sub thread is " + i);
}
}
}).start();
还可以用lambda表达式进一步简化,这要求jdk的版本为8以上
new Thread(() -> {
for (int i = 0; i < 20; i++) {
System.out.println("the number in sub thread is " + i);
}
}).start();
2.3 实现Callable接口
-
实现Callable接口,需要返回值类型
-
重写call方法,需要抛出异常
-
创建目标对象
-
创建执行服务
ExecutorService service = Executors.newFixedThreadPool(1);
-
提交执行
Future<Boolean> result = service.submit(thread);
-
获取结果
boolean r = result.get();
-
关闭服务
service.shutdownNow();
public class TestCallable implements Callable<Boolean> {
@Override
public Boolean call() {
for (int i = 0; i < 20; i++) {
System.out.println("the number in sub thread is " + i);
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable thread = new TestCallable();
ExecutorService service = Executors.newFixedThreadPool(1);
Future<Boolean> result = service.submit(thread);
boolean r = result.get();
service.shutdownNow();
for (int i = 0; i < 20; i++) {
System.out.println("the number in main thread is " + i);
}
}
}
3 线程状态
3.1 停止线程
- 不推荐使用jdk提供的stop(),destroy()方法
- 推荐线程自己停止
- 使用一个标志位进行终止变量,当flag=false时,则终止线程运行
public class TestStop implements Runnable {
//设置一个标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("thread is running... i is " + i++);
}
}
//设置公开的方法停止线程
public void stop() {
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("main thread..." + i);
if (i == 90) {
testStop.stop();
System.out.println("thread stop");w
}
}
}
}
3.2 线程休眠
- sleep方法指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态
- 每个对象都有一个锁,sleep不会释放锁
public class TestSleep implements Runnable {
private int ticketNum = 10;
@Override
public void run() {
while (true) {
if (ticketNum <= 0)
break;
//模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNum-- + "张票");
}
}
public static void main(String[] args) {
TestThread4 thread = new TestThread4();
new Thread(thread, "test1").start();
new Thread(thread, "test2").start();
new Thread(thread, "test3").start();
}
}
模拟网络延时可以放大问题的发生性
//模拟倒计时
public class CountDown {
public static void countDown() throws InterruptedException {
int num = 10;
while (true) {
Thread.sleep(1000);
System.out.println(num--);
if (num <= 0) {
break;
}
}
}
pubilc static void main(String[] args) {
try {
countDown();
} catch {
e.printStackTrace();
}
}
}
3.3 线程yield
- yield让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让CPU重新调度,yield不一定成功
public class TestYield {
public static void main(String[] args) {
new Thread(new MyThread(), "a").start();
new Thread(new MyThread(), "b").start();
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
Thread.yield();
System.out.println("线程停止执行");
}
}
3.4 线程强制执行
- join合并线程,其他线程阻塞,待此线程执行完成后,再执行其他线程,相当于插队
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("sub thread is running..." + i);
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new TestJoin());
thread.start();
//main thread
for (int i = 0; i < 100; i++) {
if (i == 20) {
thread.join();
}
System.out.println("main thread is running..." + i);
}
}
}
3.5 观测线程状态
- Thread.State
- NEW:尚未启动的线程处于此状态
- RUNNABLE:在Java虚拟机中执行的线程处于此状态
- BLOCKED:被阻塞等待监视器锁定的线程处于此状态
- WAITING:正在等待另一个线程执行特定动作的线程处于此状态
- TIMED_WAITING:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
- TERMINATED:已退出的线程处于此状态
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sub thread is running...");
});
//观察状态
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(100);
state = thread.getState();
System.out.println(stated);
}
}
}
3.6 线程优先级
线程调度器监控程序中启动后进入就绪状态的所有线程,按照优先级决定应该调度哪个线程来执行
线程的优先级用数字表示,范围从1到10,数字越大代表优先级越高,通过getPriority()和setPriority(int)来获取和改变优先级
优先级的设定一定要在线程启动之前
此外,线程优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用,得看CPU的调度
public class TestPriority {
public static void main(String[] args) {
//主线程优先级
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
Thread t1 = new Thread(new MyThread());
Thread t2 = new Thread(new MyThread());
Thread t3 = new Thread(new MyThread());
Thread t4 = new Thread(new MyThread());
Thread t5 = new Thread(new MyThread());
Thread t6 = new Thread(new MyThread());
//设置优先级
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(4);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY);
t4.start();
t5.setPriority(-1); //报错
t5.start();
t6.setPriority(11); //报错
t6.start();
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
}
}
3.7 守护(daemon)线程
线程分为用户线程和守护线程,守护线程如后台记录操作日志,监控内存,垃圾回收等
虚拟机必须确保用户线程执行完毕,不用等待守护线程执行完毕
public class TestDaemon {
public static void main(String[] args) {
Thread thread = new Thread(new MonitorThread);
//默认为false表示用户线程
thread.setDaemon(true);
thread.start();
new Thread(new MyThread()).start();
}
}
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("sub thread is running...");
}
System.out.println("over");
}
}
class MonitorThread implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("monitor is running...");
}
}
}
结果显示monitor线程会在mythread线程执行完毕后再执行一段时间,最终停止
4 线程同步
线程同步发生在多个线程操作同一个资源,也就是并发的情况下
线程同步其实是一种等待机制,多个需要同时访问对象的线程进入此对象的等待池形成队列,等待前面线程使用完毕。
4.1 锁机制
为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可
但也存在问题
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁释放锁会导致比较多的上下文切换和调度延时,引发性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引发性能问题
4.2 线程不安全案例
public class UnsafeBuyTicket {
public static void main(String[] args) {
new Thread(new BuyTicket(), "test1").start();
new Thread(new BuyTicket(), "test2").start();
new Thread(new BuyTicket(), "test3").start();
}
}
class BuyTicket implements Runnable {
private int ticketNum = 10;
private boolean flag = true;
private void buy() throws InterruptedException {
if (ticketNum <= 0) {
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "get " + ticketNum--);
}
@Override
public void run() {
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account("testAccount", 100);
Withdraw withdraw1 = new Withdraw(account, 50, "test1");
Withdraw withdraw2 = new Withdraw(account, 100, "test2");
withdraw1.start();
withdraw2.start();
}
}
class Account {
String name;
int money;
public Account(String name, int money) {
this.name = name;
this.money = money;
}
}
class Withdraw extends Thread {
Account account;
int withdrawMoney;
int moneyInHand;
public Withdraw(Account account, int withdrawMoney, String name) {
super(name);
this.account = account;
this.withdrawMoney = withdrawMoney;
}
@Override
public void run() {
if (account.money < withdrawMoney) {
System.out.println(Thread.currentThread().getName() + "no enough money");
return;
}
//sleep增加问题发生的可能性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money -= withdrawMoney;
moneyInHand += withdrawMoney;
System.out.println(account.name + "余额为:" + account.money);
System.out.println(Thread.currentThread().getName() + "手中的钱:" + moneyInHand);
}
}
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
list.add(Thread.currnetThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
4.2 同步方法
通过private关键字保证数据对象只能被方法访问,使用synchronized关键字
- synchronized方法
- public synchronized void method(int args) {}
- synchronized方法控制对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程阻塞。方法一旦执行,就独占该锁,直到方法返回才释放锁
- 将一个大的方法声明为synchronized会影响效率
- 方法内需要修改的资源才需要锁,锁的范围太大会影响执行速度
- synchronized块
- synchronized(Obj) {}
- Obj为同步监视器
- 可以是任何对象,但推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this
- 执行过程和synchronized方法一样
使用同步方法或者同步代码块修改不安全案例
public class SafeBuyTicket {
public static void main(String[] args) {
new Thread(new BuyTicket(), "test1").start();
new Thread(new BuyTicket(), "test2").start();
new Thread(new BuyTicket(), "test3").start();
}
}
class BuyTicket implements Runnable {
private int ticketNum = 10;
private boolean flag = true;
//synchronized同步方法,锁的是this
private synchronized void buy() throws InterruptedException {
if (ticketNum <= 0) {
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "get " + ticketNum--);
}
@Override
public void run() {
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class SafeBank {
public static void main(String[] args) {
Account account = new Account("testAccount", 100);
Withdraw withdraw1 = new Withdraw(account, 50, "test1");
Withdraw withdraw2 = new Withdraw(account, 100, "test2");
withdraw1.start();
withdraw2.start();
}
}
class Account {
String name;
int money;
public Account(String name, int money) {
this.name = name;
this.money = money;
}
}
class Withdraw extends Thread {
Account account;
int withdrawMoney;
int moneyInHand;
public Withdraw(Account account, int withdrawMoney, String name) {
super(name);
this.account = account;
this.withdrawMoney = withdrawMoney;
}
@Override
public void run() {
//锁的是共享资源account
//如果synchronized加载run方法上,那么锁的是this,也就是Withdraw对象,但这个不是共享资源
synchronized (account) {
if (account.money < withdrawMoney) {
System.out.println(Thread.currentThread().getName() + "no enough money");
return;
}
//sleep增加问题发生的可能性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money -= withdrawMoney;
moneyInHand += withdrawMoney;
System.out.println(account.name + "余额为:" + account.money);
System.out.println(Thread.currentThread().getName() + "手中的钱:" + moneyInHand);
}
}
}
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
synchronized (list) {
list.add(Thread.currnetThread().getName());
}
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
对于第三个案例,我们还可以使用JUC安全类型内的集合CopyOnWriteArrayList
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
4.3 死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,导致两个或多个线程都在等待对方释放资源,都停止执行的情形
一个同步块同时拥有两个以上对象的锁时,就有可能会发生死锁问题
public class TestDeadlock {
public static void main(String[] args) {
MyThread t1 = new MyThread("test1", 0);
MyThread t2 = new MyThread("test2", 1);
t1.start();
t2.start();
}
}
class A {
}
class B {
}
class MyThread extends Thread {
//用static来保证资源只有一份
static A a = new A();
static B b = new B();
String name;
int choice;
public MyThread(String name, int choice) {
this.name = name;
this.choice = choice;
}
@Override
public void run() {
try {
do();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void do() throws InterruptedException {
if (choice == 0) {
synchronized (a) {
System.out.println(name + "get a");
Thread.sleep(1000);
synchronized (b) {
System.out.println(name + "get b");
}
}
} else {
synchronized (b) {
System.out.println(name + "get b");
Thread.sleep(2000);
synchronized (a) {
System.out.println(name + "get a");
}
}
}
}
}
程序会产生死锁,因为一个同步块同时拥有了两个锁a和b,而且a和b的资源只有一份(用static修饰)。如果把程序修改一下,就不会死锁
private void do() throws InterruptedException {
if (choice == 0) {
synchronized (a) {
System.out.println(name + "get a");
Thread.sleep(1000);
}
synchronized (b) {
System.out.println(name + "get b");
}
} else {
synchronized (b) {
System.out.println(name + "get b");
Thread.sleep(2000);
}
synchronized (a) {
System.out.println(name + "get a");
}
}
}
死锁产生的必要条件
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不妨
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
只要想办法打破其中任一条件,就可以避免死锁的发生
4.4 Lock
synchronized关键字是隐式定义锁来实现同步,而Lock对象显示定义同步锁实现同步
Lock接口位于JUC包,是多个线程对共享资源进行访问的工具,作用和synchronized一样
- ReentrantLock 可重入锁
- 实现了Lock
- 与synchronized有相同的并发性和内存语义
- 可以显示加锁和释放锁
- 可以使用tryLock()方法尝试获得锁,如果无法获得,程序不会无限等待下去,而是做一些额外处理
public class TestLock {
public static void main(String[] args) {
MyThread thread = new MyThread();
new Thread(thread).start();
new Thread(thread).start();
new Thread(thread).start();
}
}
class MyThread implements Runnable {
int ticketNum = 10;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//加锁
lock.lock();
try {
if (ticketNum <= 0) {
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNum--);
} finally {
//释放锁
lock.unlock();
}
}
}
}
Lock和synchronized对比
- Lock是显示锁,需要手动开启和关闭,synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好
- 优先使用顺序:Lock > 同步代码块 > 同步方法
5 线程协作
5.1 线程通信
Java提供了几个方法解决线程通信的问题
- wait():表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
- wait(long timeout):指定等待的毫秒数
- notify():唤醒一个处于等待状态的线程
- notifyAll():唤醒同一个对象上所有调用wait()方法的线程,优先级高的优先调度
这几个方法都是Object类的方法,都只能在同步方法或同步代码块中使用,否则会抛出异常
5.2 生产者消费者问题
解决方法一:管程法
生产者将生产好的数据放入缓冲区,消费者从缓冲区获取数据
public class TestPC {
public static void main(String[] args) {
SynBuffer buffer = new SynBuffer();
new Producer(buffer).start();
new Consumer(buffer).start();
}
}
class Producer extends Thread {
SynBuffer buffer;
public Producer(SynBuffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
buffer.push(new Product(i));
System.out.println("生产了 " + i + "个产品");
}
}
}
class Consumer extends Thread {
SynBuffer buffer;
public Consumer(SynBuffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了 " + buffer.pop().id + "个产品");
}
}
}
class Product {
int id;
public Product(int id) {
this.id = id;
}
}
class SynBuffer {
Product[] products = new Product[10];
int count = 0;
public synchronized void push(Product product) {
if (count == products.length) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
products[count] = product;
count++;
this.notifyAll();
}
public synchronized Product pop() {
if (count == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
Product product = products[count];
this.notifyAll();
return product;
}
}
解决方法二:信号灯法
public class TestPC {
public static void main(String[] args) {
Show show = new Show();
new Actor(show).start();
new Audience(show).start();
}
}
class Actor extends Thread {
Show show;
public Actor(Show show) {
this.show = show;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
show.play("show1");
} else {
show.play("show2");
}
}
}
}
class Audience extends Thread {
Show show;
public Audience(Show show) {
this.show = show;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
show.watch();
}
}
}
class Show {
String item;
boolean flag = true;
public synchronized void play(String item) {
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了" + item);
this.notifyAll();
this.item = item;
this.flag = !this.flag;
}
public synchronized void watch() {
if (flag) {
try {
this.wait();
} catch (IterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了" + item);
this.notifyAll();
this.flag = !this.flag;
}
}
6 线程池
经常创建和销毁使用量特别大的资源对性能影响很大,一般要使用线程池,提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,避免频繁创建销毁,可以重复利用
6.1 使用线程池
线程池相关的API:ExecutorService和Executors
- ExecutorService:真正的线程池接口,常见子类ThreadPoolExecutor
- void execute(Runnable command):执行任务/命令。无返回值,一般用来执行Runnable
- <T> Future<T> submit(Callable<T> tast):执行任务,有返回值,一般用来执行Callable
- void shutdown():关闭连接池
- Executors:工具类,线程池的工厂类,用于创建并返回不同类型的线程池
public class TestPool {
public static void main(String[] args) {
//创建服务,创建线程池
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 Runnbale {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + i);
}
}