一、等待唤醒机制
-
线程间的通信
多个线程在处理同一个资源,但是处理动作及线程的任务却不相同
多个线程并发执行时没在默认情况下 CPU 是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且希望有规律的执行,那么多线程之间需要一些协调通信,由此来达到多线程共同操作一份数据
那么如何线程通信有效利用资源,在多线程处理统一资源,并任务不同时,需要线程通信来解决线程之间对同一个变量使用或操作,就是多个线程在操作同一份数据时,避免对同一共享变量争夺,也就是需要通过一定手段是各个线程能有效利用资源, 那么这就是 等待唤醒机制
-
** 等待唤醒机制**
等待唤醒机制就是多个线程的一种协作机制.在一个线程进行了规定的操作后,就进入等待状态 ( wait() ) ,等待其他线程执行完指定的代码后,再将其唤醒 ( notify() ) ,若有多个线程等待时, 可以使用 notifyAll() 来唤醒所有的等待线程
等唤醒等待的方法
wait : 线程不在活动,进入 wait set 中,因此不会浪费 CPU 的资源,这是线程状态就是 waiting , 还要等待其他线程执行一个特别动作, 也就是 通知 (notify) 在这个对象上等待线程从 wait set 中释放出来,从新进入调度队列中
notify : 则是选取所通知对象的 wait set 中一个线程释放,
notifyAll : 则释放所通知对象 wait set 上的全部线程
二、线程池
-
线程池
如果并发数很多,而每个线程都执行很短时间就结束了,这样频繁创建线程会降低系统的效率,因为频繁创建线程和销毁线程需要时间,那么线程池可以来解决这个问题
线程池: 就是容纳多个线程的容器,其中的线程可以反复使用,省去频繁创建线程对象的操作,无需反复创建线程而消耗过多资源
-
线程池的使用
在java 中线程池的顶级接口是 java.util.concurrent.Executor, 但是 Executor 冰块不是一个线程池,只是一个执行线程的工具, 而真正的线程池接口是 java.util.concurrent.ExecutorService.
Executor 类中有创建线程池的方法有:
public static ExecutorService newFixedThread(int nThread) : 返回线程池对象
public Future<?> submit(Runnable task) : 获取线程池中某个线程对象,并执行
Future 接口 : 用来记录线程任务执行完毕后产生的结果,线程池创建与使用
使用线程池中线程对象的步骤
a 创建线程池对象
b 创建Runnable 接口子类对象
c 提交Runnable 接口子类对象
d 关闭线程池(一般不做)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
public static void main(String[] args){
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(2); //2个先传给你对象
//创建 Runnable对象
Runnabletest rt = new Runnabletest();
// //自己创建线程对象的方式
// Thread t = new Thread(rt);
// t.start();
//从线程池中获取线程,然后调用 Runnabletest 中的 run()
service.submit(rt);
//在获取两个线程对象,调用Runnabletest 中的 run()
service.submit(rt);
service.submit(rt);
//注意submit方法调用结束后,程序并未终止,因为线程池控制了线程的关闭
//使用完的线程又到线程池中
//线程池关闭
//service.shutdown();
}
}
class Runnabletest implements Runnable{
@Override
public void run(){
System.out.println("我需要一杯水");
try{
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("有一杯水放在桌子上: "+ Thread.currentThread().getName());
System.out.println("将水喝到肚子里了");
}
}
输出:
三、lambda 表达式
-
传统代码-使用实现类
要启动一个线程,需要创建一个 Thread 类的对象并调用 start()方法,为了指定线程内容,需要调用 Thread 类结构方法:
Thread(Runnable target)
为了获取 Runnable 接口实现对象,可以为该接口定义一个实现类 RunnableTests
public class RunnableLambda {
public static void main(String[] args){s
RunnableTests rt = new RunnableTests();
new Thread(rt).start();
}
}s
class RunnableTests implements Runnable{
@Override
public void run(){
System.out.println("多线程任务执行");
}
-
传统代码-使用匿名内部类
这个 RunnableTests 类只是为了实现 Runnable 接口而存在,而仅被使用了唯一一次,所以使用匿名内部类的语法可省去该类的单独定义,即匿名内部类
public class RunnableLambda {
public static void main(String[] args){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("读线程任务执行");
}
}).start();
}
}
匿名内部类的好处与弊端
一是匿名内部类可以省区实现类的定义,另外是匿名内部类语法较复杂
语义分析
仔细分析代码中的语义, Runnable 接口只有一个run 方法的定义
public abstract void run();
即定制了一种做事情的方案(其实就是一个函数):
无参数:不需要任何条件即可执行该方案
无返回值: 该方案不生产任何结果
代码块:该方案具体执行步骤
同样语义体现在 Lambda语法中,要更加简单:
() -> System.out.println(“读线程任务执行”)
前面一堆小括号即 run 方法的参数(无),代表不需要任何条件;
中间的一个箭头代表将前面的参数传递给后面的代码;
后面的输出语句就是业务逻辑
-
Lambda标准格式
Lambda 省去面向对象的结构,格式由三部分组成:
一些参数
一个箭头
一段代码
Lambda 表达式的标准格式:
(参数类型 参数名称)-> {代码语句}
public class LambdaDemoOne {
public static void main(String[] args){
inCook(() -> {
System.out.println("吃饭了");
});
}
private static void inCook(Cook cook){
cook.makeFood();
}
}
interface Cook{
void makeFood();
}
输出:
Lambda的参数与返回值
以前的写法:
import java.util.Arrays;
import java.util.Comparator;
public class CompratorDemo {
public static void main(String[] args){
Person[] array = {
new Person("小明",12),
new Person("小张",10),
new Person("小红",11)
};
//创建 Comparator 比较器对象
Comparator<Person> cp = new Comparator<Person>(){
@Override
public int compare(Person o1,Person o2){ //为了指定compare 方法,不得不使用 Comparator 接口实现类
return o1.getAge() - o2.getAge(); //大于零为正序
}
};
Arrays.sort(array,cp);//第二个参数为排序规则,就是comparator 接口实例
//Arrays.stor()方法需要排序规则,即Comparator接口实例,收i选抽象方法compre 是关键
for (Person person : array){ //遍历数组
System.out.println(person);
}
}
}
class Person{
private String name;
private int age;
//重写 toString 不然输出为一串看不懂的字符
@Override
public String toString(){
return age + "岁的" + name;
}
//构造方法无需写 void
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName(){
return this.name;
}
public int getAge() {
return this.age;
}
}
输出:
下面是Lambda 写法
import java.util.Arrays;
public class ComparatorLambdaDemo {
public static void main(String[] args){
Persons[] arrays = {
new Persons("Alice",9),
new Persons("Bob",11),
new Persons("Tom",13)
};
//Lambda 格式
Arrays.sort(arrays,(Persons o1,Persons o2) -> {
return o2.getAge() - o1.getAge(); //采用倒序
});
for (Persons persons : arrays){
System.out.println(persons);
}
}
}
class Person{
//skip 内容同上
}
输出:
注意: 使用Lambda 必须有接口,且接口中有且仅有一个抽象方法, 使用 Lambda 必须具有上下文推断,就是方法的参数或局部变量必须为lambda 对应的忌口类型