Lambda表达式 流式编程 线程

一、Lambda表达式

(一)简介

1.  是 jdk1.8 入的一个新特性,简化了匿名内部类。

2.   只能作用于函数接口(接口里只有一个抽象方法)。

3.  可以理解为一个匿名方法:没有名字的方法。

(二)基础语法

(parameters)-> {statements}

parameters

1. 箭头左边为形式参数,可以有也可以没有也可以有多个.参数类型可以省略,如果省略必须全部省略。例如加入有5个参数,不可以只省略3个,要把5个的参数类型都省略。

2. 如果形参有且只有一个,那么小括号可以省略

statements

右边是方法体,如果只有一行代码可以省略{}并且return 关键字必须省略,

如果多行代码 那么要加上{}; 表示方法体,有没有return都可以(看返回值类型)。

(三)案例

下面是各种案例

public class _02LambdaDemo01 {
    public static void main(String[] args) {
        //测试实现类
        A c = new A();
        c.print();

        //3. 使用匿名内部类的方式实现NoParamterNoReturn接口 打印哈哈哈我哭了
        NoParamterNoReturn npnr = new NoParamterNoReturn() {
            public void print() {
                System.out.println("我一定能学会java");
            }
        };
        npnr.print();

        //4. 使用Lambda表达式实现NoParamterNoReturn接口 打印哈哈哈我哭了
        NoParamterNoReturn npnr2 = ()-> {System.out.println("哈哈哈我哭了");
            System.out.println("哈哈哈我笑了");
        };
        npnr2.print();

        //5. 使用Lambda表达式实现OneParamterNoReturn接口 打印‘我喜欢+形参“
        OneParamterNoReturn opnr = info -> System.out.println("我喜欢"+info);
        opnr.print("java");

        //6. 使用lambda表达式,实现MuilParameterNoReturn接口,
        // 打印两个参数拼接的效果,测试传入"我今年","18"
        MuilParamterNoReturn mpnr = (info,age) -> System.out.println(info + "今年"+age);
        mpnr.print("小明",20);
       // 使用lambda表达式,实现NoParameterReturn接口,
        // 计算两个随机数,区间[25,40]的和",测试
        NoParamterReturn npr = ()->{ int num1 = (int)(Math.random()*16+25);
                                     int num2 = (int)(Math.random()*16+25);
                                     return num1+num2;};
        int num = npr.calculate();
        System.out.println("两个随机数的和为:"+num);

        /*8. 使用lambda表达式,实现OneParameterReturn接口,
        计算形参的立方,测试传入3
        */
        OneParamterReturn opr = a ->(a*a*a);
        num = opr.calculate(3);
        System.out.println("一个数字的立方和"+num);

        /*9. 使用lambda表达式,实现MuilParameterReturn接口,
        计算两个形参的立方和,测试传入3和4
        */
        MuilParamterReturn mpr = (a,b)->a*a*a+b*b*b;
        num = mpr.calculate(3,4);
        System.out.println("两个数字的立方和为:"+num);
    }
}

class A implements NoParamterNoReturn {
    public void print() {
        System.out.println("main方法真简单");
    }
}

//里面的抽象方法 没有形参 也没有返回值
interface NoParamterNoReturn {
    void print();
}

//一个形参没有返回值
interface OneParamterNoReturn {
    void print(String info);
}

//多个形参 没有返回值
interface MuilParamterNoReturn {
    void print(String info, int age);
}

//带返回值 NoParamterReturn {
interface NoParamterReturn {
    int calculate();
}

//没带返回值 NoParamterReturn {
interface OneParamterReturn {
    int calculate(int a);
}

@FunctionalInterface//注解 校验该接口是否为函数式接口
interface MuilParamterReturn {
    int calculate(int a, int b);
}

结果如下: 

(四)变量的捕获

匿名内部类对变量的捕获,也就是内部对外部的引用和访问

匿名内部类可以访问外部的局部变量(形参和变量),但是不可以更改,因为局部变量被final修饰

lambda表达式也可以访问外部的局部变量(形参和变量),但是不可以更改,因为局部变量被final修饰

匿名内部类和Lambda表达式访问变量时

访问外部类的非静态成员变量时候: 外部类.this.变量名; 也可以直接 使用变量名访问

访问外部类的静态成员变量时候: 外部类.变量名   也可以直接使用变量名访问

访问内部类的成员变量时, 可以直接访问 也就是可以直接通过变量名访问

访问形参的时候,直接访问 也就是可以直接通过变量名访问

(五)Lambda表达式在集合中的应用

1. forEach()迭代
(1)list 集合
public class list_set_map {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<Integer>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.forEach(e->System.out.println(e));
    }
}

底层源码 

 Consumer是一个函数式接口,所以我们只需要传一个accept的匿名函数,也就是lambda函数即可

(2)set集合
public class list_set_map {
    public static void main(String[] args) {
        Set<Integer> set = new HashSet<Integer>();
        set.add(1);
        set.add(2);
        set.add(3);
        set.forEach(e->System.out.println(e));
    }
}
(3) map集合
public class list_set_map {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("张三", 13);
        map.put("李四", 23);
        map.put("王五", 43);
        map.entrySet().forEach(entry->System.out.println(entry.getKey()+":"+entry.getValue()));
        map.keySet().forEach(key-> System.out.println(key));
        map.values().forEach(value-> System.out.println(value));
    }
}

2.removeIf(): 移除元素

 removeIf(Predicate filter):

根据条件删除 ,传的参数是一个过滤器

public class RemoveIfTest {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3,4,4);
        List<Integer> newList = new ArrayList<>();
        newList.addAll(list);
        newList.removeIf(i-> i==4);
        System.out.println(newList);
    }
}

底层源码  

removeIf() :

     内部就是一个迭代器,遍历集合   根据传入的条件做删除操作

     条件是filter  的 test() 方法

Predicate

    是一个函数接口,里面有一个boolean test()方法,所以使用时候就使用一个lambda表达式去实现test方法

3. 排序时候使用比较器
public class sort {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3,4,4);
        List<Integer> newList = new ArrayList<>();
        newList.addAll(list);
        Collections.sort(newList,(a,b)->b-a);
        System.out.println(newList);
    }
}

二、 集合的流式编程

(一)简介

jdk1.8之后出现的新特性,是对集合中的元素进行遍历的,更像是迭代器

使用流式编程更加可以使代码更加简便

(二)步骤

1.获取数据源

2. 对流中的数据进行各种操作(中间操作)返回流对象本身

3. 对结果进行整合(最终操作)对流中数据进行各种处理,并关闭流

(三)最终操作

也就是将流中的数据收集到一起 可以存入一个集合中,最终会关闭流,如果在关闭流的基础上进行操作会报  stream has already been operated upon or closed 

 1. collect

底层是传入一个Collector的接口(非函数式接口),用来指定规则。

将流中的数据收集到一起,变成一个新的集合

public class CollectTest {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        nums.add(1);
        nums.add(2);
        nums.add(3);
        nums.add(4);

        //收集成list集合
        List<Integer> list = nums.stream().collect(Collectors.toList());

        //收集成set集合
        Set<Integer> set = nums.stream().collect(Collectors.toSet());

        //收集成map集合
        Map<String,Integer> map = nums.stream().collect(Collectors.toMap(e->"key"+e,e->e));
        System.out.println(map);
    }
}

keyMapper 是一个函数式接口,里面有一个R apply(T t)抽象方法, 我们就是通过lambda表达式来实现这个接口

 2. reduce

 将流中的数据按照一定的规则聚集起来 返回类型是Optional, 所以要用get()获得里面的数据

计算数据源的元素之和 方法参数也是lambda表达式 对应的抽象方法是R apply(T t, U u)

public class reduceTest {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        nums.add(1);
        nums.add(2);
        nums.add(3);
        nums.add(4);
        nums.add(5);
        int sum =nums.stream().reduce((a, b) -> a + b).get();//相当于 a +=b;
        System.out.println(sum);// sum =15
        int sum2 = nums.stream().reduce((a,b)->a-b).get();//相当于 a -= b;
        System.out.println(sum2);// sum2 = -13
    }
}
 3. count

计算集合中元素的个数 

public class countTest {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        nums.add(1);
        nums.add(2);
        nums.add(3);
        nums.add(4);
        nums.add(5);
        long count = nums.stream().count();
        System.out.println(count);// count = 5
    }
}
 4. forEach
public class ForEachTest {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        nums.add(1);
        nums.add(2);
        nums.add(3);
        nums.add(4);
        nums.add(5);
        //遍历list集合
        nums.stream().forEach(num-> System.out.println(num));
        //转成set集合
        Set<Integer> set = nums.stream().collect(Collectors.toSet());
        //遍历set集合
        set.stream().forEach(s-> System.out.println(s));
        //转成map集合
        Map<String,Integer> map = set.stream().collect(Collectors.toMap(s->"key"+s, s->s));
        //遍历map集合
        map.entrySet().stream().forEach(entry-> System.out.println(entry.getKey() + ":" + entry.getValue()));
    }
}
 5. max & min

max :获取最后一个元素

min: 获取第一个元素

max 和 min 里面传的是比较器。

Optional max( Comparator c)

Optional min( Comparator c)

max返回的是Optional类型,所以要用get() 接收

public class maxAndmin {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        nums.add(3);
        nums.add(2);
        nums.add(5);
        nums.add(4);
        nums.add(1);
        int max = nums.stream().max((a,b)->b-a).get();//降序
        int min = nums.stream().min((a,b)->b-a).get();//降序
        System.out.println("降序后max:"+max);//1
        System.out.println("降序后min:"+min);//5
         max = nums.stream().max((a,b)->a-b).get();//升序
         min = nums.stream().min((a,b)->a-b).get();//升序
        System.out.println("升序后max:"+max);//5
        System.out.println("升序后min:"+min);//1
    }
}
 6. Matching

(1)allMatch: 只有当流中所有的元素,都匹配指定的规则,才会返回 true

(2)anyMatch: 只要流中有任意的数据,满足指定的规则,都会返回 true

(3)noneMatch: 只有当流中的所有的元素,都不满足指定的规则,才会返回true

public class MatchingTest {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        nums.add(3);
        nums.add(2);
        nums.add(5);
        nums.add(4);
        nums.add(1);
        boolean b = nums.stream().anyMatch(num->num<2);//任意一个小于2
        System.out.println(b);//true
        b = nums.stream().noneMatch(n->n>6);//全都不大于6
        System.out.println(b);//true
        b = nums.stream().allMatch(n->n>0);//所有元素都大于0
        System.out.println(b);//true
    }
}
 7. find

(1)findFirst: 从流中获取一个元素(一般情况下,是获取的开头的元素) 

(2)findAny: 从流中获取一个元素(一般情况下,是获取的开头的元素)

这两个方法,绝大部分情况下,是完全相同的,但是在多线程的环境下, findAny和find返回的结果可能不一样。

public class FindTest {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        nums.add(1);
        nums.add(2);
        nums.add(3);
        nums.add(4);
        nums.add(5);
        int first =  nums.stream().findFirst().get();
        System.out.println(first);//first = 1
        int any = nums.stream().findAny().get();
        System.out.println(any);//any = 1
    }
}

(四)中间操作

 1. filter   And distinct

filter():过滤出满足条件的元素

distinct(): 去掉重复的值 

public class FilterTest {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        nums.add(1);
        nums.add(2);
        nums.add(3);
        nums.add(4);
        nums.add(5);
        nums.add(2);
        nums.add(2);
        //过滤出满足filter() ()里面的元素
        nums.stream().filter(e -> e % 2 == 0).forEach(System.out::print);//2 4 2 2
        System.out.println("");
        //去重
        nums.stream().distinct().forEach(System.out::print);// 1 2 3 4 5
    }
}
 3. sorted&  limit &  skip

sorted(Comparator c) 内传的是一个比较器  自定义比较规则

limit(long size): 表示截取流中的前size个元素

skip(long size): 表示跳过前size个元素

public class SortTest {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        nums.add(1);
        nums.add(2);
        nums.add(3);
        nums.add(4);
        nums.add(5);
        //跳过前两个元素
        nums.stream().skip(2).forEach(System.out::print);//3 4 5
        System.out.println("");
        //截取前三个元素
        nums.stream().limit(3).forEach(System.out::print);//1 2 3
        System.out.println("");
        //降序排列
        nums.stream().sorted((a,b)->b-a).forEach(System.out::print);//5 4 3 2 1
        System.out.println("");
    }
}
 5. map &  mapTolnt & mapToDouble & mapToLong
public class MapTest {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        nums.add(1);
        nums.add(2);
        nums.add(3);
        nums.add(4);
        nums.add(5);

        //1, map 将元素映射成另外一种类型
        Set<Integer> set = nums.stream().map(num -> num*2).collect(Collectors.toSet());
        System.out.println(set);

        System.out.println(nums);
        //mapToInt(): 将元素映射成IntStream
        int a = nums.stream().mapToInt(num -> num).sum();
        System.out.println(a);
        //mapToDouble(): 将元素映射成DoubleStream
        nums.stream().mapToDouble(num->num*2).forEach(System.out::println);//2.0  4.0  6.0  8.0  10.0
        //mapToLong(): 将元素映射成LongStream
        long b = nums.stream().mapToLong(num -> num).sum();
        System.out.println(b);//15
    }
}

三、线程

(一)简介

1.进程、程序和线程

(1)进程:是操作系统进行资源分配的最小单位每个进程都有自己的资源和地址空间。

                    多个进程可以同时执行,互不影响,资源不共享。

                  --- 进程是正在运行的程序

比如在Windows系统中,一个运行的xx.exe就是一个进程

eg:   打开了微信,这个正在运行的微信就是进程。计算机会分配给它一定的内存空间,用于它的运行和存储

(2)程序

           是一个没有生命的实体,只有处理器赋予生命时,成为一个有活动的实体,我们称为进程。

(3)线程

         线程是运算调度的最小单位,线程不能独立存在,一个进程可以有一个线程也可以有多个线程。每个线程都有自己的局部变量。指令指针,堆栈。它们共享进程的代码,数据,全局变量等

多线程可以实现并发执行,提高程序的效率。每个线程都是一个栈,线程里面的方法是一个栈帧

2. 进程与线程的区别

     1. 一个进程可以有多个线程,它们共享进程的堆和方法区资源,但是每个线程又有自己的程序计数器、虚拟机栈、和本地方法栈,所以系统产生一个线程或者线程之间进行上下文切换时负担比进程小的多,因此线程又被称为轻量级进程。

  2. 一个进程崩溃时在保护模式不会影响其他进程,但是一个线程奔溃了,整个线程都会死掉

  3. 线程不能独立存在,必须依赖于进程。

程序崩溃:

    不管你是否有意识,web程序都是被多线程执行的,web开发天然就是多线程开发。多线程会遇到临界区资源的问题,这时候一般通过锁来解决。  排队等锁、IO操作、对数据库连接的获取 都会引发线程阻塞。并发的线程越多,阻塞的时间也就越长,从web请求者的角度看,就是响应时间变长,系统变慢。
被阻塞线程越多,占用的资源也就越多,也不释放。如果阻塞的线程数超过了某个系统资源的极限,就会导致系统宕机,应用崩溃。

3.  CPU

现在一个CPU都是多核心的,一个CPU在一个时间片上可以执行一个线程

4. CPU的时间片

       概念

    在宏观上,我们打开了好几个程序,各个程序同时运行,互不打扰,但是在微观上,由于只有一个CPU,一次只能运行一个进程的某一个线程,所以CPU调度机制,会将时间划分成若个时间片。然后这个时间片给这个进程使用,下一个时间片给另外一个进程使用。

      调度机制

进程的调度机制是时间片轮转算法,是一种抢占式算法,CPU会分配给进程一个时间片当时间片用完后,该进程被打断,将时间片分配给下一个进程,这个过程一直持续到所有进程执行结束

实现原理:

  1. 系统将所有的就绪进程按先来先服务的原则,排成一个队列,

  2. 每次调度时,把CPU分配给队首进程,并令其执行一个时间片.时间片的大小从几ms到几百ms.

  3. 当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来停止该进程的执行,并将它送往就绪队列的末尾;

  4. 然后,再把处理器分配给就绪队列中新的队首进程,同时也让它执行一个时间片

5. 串行与并发

串行可以理解为接力赛,只有上一个人跑完了,下一个人拿到棒才可以跑。

并发就是指你有处理多个任务的能力但不一定要同时。宏观上是多个进程同时执行,微观上是多个进程交替执行

并行是指两个或者两个以上的事件在同一时刻发生,有同时处理多个任务的能力

举例

你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

(二)线程的调度机制 

     1.线程的状态 (7种)

(1)新建状态 

     线程已经被创建,但是还没有调用start()方法

(2) 就绪状态

      调用了start()方法,等待CPU调度执行

(3) 运行状态

         获得了CPU时间片,执行run()方法

(4) 阻塞状态

          因为某些原因,放弃了CPU的使用权,暂时停止运行,直到进入就绪状态

(5) 等待状态

        线程因为某些原因被终止运行,此时不会进入就绪状态,不会被分配时间片,直到被唤醒

(6) 超时等待状态

      线程在指定的时间内等待,超过这个指定的时间就会自动进入就绪状态

(7)终止状态

    线程的run方法执行完毕或者因为异常退出结束线程的生命周期

(三)线程的创建及常用API

1. 线程的创建

   (1) Thread

继承Thread类,重写run方法

public class ThreadCreate {
    public static void main(String[] args) {

        MyThread mt = new MyThread();
        mt.start();
    }
}
class MyThread extends Thread{
    public void run(){
        System.out.println("MyThread is running");
    }
}

使用匿名内部类的方式创建线程


public class ThreadCreate {
    public static void main(String[] args) { 
        //使用匿名内部类的方式创建线程
        Thread t = new Thread(){
            public void run(){
                System.out.println("Thread is running");
            }
        };
    }
}

(2) Runnable接口

实现Runnable,重写run方法,将Runnable子类对象传入Thread的构造器中。

public class RunnableCreate {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
        Thread t = new Thread(mr);
        t.start();
    }
}
class MyRunnable implements Runnable{
    public void run(){
        System.out.println("Hello, world!");
    }
}

 (3)

 1. 先获取Callable对象,重写里面的call方法,相当于run方法,但是call有返回值

 2. 再获取FutureTask对象,将Callable对象传入构造器

3. 再最后获取Thread对象,传入FutureTask对象

 4. 这种方式可以获取线程里的返回数据,进行其他操作,相比前两种方式 功能更强大一些

public class CollableCreate {
    public static void main(String[] args) throws Exception {
 //函数式接口
        Callable c = ()->{
            int sum = 0;
            for(int i=1;i<=100;i++){
                sum += i;
            }
            return sum;
        };
        FutureTask<Integer> ft = new FutureTask(c);
        Thread t = new Thread(ft);
        t.start();

        int result= ft.get();
        System.out.println(result);
        System.out.println("main thread end");
    }
}
2. 常用的构造器

(1)new Thread(Runnable target)

使用指定Runnable对象作为参数,创建一个Thread对象

(2) new Thread(Runnable target,String name)

使用指定Runnable对象作为参数,创建一个Thread对象,并指定对象名为name.

(3)new Thread(String name)

 创建一个Thread对象,并指定名称为name

public class Tset {
    public static void main(String[] args) {
        //new Thread(String name)
        Thread t1 = new Thread("小红");
        //new Thread(Runnable target)
        Thread t2 = new Thread(new Runnable(){
            public void run(){
                System.out.println("小明");
            }
        });

        MyRunnable mr = new MyRunnable();
        //new Thread(Runnable target,String name)
        Thread t3 = new Thread(mr, "小兰");

        t1.start();
        t2.start();
        t3.start();
    }
}
class MyRunnable implements Runnable{
    public void run(){
        System.out.println("小兰");
    }
}
3. 常用的属性方法
方法名用途
static Thread currentThread()Thread类的静态方法,可以用于获取运行当前代码片段的线程对象
long getId()返回该线程的标识符
String getName()返回该线程的名称
int getPriority()返回该线程的优先级
Thread.State getState()获取线程的状态
boolean isAlive()判断线程是否处于活动状态
boolean isInterrupted()判断线程是否已经中断
boolean isDaemon()判断线程是否为守护线程
public class PropertyMethodDemo {
    public static void main(String[] args) {
        /**
         *  main方法 本质上 就是一个线程
         */
        //1.获取当前线程的对象
        Thread current = Thread.currentThread();
        //2.获取线程的名字
        String name = current.getName();
        System.out.println("当前线程名字" + name);
        //3.获取线程的唯一标识符
        long getId = current.getId();
        System.out.println("当前线程唯一标识符" + getId);
        //4.获取线程的优先级
        int priority = current.getPriority();
        System.out.println("当前线程优先级" + priority);
        //5.当前线程的状态
        Thread.State state = current.getState();
        System.out.println("当前线程状态" + state);
        //6.判断当前线程是否存活
        boolean live = current.isAlive();
        System.out.println("当前线程是否  " + live);
        //7. 当前线程是否为守护进程
        boolean isDaemon = current.isDaemon();
        System.out.println("是否为守护进程" + isDaemon);

        //8.是否被打断
        boolean i = current.isInterrupted();
        System.out.println("该线程是否为中断状态" + i);


    }
}
 4.守护进程

void setDaemon(boolean on)  

线程分为前台线程(普通线程)和守护线程(后台进程),线程默认为普通线程也就是前台线程,但是将线程设置为后台线程,那么所有的前台进程终止了它也会强制终止。

GC就是运行在一个守护线程上的

由上图可知 :不将DaemonThread设置为守护线程前,DaemonThread会执行完它的方法

public class DaemonTest {
    public static void main(String[] args) {
        NormallThread nt = new NormallThread();
        DaemonThread dt = new DaemonThread();
        nt.start();
        //将DaemonThread设置为守护线程
        //dt.setDaemon(true);
        dt.start();

    }
}
class NormallThread extends Thread {
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println(i+" Normal Thread: ");
        }
    }

}
class DaemonThread extends Thread {

    public void run(){
        for(int i=0;i<30;i++){
            System.out.println("Deamon Thread: "+i);
        }
    }
}

下图是将 DaemonThread设置为守护线程,可以看到DaemonThread里面的run()方法没有运行完,程序就结束了。

5. 声明周期相关方法

 (1)sleep()

            static void sleep(long millis)

     线程睡眠方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()方法平台移植性好。

public class _01SleepDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread("download"){
            public void run(){
                for(int i=1;i<=10;i++){
                    System.out.println("正在下载视频······"+(i*10)+"%");
                    //使用休眠方法,来假装模拟正在下载中
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        //t1.setDaemon(true);//当设置为守护线程,没有普通线程,所以该线程也会终止,不会执行tun()方法
        t1.start();
    }
}

 (2)yield()

            static void yield()

 线程让步方法,暂停当前正在执行的线程对象,使之处于就绪状态,把执行机会让给相同或者更高优先级的线程。但是有可能该线程继续抢到时间片,该线程继续执行

 (3)join()

             void join()

线程加入方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到join添加进来的进程结束,当前线程再由阻塞转为就绪状态

package com.se.thread.elifeMethod;

public class JoinTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("t1 " + i);
                }
            }

        });
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 10; i++) {
                    if (i == 5) {
                        try {
                            t1.join();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    System.out.println("t2 " + i);
                }
            }
        });
        t1.start();
        t2.start();
    }
}

(4)interrupt()

             void interrupt()

线程打断方法。 打断哪个线程,就用哪个线程对象调用。

public class _04InterruptedDemo {
    public static void main(String[] args) {
        Thread lin = new Thread("林永健"){
            public void run(){
                System.out.println(getName()+"说:开始睡觉了");
                try {
                    //线程休眠100秒模拟睡着了
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println(getName()+"说:干嘛呢干嘛呢");
                    throw new RuntimeException(e);
                }
            }
        };
        Thread huang = new Thread("黄宏"){
            public void run(){
                for(int i=0;i<10;i++){
                    System.out.println(getName()+"说"+(i+1)+"80");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                System.out.println("搞定");
                lin.interrupt();
            }
        };

        lin.start();
        huang.start();
    }
}

打断林睡觉就用lin线程名字调用。

四、临界资源

(一)简介

在一个进程中,多个线程之间是资源共享的  一个资源被多个线程共同访问,这个资源就是临界资源。当多个线程共享同一个资源时候可能会发生临界资源安全问题。

如何解决呢?

针对于临界资源安全隐患问题的解决方式,引入锁机制

 1. 锁机制的作用是将异步的代码块变成同步的代码块

 2. 语法

           synchronized(锁对象的地址){

            //需要同步的代码块 如果不同步就会出现安全隐患问题

           }

 3. 任何的java对象都可以作为锁,一个要求:所有的线程看到的都是同一个对象

 4. 同步代码块在可能的情况下,尽量缩小范围,提高其他代码的并发效率

 5. 运行逻辑:

        当一个线程A执行到 s{} 就表示该线程获取了锁对象,其他线程都必须在代码块上面等待,

        直到线程A执行完同步代码块, 会自动释放锁对象,其他线程才有机会获取锁对象,谁获取到谁执行同步代码块

   就是将异步操作变成同步操作,同一时刻只有一个线程能够访问这个临界资源。

(二)锁

1. 锁机制简介

关键字:synchronized

用关键字将代码块包起来,进行同步代码块:

synchronized(同步监视器-锁对象引用){

  //代码块

}

   同步代码块包含两个部分:

          一个是充当锁的对象的引用

          一个是由这个锁保护的代码块

注意:

如果synchronized定义在方法上,那么必须用this表示锁

锁的对象必须是同一个

public class BankTest02 {
    public static void main(String[] args) {

        Object lock = new Object();
        Bank bank = new Bank();
        Thread xiaoming = new Thread("小明") {
            @Override
            public synchronized void run() {
                while (true) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    int addMoney = (int) (Math.random() * 1001 + 1000);
                    bank.add(addMoney);
                    try {
                        System.out.println("小明正在存钱");
                        Thread.sleep(1000);
                        this.notify();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }

            }
        };
        Thread xiaohong = new Thread("小红") {
            public synchronized void run() {
                while (true) {
                    int takeMoney = (int) (Math.random() * 301 + 300);
                    if (bank.balance < takeMoney) {
                        System.out.println(Thread.currentThread().getName() + "取钱失败,小明快点存钱");
                        this.notify();
                        try {
                            this.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    bank.take(takeMoney);
                }
            }

        };
        xiaoming.start();
        xiaohong.start();
    }
}


class Bank {

    public int balance = 0;

    public void add(int money) {
        System.out.print(Thread.currentThread().getName() + "存了" + money + "元");
        balance += money;
        System.out.println("余额为:" + balance);
    }

    public void take(int money) {
        System.out.print(Thread.currentThread().getName() + "取了" + money + "元  ");
        balance -= money;
        System.out.println("余额为:" + balance);
    }
}


解析:

上述代码,因为关键字定义在方法上,所以必须用this代表当前锁,但是在两个线程中,this指向的不一样,所以当没钱的时候不会通知小明存钱。

(三)单例模式的改进

class Single {
    private static Single instance = null;
    private Single(){};
    public Single getInstance(){
        synchronized(this){
            if(instance == null){
                instance = new Single();
            }
            
        }
        return instance;
    }
}

(四)死锁

1.产生

两个或者两个以上的线程无限期的等待对方的资源而导致阻塞的一种现象,

条件:

互斥使用:当一个资源被某一个线程使用时候,其他线程不能使用。

不可抢占:资源请求者不能从资源占领者手里抢占,只有资源占有者主动释放

请求与保持:资源请求者请求其他资源的同时又对原有资源的占有

 循环与等待:P1占有P2的资源 P2占有P3的资源 P3占有P2的资源,形成了一个等待环路

2. 解决死锁的方法

      1.  避免使用多把锁

使用多把锁会增加死锁的概率,因此应该尽量避免使用多把锁。可以考虑使用更高级别的同步工具,例如信号量、读写锁、并发集合

     2.   避免嵌套锁

    在持有一个锁的情况下,尽量避免获取其他锁,尤其是嵌套锁。如果确实需要嵌套锁,可以考虑使用线程本地变量或者其他的同步工具来避免死锁。

     3.  统一锁的获取顺序

   在多线程中使用多把锁时,为了避免死锁,应该尽量保证所有线程获取锁的顺序是一致的。可以按照某种全局的规则来确定锁的获取顺序,例如按照对象的 hash 值来获取锁。

    4.   限制锁的持有时间

   持有锁的时间越长,发生死锁的概率就越大。因此,可以考虑限制锁的持有时间,避免线程长时间持有锁而导致其他线程无法获取锁的情况。

   5.    超时等待锁

   如果一个线程尝试获取锁时发现已经被其他线程占用,可以设置一个超时时间,超过这个时间就放弃获取锁。这样可以避免线程一直阻塞等待锁而导致死锁。

   6.   破除循环等待

      6.1 按顺序获取资源

       按顺序获取资源是一种比较常见的破除循环等待的方法。如果所有的线程都按照固定的顺序获取资源,那么就不会出现循环等待的情况。

     6.2 统一资源分配器

       统一资源分配器是一种能够有效避免死锁的方法。在这种方法中,所有的资源都由一个统一的资源分配器来进行分配和释放,每个线程在需要资源时向资源分配器发出请求,资源分配器根据当前的情况来分配资源。这样就能够避免循环等待和其他死锁问题的发生。

   7.   检测死锁

    可以定期检测系统中是否存在死锁,并且采取相应的措施来解决死锁问题。例如,可以使用 jstack 工具来查看死锁情况,或者使用死锁检测算法来自动检测死锁。

设置超时等待

Object wait(long timeout) 方法让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过参数 timeout 设置的超时时间,会自动唤醒

如果 timeout 参数为 0,则不会超时,会一直进行等待,类似于 wait() 方法

3. 锁的常用API

(1)  wait()  

   进入阻塞状态的等待队列中,让当前的线程释放自己的锁进入等待队列中

等待队列中的线程,不参与CPU时间片和锁的争抢,直到被唤醒

(2)  notify()

随机唤醒或者通知一个等待这个锁的线程

被唤醒的进入到锁池,开始争抢锁标记

(3)  notifyAll()

唤醒所有等待这个锁的线程,

被唤醒的进入到锁池,开始争抢锁标记

4. ReentrantLock 可重入锁
5.生产者消费者

 生产者 生产数据,消费者消费数据,生产者和消费者都是对缓冲区里面的数据进行操作,不会直接接触。

生产者和消费者在同一段时间内共用同一个存储空间,生产者往存储空间中添加产品,消费者从存储空间中取走产品,当存储空间为空时,消费者阻塞,当存储空间满时,生产者阻塞。

    生产者 消费者设计模式 

1. 多线程并发的一种经典设计模式 

2. 多个生产者 - 一个仓库 - 多个消费者

    仓库里面数据不足时候 生产者生产数据存储到仓库,通知消费者进行消费

    仓库里面的数据充足时候 消费者消费仓库里面的数据,不足时,通知生产者生产数据 

3. 优点:

    - 解耦: 消费者和生产者没有耦合,互不相干 

   - 支持并发 多个生产者和消费者可以并发运行 

   - 支持闲忙不均: 因为有仓库,生产过快,可以先存仓库中让消费者慢慢的处理数据 

五、线程池

(一)简介

    在之前 ,我们想要使用线程,就创建一个线程,线程执行结束后销毁,但是当我们频繁的创建线程和销毁线程的时候 对CPU是一种负荷

如何解决呢?

     可以使用线程池来解决这个问题,想要使用的时候去线程池中找有没有闲置的线程,如果有就使用,使用完不是销毁而是归还给线程池。

这几个固定线程是核心线程,当核心线程都在做任务时候,线程池中可以维护几个临时线程,可以处理用户的任务;处理完后,临时线程也不是立即销毁,而是闲置一个指定时间段,当这个时间段没有收到其他任务时,就可以销毁。

(二)线程池的创建

6个参数 

int corePoolSize 核心线程池的数量

int  maximumPoolSize:  线程池的最大容量

long keepAliveTime 临时线程可以空闲的时间

TimeUnit unit : 临时线程保持存活的时间单位

BlockingQueue 用于存储用户任务的阻塞队列,是一个接口

ThreadFactory threadFactory 指定线程池的拒绝访问策略

前提条件,阻塞队列设置容量

1.  任务数量 < 核心线程+阻塞队列容量  只有核心线程处理

2. 任务数量 > 核心线程+阻塞队列容量 且 任务数量<= 线程池的最大容量+阻塞队列容量就会触发临时线程

3. 任务数量 > 线程池的最大容量+阻塞队列容量 就会触发拒绝策略

(三)线程池工具类里面的常用方法

 1. static ExecutorService newSingleThreadExecutor():

 : 返回只有一个核心线程的线程池 里面的阻塞队列容量可以理解为int的最大值   没有临时线程

 2. static ExecutorService newFixedThreadEPoolr(int nThreads):

 : 返回可以指定核心线程个数的线程池,里面的阻塞队列容量可以理解为int的最大值 没有临时线程

 3. static ExecutorService newSCachedThreadExecutor(): :返回只有临时线程的线程池

 临时线程的数量为int的最大值,闲置时间一分钟

 4. static ExecutorService ScheduledExecutorService ():  返回只有指定核心线程数量的线程池 临时线程的数量(int的最大值-核心数),临时线程的闲置时间为0

public class ThreadExcutorsDemo {
    public static void main(String[] args) {

        //只有 一个核心线程  阻塞队列为int 的最大值
        //10个任务串行
        //ExecutorService service = Executors.newSingleThreadExecutor();//10秒
        //5个核心线程  阻塞队列为int 的最大值
        //5个任务并行
        //ExecutorService service = Executors.newFixedThreadPool(5);//2秒
        //只有临时线程 数量为int的最大值
        //下面有10个任务,所以有10个临时线程并发执行
        ExecutorService service = Executors.newCachedThreadPool();//1秒
        for (int i = 0; i < 10; i++) {
            service.submit(() -> {
                long  currentMilliseconds1 = new Date().getTime();
                for (int j = 0; j < 10; j++) {
                    System.out.println(j + Thread.currentThread().getName());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                long currentMilliseconds2 = new Date().getTime();
                System.out.println("总共耗时:" + (currentMilliseconds2 -
                        currentMilliseconds1) + "毫秒");
            });
        }
        System.out.println("主线程结束");
    }
}

调度线程池的方法功能很多,可以设置延迟时间

 ScheduledExecutorService 是 ExecutorService的子类型

 schedule{

     Runnable runnable, 任务

      long delay, 延迟时间

      TimeUnit unit 延迟时间的单位

 };

延迟5秒后执行。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ThreadPoolMethod {
    public static void main(String[] args) {

        Runnable runnable = ()->{
            for (int j = 0; j < 10; j++) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + " " + "hello world");
            }
        };
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5);
        for(int i=0;i<10;i++){
            scheduler.schedule(runnable,5, TimeUnit.SECONDS);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值