【秋招冲刺篇】5.20的正确操作===操作系统

一,产生线程不安全的原因

1.线程是抢占式执行
导致两个线程里的先后顺序无法确定~
这样的随机性,是由操作系统内核决定的,是导致线程安全问题的根本所在。

2.多个线程修改同一个变量:

3.原子性
像++这样的操作就不是原子性的操作,我们可以加锁。
赋值或者一个步骤的操作是原子的
4.内存可见性
单线程下没有什么影响,但是到了多线程,就会引发不安全,
假设:
如果某个线程进行循环自增,那么涉及到多次LOAD,ADD,SAVE。
为了使结果读的更快,提高程序的效率,线程会把多次的LOAD和SAVE省略。变成了LOAD,ADD,ADD,SAVE,此时我另外一个线程去读取这个值,会发现值没有改变,是因为线程2还在add,值还在cpu里面。
(解决方法加上volatile,可以防止优化操作)
5.指令重排序
编译器会按照一个较为节省时间(或资源的路径去执行)

二.线程的所有状态

NEW:安排了,还没有开始工作
RUNNABLE:可工作的,又可以分为正在工作,和即将开始工作。
BLOCKED和WAITING:这几个都表示排队等待。
TERMINATED:工作完成了。

三, synchronized和volatile

synchronized:(既可以原子性,又可以保证内存可见性)
的本质功能,就是把并行变成串行,通过加锁,
锁竞争多个线程争同一个锁。
当然,为了防止程序员犯蠢,synchronized内部记录了这个锁是哪个线程所持有的。
synchronized修饰普通方法相当于,对this进行加锁,
synchronized修饰静态方法,相当于对类对象进行加锁。
内寸可见性:直接对内存进行操作。

volatile
往往一个线程读一个线程写会用到这个volatile
计算机中一般理解成可变的,用来保证内存可见性,但不能保证原子性

四.Wait和notify(都是Object方法)

举例:比如,亿万富翁小明去银行的ATM机去取钱,同样,在他身后还有一群人准备取钱,小明发现ATM钱不够,可是他又着急取钱,此时就需要我们的工作人员把他带走,去取钱,让后面的人先取钱。
如果,工作人员,直接放,无限多的钱到ATM机中,这些人又需要同时的去竞争这个ATM。

这就好比,从一个就绪队列转换到阻塞队列中去,

Wait 的作用
1.将当前代码执行的线程,放到等待队列中。
2.释放当前锁
3. 满足一定条件被唤醒,重新获取到锁。

在这里插入图片描述
结果是 Wait之前证明是在Wait之前就结束了

Wait的使用

Wait的使用必须要在synchronized中且调用Wait的锁对象是同一个才可以使用
这个条件之一就是 Notify,有了Notify才能重新获取到锁。

Notify和NotifyAll
一个是唤醒Wait的线程,另外一个全部线程唤醒。

Wait和Sleep的区别
1.等待时间
Sleep可以指定一个固定时间进行阻塞等待,
Wait既可以指定时间,也可以无限等待。
2.唤醒方式
Wait使用notify来唤醒,而sleep等到时间到了或者interrup来唤醒
3.用途
Wait是用来调整线程的先后顺序,Sleep单纯让某一线程休眠,并不涉及多线程(虽然Sleep也能进行顺序控制,但是这种控制不可靠。)

五.单例模式以及单例模式的实现

(这里的设计模式就相当于棋谱一样,程序员按照相应的模式,来进行相应的操作)
懒汉模式:用到的时候才创建实例

public class Main {
   static class Singleton{
       //饿汉模式
      private   Singleton(){

      }
      private static Singleton instance = null;

      public  static  Singleton getInstance(){
          if(instance==null){
              instance =new Singleton();
          }
          return instance;
      }

   }

    public static void main(String[] args) {
      Singleton instance =   Singleton.getInstance();
    }
}

我们仔细看这个代码会发现问题,当这个代码的main中没有实例的
instance的话,直接调用getInstance()方法会出错,涉及到线程安全问题。
所以我们需要把这个判断全部加上锁,保证这两个操作都是原子性的
(但是锁会导致资源开销变大,所以再进行判断,目的要让第一次成为线程安全的)

if(instance==null){
synchronized(Singleton.class)
if(instance==null){
              instance =new Singleton();
          }
          return instance;
      }
}

饿汉模式:开始的时候直接创建实例

public class Main {
   static class Singleton{
       //饿汉模式
      private   Singleton(){

      }
      private static Singleton instance = new Singleton();

      public  static  Singleton getInstance(){
          return instance;
      }

   }
}

六.队列的分类

都知道有栈和对列,但是实际上对列才是最常用的,栈不常用。
这里的对列又分为。
优先队列:按优先级先进先出的对列

消息队列:对列里的数据有一定的分类信息,出队列的时候,不是单纯的先进先出,而是按照分类作为维度,让某个类的元素先进先出。
详细用到的场景:医院里面 做检查有很多类型。

阻塞对列:阻塞对列和以上两种对列不同,阻塞队列不为空,线程是安全的。
从以下两方面来谈
当对列为空,我们出对列会发生阻塞,直到有元素为止。
当对列满了,入队列会发生阻塞,直到有元素出队列为止。
有了阻塞对列就可以实现生产者消费者模型。
在计算机中,生产者是一组线程,消费者也是一组线程
判断一个代码是否好的标准指的是看代码,是否是高内聚,低耦合
低耦合指的是两个代码片段关系是否,比较紧密。
用这个模型可以有效的解耦合,
1.解耦合
因为假设A要给B传输数据,只能a和b传,到时候我想让a传给c,需要该大量的代码。
因此我们使用生产者消费者模型,在a和b的中间加入一个阻塞队列。a把数据产生到对列,b从对列里面拿数据。这样就让耦合降低了。
2.肖峰填谷
有了大坝我们的水就可以得到有效的保护,避免水流太大。
把高峰填到低谷期。
当用户一大批需求冲入网关(服务器入口)会有一个阻塞队列将需求缓存,然后被具体服务器接受。避免服务器崩溃。
在这里插入图片描述

线程池

多进程是解决并发编程的方案,但是进程有点太重量了。(创建和销毁开销比较大)因此引入了线程,即便如此,频繁的线程的创建与销毁,还是会浪费资源。
因此有两种解决方法,
1.引入协程
2.引入线程池
线程池,指的是使用线程的时候,将提前创建好的线程,放在一个池子里,当我们需要线程的时候,直接从池子里面选取一个线程,当我们不用的话在放到池子里去。(这种操作全部在用户态进行就比较高效)
而创建和销毁线程,涉及到用户态和内核态的切换就比较低效了。

用户态和内核态

用户态就是应用程序执行的代码
内核态就是操作系统执行的代码
一般认为,用户态和内核态切换是一个开销比较大的操作
举例子:就好比我们去银行取钱,我们去取钱的话效率就比较高效,而直接让工作人员去取,效率就会变得低效,因为你是着急用的,而工作人员不着急,而是按部就班的取钱。自己取就是用户态,而给工作人员取就是用户态给内核态。
因此一旦把某个任务交给内核去做,啥时候事情可以做好,就非常难把握了。

ThreadPoolExecutor的构造方法参数都是啥意思

ThreadPoolExecutor里面包含的线程不是一成不变的,是能够根据任务量来自适应的,如果任务比较多,就会多创建一些线程,如果任务比较少,就少创建一些线程。
corePoolSize 核心线程数
maxmumPoolSize 最大线程数

  • 19
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷的快乐人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值