Java学习记录之线程池、Lambda表达式

等待唤醒机制
就是在一个线程进行了规定操作后,就进入等待状态(wait()),等待其它线程执行完它们的指定代码过后,再将其唤醒(notify());在有多个线程进行等待时,如果需要,可以用notifyAll()方法来唤醒所有的等待线程

wait:线程不再活动,不在参与调度,进入wait set中,因此不会浪费CPU资源,也不会去竞争锁了,这时线程状态即是WAITING.它还要等着别的线程执行一个特别的动作,也即“notify”.
notify:选取所通知对象的wait set中的一个线程释放

注意:
哪怕只通知一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以需要再次尝试去获取锁。成功后才能在当初调用wait方法之后的地方恢复
总结如下:
1.如果能获取锁,线程就从WAITING状态变成RUNNABLE状态
2.否则,从wait set 出来,又进入entry set,线程就从WAITING状态又变成了BLOCKED状态

调用wait和notify方法需要注意的细节
1.wait方法与notify方法必须由同一个锁对象调用,因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程
2.wait方法与notify方法是属于Object类的方法的,因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的
3.wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法

就拿生产包子、消费包子来说明等待唤醒机制如何有效利用资源:

//包子资源类
public class BaoZi{
    String pier;
    String xianer;
    boolean flag = false;//包子资源 是否存在  包子资源状态    .     
}
//吃货线程类
public class ChiHuo extends Thread{
   private BaoZi bz;/*这句话只是声明了一个变量,变量名为bz,变量类型为BaoZi,
   就是说bz能够引用BaoZi类型的对象,注意只是能够
   只有用bz=new BaoZi() 才说明新建了一个BaoZi对象,并把它赋值给变量bz,也就是说现在的
   bz才实际上引用了一个BaoZi类型的对象,在堆中开辟了空间。
   */
   public ChiHuo(String name,BaoZi bz){
       super(name);
       this.bz=bz;
   }
   @Override
   public void run(){
       while(true){
           synchronized(bz){
               if(bz.flag==false){//没包子
		  try{
                      bz.wait();
		  }catch(InterruptedException e){
		      e.printStackTrace();
		  }
		}
		System.out.println("吃货正在吃"+bz.pier+bz.xianer+"包子");
		bz.flag=false;
		bz.notify();
           }
       }
   }
}

//包子铺线程类
public class BaoZiPu extends Thread{
    private BaoZi bz;
    public BaoZiPu(String name,BaoZi bz){
        super(name);
        this.bz=bz;
    }
    @Override
    public void run(){
      int count=0;
      //造包子
      while(true){
         synchronized(bz){
            if(bz.flag==true){
               try{
                  bz.wait();
   	       }catch(InterruptedException e){
                  e.printStackTrace();
	       }
	    }
	    //没有包子,做包子
	    System.out.println("开始做包子")if(count%2==0){
                bz.pier="冰皮";
                bz.xianer="五仁"}else{
                 bz.pier="薄皮";
                 bz.xinaer="牛肉"}
  	     count++;
  	     bz.flag=true;
  	     System.out.println("包子做好了:"+bz.pier+bz.xainer);
  	     System.out.println("吃货来吃吧");
  	     bz.notify();
         }
      }
    }
}

//测试类
public class Demo{
   public static void main(String[] args){
       //等待唤醒案例
       BaoZi bz = new BaoZi();
       BaoZiPu bzp = new BaoZiPu("包子铺",bz);
       ChiHuo ch = new ChiHuo("吃货",bz);

       ch.start();
       bzp.start();
       
   }
}

线程池
我们使用线程的时候就去创建一个线程,这样实现起来非常简单,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频发的创建线程和销毁线程需要时间。
在Java中通过线程池来达到线程重复使用的效果,就是执行完一个任务,并不销毁线程,可以被复用。

线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多的资源;

合理利用线程池的三个好处:
1.降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
2.提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
3.提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目。防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)

Java里面线程池的顶级接口是java.util.concurrent.Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService.
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在java.util.concurrent.Excutors线程工厂类里提供了一些静态工厂,生成一些常用的线程池。

Executors类中有个创建线程池的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)

使用线程池对象的方法:
public Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行
Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

使用线程池中线程对象的步骤:
1.创建线程池对象
2.创建Runnable接口子类对象。
3.提交Runnable接口子类对象。
4.关闭线程池(一般不做)

public class MyRunnable implements Runnable{
    @Override
    public void run(){
        System.out.println("我要一个教练")try{
           Thread.sleep(2000);
        }catch(InterruptedException e){
           e.printStackTrace();
        }
        System.out.println("教练来了:"+Thread.currentThread().getName());
    }
}
//测试类
public class ThreadPoolDemo{
    public static void main(String[] args){
        //创建线程池对象
        ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
        //创建Runnable实例对象
        MyRunnable r = new MyRunnable();
        //从线程池中获取线程对象,然后调用MyRunnable中的run()
        service.submit(r);
        //再获取两个线程对象
        service.submit(r);
        service.submit(r);
        //注意:**submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭**
    }
}

Lambda表达式
通过对比来感受一下Lambda表达式:

public class DemoRunnable{
    public static void main(String[] args){
         //内部类
         Runnable task = Runnable(){
            @Override
            public void run(){
                System.out.println("多线程任务执行")}
         };
         new Thread(task).start();
    }
}

使用Lambda表达式优化上面的代码;

public calss DemoLambdaRunnable{
   public static void main(String[] args){
        new Thread(()->System.out.println("多线程任务执行")).start();
        //小括号代表Runnable接口run抽象方法的参数为空,大括号代表run的方法体
   }
}

Lambda的参数和返回值
要求:
使用数组存储多个Person对象
对数组中的Person对象使用Arrays类中的sort方法通过年龄进行升序排序

下面举例演示java.util.Comparator接口的使用场景代码,其中抽象方法定义为:
public abstract int compare(T o1,T o2);
当需要对一个数组对象进行排序时,Arrays.sort()方法需要一个Comparator接口实例来指定排序的规则。

//Person类
public class Person{
   private String name;
   private int age;
   //省略构造方法、toString方法与Getter Setter
}
import java.util.Arrays;
import java.util.Comparator;

public class DemoComparator{
   public static void main(String[] args){
       //本来年龄乱序的对象数组
       Person[] array ={new Person("古力娜扎"19)new Person("迪丽热巴"18)new Person("马儿扎哈"20)}/*
       或者这样创建Person类的数组
        Person[] array = new Person[3];
        array[0] = new Person("古力娜扎",19);
        ...
       */
       //内部类
       Comparator<Person> comp = new Comparator<>(){
           @Override
           public int compare(Person o1,Person o2){
              return o1.getAge()-o2.getAge();
           }
       };
       Arrays.sort(array,comp);//第二个参数为排序规则,即Comparator接口实例
   }
}

下面我们来分析上述代码真正要做的是什么
1.为了排序,Arrays.sort方法需要排序规则,即Comparator接口的实例,抽象方法compare是关键
2.为了指定compare的方法体,不得不需要Comparator接口实现类
3.为了省去定义一个ComparatorImpl实现类的麻烦,不得不使用匿名类
4.必须覆盖重写抽象compare方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能错
5.实际上,只有参数和方法体才是关键

Lambda写法:

import java,util.Arrays;

public class DemoCompaaratorLambda{
    public static void main(String[] args){
       Person[] array = {new Person("古力娜扎"19)new Person("迪丽热巴"18)new Person("马儿扎哈"20)};
       Arrays.sort(array,(Person a,Person b) -> {return a.getAge()-b.getAge();});
    }
}

Lambda格式的有参有返回

//接口
public interface Calculator{
   int calc(int a,int b);
}

//测试类
public class DemoCalculatorLambda{
    public static void main(String[] args){
      invokeCalc(130,120,(int a,int b) -> {return a+b;});
    }
    private static void invokeCalc(int a,int b,Calculator calculator){
       int result = calculator.calc(a,b);
       System.out.println("结果是:"+result);
    }
}

Lambda省略格式
在Lambda标准格式的基础上,使用省略写法的规则为:
1.小括号内参数的类型可以省略
2.如果小括号内有且仅有一个参数,则小括号可以省略
3.如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号

Lambda的使用前提:
1.使用lambda必须是接口,且要求接口中有且仅有一个抽象方法。
无论是JDK内置的Runnable、Comparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda
2.使用Lambda必须具有上下文推断
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例
备注:有且仅有一个抽象方法的接口,称为“函数式接口”

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。、可私 6信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 、可私信6博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 、可私信6博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值