线程状态
1. 概述
- New 新建状态
- Runnable 运行状态
- Blocked 阻塞状态
- Terminated 死亡状态
- Timed Waiting 休眠状态
- Waiting 永久等待状态
2. Timed Waiting(计时等待)
通常调用Thread的sleep方法实现Timed Waiting,需要注意:
- 进入Timed Waiting转台的一种常见方法是调用Thread的sleep方法, 单独的线程也可以调用,不一定非要有协作关系
- 为了让其它线程有机会执行,可以加将Thread.sleep()的调用放在线程run()之内。这样才能保证线程执行过程中会睡眠
- sleep与锁无关,线程睡眠到期自动苏醒,并返回到Runnable(可运行)状态
Tips:sleep中指定的时间是线程不运行的最短时间。sleep()方法不能保证线程休眠到期后就马上执行
3. Blocked(锁阻塞)
举例,线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。
4. Waiting(无限等待)
/*
创建一个顾客线程:调用wait()方法,放弃cpu的执行,进入到WAITING状态
创建一个老板线程:调用notify()方法,唤醒顾客线程
*/
public class Demo01WaitAndNotify {
public static void main(String[] args) {
Object obj = new Object();
//创建一个顾客线程
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("顾客来了");
synchronized (obj) {
System.out.println("顾客说出要的包子的种类");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("包子到手了,开吃!!!");
}
}
};
//创建一个老板线程
Thread t2 = new Thread() {
@Override
public void run() {
System.out.println("老板做包子了");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
System.out.println("老板花了5s中做好了包子");
obj.notify();
}
}
};
Thread t3 = new Thread() {
@Override
public void run() {
System.out.println("线程3");
}
};
t1.start();
t2.start();
t3.start();
}
}
一个调用了某个对象的 Object.wait 方法的线程会等待另一个线程调用此对象的Object.notify()方法 或 Object.notifyAll()方法。
waiting状态并不是一个线程的操作,它体现的是多个线程间的通信,可以理解为多个线程之间的协作关系,多个线程会争取锁,同时相互之间又存在协作关系。
等待唤醒机制
1. 线程间通信
概念:多个线程在处理同一个资源,但是线程的任务却不相同。
Why?:多个线程并发执行时,在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
How?:多个线程在处理同一个资源时,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。就是多个线程在操作同一份数据时,避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段就是等待唤醒机制。
2. 等待唤醒机制
等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:
- wait():线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中。
- notify():则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
- notifyAll():则释放所通知对象的 wait set 上的全部线程。
细节:
1… wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。 - wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
- wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
代码:
包子资源类:
public class BaoZi {
String pi;
String xian;
boolean flag = false;
}
吃货线程类:
public class ChiHuo extends Thread {
private BaoZi bz;
public ChiHuo(BaoZi bz) {
this.bz = bz;
}
@Override
public void run() {
synchronized (bz) {
while (true) {
if (bz.flag == false) {
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃货开始吃" + bz.pi + bz.xian + "包子!!!");
bz.flag = false;
bz.notify();
System.out.println("吃货把" + bz.pi + bz.xian + "包子吃完了,叫包子铺生产包子!!!");
System.out.println("==================");
}
}
}
}
包子铺线程类:
public class BaoZiPu extends Thread {
int count = 0;
private BaoZi bz;
public BaoZiPu(BaoZi bz) {
this.bz = bz;
}
@Override
public void run() {
synchronized (bz) {
while (true) {
if (bz.flag == true) {
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被唤醒后执行
if (count % 2 == 0) {
bz.pi = "薄皮";
bz.xian = "三鲜馅";
} else {
bz.pi = "冰皮";
bz.xian = "牛肉馅";
}
count++;
System.out.println("包子铺正在做" + bz.pi + bz.xian + "包子");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
bz.flag = true;
bz.notify();
System.out.println("包子铺做好了" + bz.pi + bz.xian + ",吃货可以吃了!!!");
}
}
}
}
测试类:
public class Demo02Main {
public static void main(String[] args) {
BaoZi bz = new BaoZi();
new BaoZiPu(bz).start();
new ChiHuo(bz).start();
}
}
线程池
1. 线程池概念
线程池:线程池就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无须反复创建多个多个线程而消耗过多资源。
线程池的工作原理:
合理利用线程池带来的好处:
- 降低资源消耗
- 提高响应速度
- 提高线程的可管理性
2. 线程池的使用
java.util.concurrent.Executors:线程池的工厂类,用来生成线程池
Executors类中的静态方法:
static ExecutorService newFixedThreadPool (int nThreads) 创建一个可重用固定线程数的线程池
参数:new nThreads:创建线程池中包含的线程数量
返回值:ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接受
java.util.concurrent.ExecutorService:线程池接口
用来从线程池中获取线程,调用start方法,执行线程任务的方法
submit(Runnable task) 提交一个Runnabale任务用于执行
关闭/销毁线程池的方法
void shutdown()
线程池的使用步骤:
- 使用线程池工厂的Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
- 创建一个类,实现Runnable接口,重写run方法,设置线程任务
- 调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
- 调用ExecutorService中的方法shutdown销毁线程池
Runnable实现类代码:
public class RunnableImpl implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行!");
}
}
线程池测试类:
public class Demo01ThreadPool {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
es.shutdown();
}
}
Lambada表达式
1. Lambda表达式
标准格式:
(参数类型 参数名称) -> {代码语句}
格式说明:
- 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
->
是新引入的语法格式,代表指向动作。- 大括号内的语法与传统方法体要求基本一致。
- 无参无返回实例:
接口类代码:
public interface Cook {
void makeFood();
}
测试类代码:
public class Demo04Main {
public static void main(String[] args) {
invokeCook(new Cook(){
@Override
public void makeFood() {
System.out.println("做饭了!");
}
});
//使用Lambda接口优化书写
invokeCook(()->{
System.out.println("做饭了!");
});
//优化省略Lambda接口
invokeCook(()->System.out.println("做饭了!"));
}
public static void invokeCook(Cook cook){
cook.makeFood();
}
}
- 有参无返回实例:
接口类代码:
public class Person {
private String name;
private int age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
测试类代码:
public class Demo01Arrays {
public static void main(String[] args) {
Person[] arr = {
new Person("Mary",18),
new Person("Tom",23),
new Person("Love",19),
new Person("Able",17)
};
// Arrays.sort(arr, new Comparator<Person>() {
// @Override
// public int compare(Person o1, Person o2) {
// return o1.getAge()-o2.getAge();
// }
// });
// 使用Lambda优化匿名内部类
Arrays.sort(arr,(Person o1, Person o2)->{
return o1.getAge()-o2.getAge();
});
//优化省略Lambda接口
Arrays.sort(arr,(o1, o2)->o1.getAge()-o2.getAge());
for (Person person : arr) {
System.out.println(person);
}
}
}
- 有参有返回实例:
接口类代码:
public interface Calculator {
int calc(int a,int b);
}
测试类代码:
public class Demo06Main {
public static void main(String[] args) {
// invokeCalc(10, 20, new Calculator() {
// @Override
// public int calc(int a, int b) {
// return a + b;
// }
// });
invokeCalc(20, 30, (a, b) -> {
return a + b;
});
//优化省略Lambda接口
invokeCalc(20, 30, (a, b) -> a + b);
}
public static void invokeCalc(int a, int b, Calculator c) {
int sum = c.calc(a, b);
System.out.println(sum);
}
}
2. Lambda省略格式
省略规则:
- 小括号内参数的类型可以省略;
- 如果小括号内有且仅有一个参,则小括号可以省略;
- 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
3. Lambda的使用前提
- 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
- 使用Lambda必须具有上下文推断。