多线程详解

Posted fzly-88

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程详解相关的知识,希望对你有一定的参考价值。

Java.Thread

01线程简介

一个进程-->多个线程

进程:执行程序的一次执行过程

线程:就是独立的执行路径

现在的多线程 多是 模拟出来的

02线程实现(重点)

三种创建方式:

  • 继承Thread(重点)

    自定义线程类继承Thread类
    重写run()方法,编写线程执行体
    创建线程对象,调用start()方法启动线程
    

     

  • 实现Runnable接口(重点)

    定义MyRunnable类实现Runnable接口
    实现run()方法,编写线程执行体
    创建线程对象,调用start()方法启动线程
    
  • 实现Callable接口(了解 )

    实现Callable接口,需要返回值类型
    重写call方法,需要抛出异常
    创建目标对象
    创建执行服务:ExecutorService ser + Executors.newFixedThreadPool(1);
    提交Future<Boolean> result1 = ser.submit(t1);
    获取结果: boolean r1 = result1.get()
    关闭服务:ser.shutdownNow();
    

Lambda表达式

为什么要使用Lambda表达式 :

  • 避免匿名内部类定义过多

  • 可以让你的代码看起来很简洁

  • 去掉了一堆没有意义的代码,只留下核心的逻辑.

    任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口.

    对于函数式接口,我们可以通过lambda表达式来创建该接口的对象.


推导lambda表达式:

/*
推导lambda表达式 最初代码
 */
public class TestLambda1 {
    public static void main(String[] args) {
        ILike like = new Like();
        like.lambda();
    }
}

//1.定义一个函数式接口
interface ILike{
    void lambda();
}

//2.实现类
class Like implements ILike{
    public void lambda() {
        System.out.println("i like lambda");
    }
}

 

/*
推导lambda表达式 2.3.4.5.6是各种实现接口的方法,逐层推进
idea若是报错:将ProjectSettings 中Language level改成 8
参考:https://blog.csdn.net/fenghuibian/article/details/52704057
 */
public class TestLambda1 {

    //3.静态内部类
    static class Like2 implements ILike{
        public void lambda() {
            System.out.println("i like lambda2");
        }
    }

    public static void main(String[] args) {
        ILike like = new Like();
        like.lambda();

        like = new Like2();
        like.lambda();

        //4.局部内部类
        class Like3 implements ILike{
            public void lambda() {
                System.out.println("i like lambda3");
            }
        }
        like = new Like3();
        like.lambda();

        //5.匿名内部类,没有类的名称,必须借助接口或父类
        like = new ILike() {
            public void lambda() {
                System.out.println("i like lambda4");
            }
        };
        like.lambda();

        //6.用lambda简化
        like = ()->{
            System.out.println("i like lambda5");
        };
        like.lambda();
    }
}

//1.定义一个函数式接口
interface ILike{
    void lambda();
}

//2.实现类
class Like implements ILike{
    public void lambda() {
        System.out.println("i like lambda");
    }
}

总结:

  • lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹.
  • 前提是接口为函数式接口
  • 如果去掉参数类型, 就必须所有参数都去掉(多个参数必须加括号)

线程常用方法

  1. 线程停止,引入标记位,让线程自己运行我完停止,不建议强行停止
  2. 线程休眠:sleep()
  3. 线程礼让:yield()
  4. 线程强行执行:join()

03线程状态

创建-->就绪状态-->(阻塞状态)-->运行状态-->dead

setPriority(int newPriority) : 更改线程的优先级
static void sleep(long millis) : 在指定的毫秒内让当前正在执行的线程休眠
void join() : 等待该线程终止
static void yield() : 暂停当前正在执行的线程对象,并执行其他线程
void interrupt() : 中断线程,别用这个方式
boolean isAlive() : 测试线程是否处于活动状态

死亡后的线程不能再次启动

测试线程优先级:setPriority(int); int型:1~10

守护(daemon)线程:thread.setDaemon(true)

04线程同步(重点)

  • 由于同一进程的多个线程共享一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:

    • 一个线程持有锁会导致其他所有需要此锁的线程挂起;
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题;
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题;
  • 死锁

    线程A拥有线程B的资源,现在需要线程B的资源,但线程B此时也再等A的资源,两个线程都无法向下运行,造成死锁。

  • 产生死锁的四个必要条件

    1. 互斥条件:一个资源每次只能被一个进程使用
    2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不发。
    3. 不剥夺条件:进程已获得的资源,在使用完之前,不能强行剥夺。
    4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
  • 通过Lock(锁)类实现锁

  • synchronized与Lock的对比

    • Lock是显示锁(手动开启,手动关闭),synchronized是隐式锁,出了作用域自动释放

    • Lock只有代码块锁,synchronized有代码块锁和方法锁

    • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

    • 优先使用顺序:

      • Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)

     

05线程通信问题

Producer(生产者)--> 数据缓冲区 --> Consumer(消费者)

package gaoji;

//测试:生产者消费者模型-->利用缓冲区解决:管程法
//生产者,消费者,产品,缓冲区
public class TestPC {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();

        new Productor(container).start();
        new Consumer(container).start();
    }
}

//生产者
class Productor extends Thread{
    SynContainer container;

    public Productor(SynContainer container){
        this.container = container;
    }
    
    //成产
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            container.push(new Chicken(i));
            System.out.println("生产了"+i+"只鸡");
        }
    }
}

//消费者
class Consumer extends Thread{
    SynContainer container;

    public Consumer(SynContainer container){
        this.container = container;
    }

    //消费
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了--》"+container.pop().id+"只鸡");
        }
    }
}

//产品
class Chicken{
    int id;//产品编号

    public Chicken(int id) {
        this.id = id;
    }
}

//缓冲区
class SynContainer{

    //需要一个容器大小
    Chicken[] chickens = new Chicken[10];
    //容器计数器
    int count = 0;

    //生产者放入产品
    public synchronized void push(Chicken chicken){
        //如果容器满了,就需要等待消费者
        if(count == chickens.length){
            //通知消费者消费,生产等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //如果没有满,我们就需要丢入产品
        chickens[count] = chicken;
        count++;

        //可以通知消费者了
        this.notifyAll();
    }

    //消费者消费产品
    public synchronized Chicken pop(){
        //判断是否能消费
        if(count==0){
            //等待生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //如果可以消费
        count--;
        Chicken chicken = chickens[count];

        //吃完了,通知生产者生产
        this.notifyAll();

        return chicken;
    }

}

线程池

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁的创建和销毁,实现重复利用。类似生活中的公共交通工具。

  • 好处

    • 提高响应速度(减少了创建新线程的时间)

    • 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)

    • 便于线程管理(....)

      • corePoolSize:核心池的大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时最多保持多长时间后会终止

ExecutorService:真正的线程池接口。常见子类 ThreadPoolExecutor

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。

以上是关于多线程详解的主要内容,如果未能解决你的问题,请参考以下文章

20160226.CCPP体系详解(0036天)

多线程 Thread 线程同步 synchronized

LINUX操作系统知识:进程与线程详解

JAVA多线程synchronized详解

多个用户访问同一段代码

20160227.CCPP体系详解(0037天)