多线程

大一菜鸡的个人笔记,欢迎指点和交流。

多线程

image-20200525100436987

原来只有一条路,车多了会拥挤,效率极低

多加几个车道,效率高,即多线程。

进程与线程

进程:在操作系统中运行的程序就是进程,比如QQ、游戏等。

一个进程可以有多个线程,比如在视频中可以同时听声音、看图像、看弹幕。

线程的特性

​ 程序是一个静态的概念。而进程是执行程序的一次执行过程,是一个动态的概念。是系统分配资源的概念。

​ 一个进程中可以包含多个线程,至少有一个线程(Main线程)。 线程是CPU调度和执行的单位

​ 很多的多线程是模拟出来的,即一个CPU在同一时间点,只能执行一段代码。因为切换的快所以有同时进行的错觉。真正的多线程是多核CPU。

image-20200525101636126

线程的创建

继承Thread类(重点)

继承Thread类

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象 调用start()方法启动线程
  • image-20200525104551025
  • image-20200525104605055
start方法——同时进行

image-20200525105355046

实际上主线程和副线程是同时执行的

run方法——先后进行

image-20200525105458675

先调用run方法再执行主线程

不建议使用:避免OOP单继承局限性

实现Runnable接口(最重要)

实现Runnable接口

image-20200525111720876

推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

实现Callable接口(了解)

  • 实现Callable接口

  • 重写call方法 需要返回一个值

  • 创建执行服务

  • 提交执行

  • 获取结果

  • 关闭服务

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    /**
     * @author:zmc
     * @function:
     * @date: 2020/7/16 11:01
     */
    public class Callable implements java.util.concurrent.Callable {
    
    
        public static void main(String[] args) {
            //创建线程池(不正规的方法)
            ExecutorService ser = Executors.newFixedThreadPool(1);
            Callable t1 = new Callable();
    
            //提交执行
            Future r1 = ser.submit(t1);
    
            //获取结果
            // Object o = r1.get();
    
            //关闭服务
            ser.shutdown();
    
            for (int i = 0; i < 500; i++) {
                System.out.println("Main哈哈哈"+i);
            }
        }
    
        @Override
        public Object call() throws Exception {
            for (int i = 0; i < 500; i++) {
                System.out.println("Callable哈哈哈"+i);
            }
            return null;
        }
    }
    
    

    callable和主线程是同时进行的

callable的好处
  • 可以定义返回值

  • 可以抛出异常

静态代理:线程的底部原理

真实对象和代理对象都要实现同一个接口

代理对象要代理真实角色

image-20200525211048736

优点

  • 代理对象可以做很多真实对象做不了的事情
  • 真实对象可以专注做自己的事情
  • 代理对象对真实对象增强

静态代理和多线程的关系

image-20200526102024612

Thread相当于weddingcompany 都是代理 中间为真实对象(Runnable接口)调用了start方法

Lambda表达式:Java8新技术

λ希腊字母表排序第十一位的字母

image-20200526102415943

理解Functional Interface(函数式接口)是学习Java8 lambda表达式的关键所在

函数式接口:只包含一个唯一一个抽象方法 可以用lambda表达式来创建对象

静态内部类

image-20200526112139608

局部内部类

image-20200526112253132

匿名内部类

image-20200526112411873

Lambda表达式的使用:直接实现

image-20200526112545145

有类型的写法image-20200526113059797
无类型的写法

image-20200526113304190

无括号的写法(只能有一个参数)

image-20200526113431418

去除花括号(只能有一行实现)

image-20200526113516758

Lambda表达式的优点

  • 避免匿名内部类定义过多
  • 使代码更加简洁
  • 只留下核心逻辑

线程状态

image-20200526114723562

image-20200526114818336

线程方法

image-20200526114851033

停止线程

不推荐用stop() destroy()方法

推荐让线程自己停下来

建议使用标识变量模拟停止:

image-20200526115350326

线程休眠

  • sleep(时间)指定当前线程阻塞的毫秒数

  • sleep存在异常InterrupteedException

  • sleep之后进入就绪状态

  • sleep可用于模拟网络延时,倒计时等

  • 每个对象都有一个锁 sleep不会释放锁

倒计时

image-20200526121825969

线程礼让

方法:Thread.yield();

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让CPU重新调度 但不一定成功

image-20200526123545479

线程插队

  • Join合并插队,其他线程阻塞。待此线程执行完成后,再执行其他线程。

    image-20200526130321653

观察线程状态

image-20200526131018233

状态:

image-20200526131136717

线程无法启动两次

线程优先级

Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,按照优先级来决定。

数字大的优先级高

image-20200526150705177

先设置优先级,再启动

优先级高,分配的资源就多。不一定优先级高的先跑,但是概率会更高。

image-20200526151559256

守护(daemon)线程

  • 线程分为用户线程(main)和守护线程(如gc线程(garbage 垃圾线程))

  • 虚拟机必须确保用户线程执行完毕

  • 虚拟机不用等待守护线程执行完毕

  • 守护线程作用:

    • 后台记录操作日志
  • 监控内存

    • 垃圾回收等等

    image-20200526153024894

主线程结束,虚拟机停止还需要一段时间,然后守护线程被强行终止。

线程同步机制

多个线程操作同一个资源

并发:同一个对象被多个线程同时操作

image-20200526154323356

image-20200526154552592

概念

​ 为了安全,在线程访问对象时加入锁(synchronized)机制,当一个线程获得一个对象的排他锁,独占资源,其他线程必须等待此线程使用完毕后释放锁。

​ 还有以下问题:

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

image-20200526164143750

线程同步->使线程变得安全

​ 数据对象:通过private关键字来保证数据对象只能被方法访问

​ 方法:使用synchronized关键字 包括两种用法:synchronized方法和synchronized块

synchronized方法控制对"对象"的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞。方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。

image-20200526164702440

缺陷

若将一个大的方法申明为synchronized 将会影响效率

实际上方法内部不是每一处都需要上锁,此时使用同步方法会导致性能浪费。

image-20200526182735081

synchronized方法

image-20200526192815745

synchronized代码块

同步块需要监视器

image-20200526192716940

死锁

多个线程持有对方需要的资源,形成了僵持。

image-20200526195325858

产生死锁的四个必要条件

  • 一个资源每次只能被一个进程使用

  • 一个进程因请求资源而阻塞时,对已获得的资源保持持有状态

  • 进程已获得的资源,在未使用完前,不能强行剥夺

  • 若干进程之前形成一种头尾相接的循环等待资源关系

  • image-20200526195817008

ReentrantLock(可重复锁)

可显式地上锁和解锁

image-20200526200118770

image-20200526200731335

Synchronized与ReentrantLock的对比

​ Lock是显式锁(手动上锁和解锁,一定要记得解锁),synchronized是隐式锁,出了作用域自动释放。

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

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

​ 优先使用顺序

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

线程协作

消费者问题

image-20200526204607729

image-20200526204632185

image-20200526204911351解决方案1 管程法——利用缓存区

image-20200526205802363

/**
 * @author:zmc
 * @function:生产者消费者模型 缓冲区方案————管程法
 * @date: 2020/7/16 13:29
 */
public class PC {

    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 < 10; i++) {
            System.out.println("生产了"+(i+1)+"只鸡");
            container.push(new Chicken(i));
        }
    }
}

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

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("消费了"+(container.pop(new Chicken(i)).id+1)+"只鸡");
        }
    }
}

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;
    this.notifyAll();
    }

    public synchronized Chicken pop(Chicken chicken){
        if(count == 0) {
            //满了 通知消费者消费
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        chickens[count--] = chicken;
        this.notifyAll();
        return chicken;
    }
}
解决方案2 信号灯法——

image-20200526205949631

/**

 * @author:zmc

 * @function: 信号灯法

 * @date: 2020/7/16 13:55
   */
   public class PC2 {

   public static void main(String[] args) {
       TV tv = new TV();
       new Player(tv).start();
       new Watch(tv).start();
   }
   static class Player extends Thread{
       TV tv = new TV();
       public Player(TV tv){
           this.tv = tv;
       }@Overridepublic void run() {for (int i = 0; i < 20; i++) {if(i%2==0){this.tv.play("正在播放快乐大本营");}else {this.tv.play("正在播放肥皂剧");}}}
   }static class Watch extends Thread{
​        TV tv = new TV();public Watch(TV tv){this.tv = tv;}@Overridepublic void run() {for (int i = 0; i < 20; i++) {
​                tv.watch();}}}


   public static class TV{
        String voice;
        boolean flag = true;public synchronized void play(String voice)  {if(!flag){try {this.wait();} catch (InterruptedException e) {
​                    e.printStackTrace();}}
​            System.out.println("演员表演了"+voice);//通知观众观看this.notifyAll();this.voice = voice;this.flag = !this.flag;}public synchronized void watch()  {if(flag){try {this.wait();} catch (InterruptedException e) {
​                    e.printStackTrace();}}
​            System.out.println("观众观看了"+voice);//通知演员观看this.notifyAll();this.voice  = voice;this.flag = !this.flag;}}
}

线程池

背景

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

原理

​ 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,实现线程的高效利用。

优点

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

​ 降低资源损耗(重复利用线程池中线程,不必每次都创建)

​ 便于线程管理

image-20200526214916294

image-20200526215153266

使用工具类,阿里规范要求通过ThreadPoolExecutor方法手动创建

image-20200526215516608

线程组

​ 为了方便管理一批线程,我们使用ThreadGroup来表示线程组。(A线程创建了B线程,B线程就是属于A线程的线程组的)

核心信息

  • 名称
  • 优先级
  • 守护与否
  • 父线程组
  • 子线程组

线程组内部属性包括:自己的名字,父线程组,使用数组记录自己下面有哪些线程组

线程组相关的方法

ThreadGroup(String name) 创建名为name的新线程组

ThreadGroup(ThreadGroup parent,String name) 创建父线程组为parent的线程组

创建系统线程组

image-20200717092608411

获取线程组名称

image-20200717092430826

线程组优先级

指的是一个线程组中所有线程最大允许的线程的优先级,实际上线程中不一定有达到这个优先级的。

image-20200717092409844

守护

默认值与父线程组相同

image-20200717092338257

父线程组

image-20200717092857394

子线程组

线程组内可以有线程组,层层嵌套形成树状结构。

创建线程组时,借助parent.add(this)方法,把自己添加到父线程组的记录数组groups[]。

对于任何一个线程,都有一个线程组,如果没有设置,就会把当前线程的线程组作为线程组。

每个线程组都知道自己有多少个线程,哪些线程;每个线程组也都知道自己有多少个线程组,哪些线程组。

子线程组相关方法

activeCount()获取线程组和子线程组所有活动线程个数的一个估计数

activeGroupCount()获取子线程组的个数的估计数

interrupt()中断线程组中的所有线程

线程组的销毁

destroy()

  • 会进行权限检验
  • 线程组必须为空——线程组的所有线程都已停止执行
  • 会把子线程组递归销毁,并且也会进行子线程组的权限检验和判空

权限检验checkAccess

checkAccess()

借助安全管理器进行权限的检验的封装

确保当前运行的线程是否有权修改此线程组

线程相关类

ThreadLocal类

​ ThreadLocalVariable(线程局部变量),为每一个使用该变量的线程提供一个变量值的副本,使得每一个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量一样。

方法

T get():返回此线程局部变量中当前线程副本中的值

void remove() :删除此线程局部变量中当前线程的值

void set(T value):设置此线程局部变量中当前线程副本中的值

用途

​ ThreadLocal和其他同步机制一样,都是为了解决多线程中对同一变量的访问冲突,比起上锁,ThreadLocal从另一个角度来解决多线程的并发访问,将需要并发访问的资源复制多份,每个线程拥有一份资源,每个线程都有自己的资源副本。在编写多线程代码时,可以把不安全的整个变量封装进ThreadLocal,或者把该对象与线程相关的状态使用ThreadLocal保存。

通常需要线程通信使用同步机制,仅仅隔离共享冲突则使用ThreadLocal。

包装线程不安全的集合类

​ ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等都是线程不安全的。

如果要保证线程安全,可以使用Collections提供的类方法把这些集合包装成线程安全的集合。

方法
//诸如此类 获取包装好的线程安全类
HashMap map = Collections.synchronizedMap(new HashMap());
线程安全的集合类

JUC包提供了大量的集合类,此处我会在JUC笔记记录。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值