一、简介:常见的并发编程的三要素
考点:是否知道 并发编程的3要素
1、并发编程三要素是否知道,能否费别解释下,举个例子?
①、原子性:一个不可再分割的颗粒,原子性指的是一个或多个操作要么全部执行成功要么全部执行失败,期间不能被中断,也不存在上下文切换,线程切换也会带来原子性的问题。
好比数据库做的事务问题,事务里面也是要求原子性的,要么成功,要么失败,一起成功,一起失败。
例如:int num=1;//原子操作
num++ ;//非原子操作,可能会出现多个线程同时再操作这个变量的问题,前面写的volatile的时候就是不保证原子性的。首先是从主内存读取num到线程的工作内存,进行+1,再把num写到主内存,除非用原子 类,即java.util.concurrent.atomic里的原子变量类。
解决办法:可以用synchronized或lock(比如ReentrantLock)来把这个多步骤操作“变成”原子操作,但是volatile不能修饰有依赖值的情况。
核心思想:把一个方法或者代码块看做一个整体,保证是一个不可分割的
②、有序性:程序执行的顺序是按照代码的先后顺序执行,因为处理器可能会对指令进行 重排,JVM在编译java代码或者CPU执行JVM字节码时,对现有的指令进行重新排序,主要目的是优化运行效率(不改变程序的结果的前提),也就是单线程的情况下
int a=3 //1
int b=4 //2
int c =5 //3
int h=a*b*c //4
上面的例子,执行顺序1,2,3,4和2,1,3,4结果都是一样的,指令重排序可以提高执行效率,但是多线程上可能会影响结果。
假如:下面的场景
//线程1
before(); //处理初始化工作,处理完成后才可以正式的运行下面的run方法
flag=true;//标记资源处理好了,如果资源没处理还,此程序就可能出现问题
//线程2
while(flag){
run();//核心业务代码
}
指令重排排序后,导致顺序换了,程序出现问题,且难排查
//线程1
flag=true;//标记资源处理好了,如果资源没处理还,此程序就可能出现问题
//线程2
while(flag){
run();//核心业务代码
}
before(); //处理初始化工作,处理完成后才可以正式的运行下面的run方法
解决办法:加锁,volatile
可见性:一个线程A对共享变量的修改,另一个线程B能够立刻看到
//线程A执行
int num=0;
//线程A执行
num++;
//线程B执行
System.out.println("num的值:"+num)
线程A执行 i++后,再执行线程B,线程B可能有两个结果,可能为0和1
因为i++在线程A中执行运算,并没有立刻更新到主内存当中,而线程B就去主内存当中读取并打印,此时打印的就是0,也可能线程A执行完成更新到主内存了,线程B的值为1.
所以需要保证线程的可见性。synchronized,lock,volatile保证线程的可见性 。
二、简介:常见的进程,线程间调度算法
考点:考查是否知道常见的进程和线程间的调度算法
一个cpu中,图中的每一个方框代表一个任务

1、说下你知道的调度算法,比如进程间的调度:
①、先来先服务调度算法:谁先来谁先处理
按照作业/进程到达的先后顺序进行调度,即:优先考虑在系统中等待时间最长的作业
排在长进程后的短进程的等待时间长,不利于短作业/进程。
长任务:处理几十分钟以上,短任务只需要几十秒
如果是先来先服务的调度算法的话,第一个先被处理,后面进来的就慢慢被处理。
②、短作业优先调度算法:要求服务时间短,任务处理的时间短,而且这个任务在大部分的作业里面占多数的。这个时候就可以采用短作业优先调度算法。如果 在系统中产生了很多的任务,每个任务处理的时间都是很短的话,比如几秒就可以处理完成,那么就可以采用短作业优先调度算法,就可以使这些短的作业优先被处理。但是如果有些时间比较长的作业,那么它就排在了后面。这种就是对长让任务不友好。
③、高响应比优先调度算法:每次调度任务的时候,它会计算每个任务的优先权。比如一个任务/进程进来需要等待cpu分配时间片,还有这个任务具体的处理时间。可以把这两个时间看作为响应时间,需要处理等待服务处理的时间,优先权就是响应比,然后根据这个比例排优先级。

缺点:需要计算优先权信息,增加了系统的开销
④、时间片轮转调度算法:轮流为每个进程做服务。让每个任务都可以在一定的时间内得到响应。缺点:需要高频的切换,也不能区分任务的紧急程度,因为每个任务是轮询的处理。
⑤、优先级调度算法:它可以根据任务的紧急程度来调用的。高优先级,优先处理。低优先级的,后处理。缺点:就是低优先级的很慢的才被处理。先进先出的思想。
2、常见的线程间的调度算法是怎么样的,java是哪种
线程调度是指系统为线程分配CPU使用权的过程,主要分两种
①、协同式线程调度(分时调度模式):线程执行时间由线程本身来控制,线程把自己的工作执行完成之后,要主动的通知系统切换到另外一个线程上,最大的好处是实现简单,且切换操作对线程自己是可知的,没啥线程同步问题。坏处是线程执行时间不可控制,如果一个线程有问题,可能一直阻塞在哪里。
类似生产者和消费者,我生产一个,你消费一个,不断生产,不断消费。
②、抢占式线程调度:每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定(Java中,Thread.yield()可以让出执行时间,但无法获取执行的时间).线程执行时间系统可控,也不会有一个线程导致整个进程阻塞。
Java中线程调度就是抢占式调度,优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那就随机选择一个线程。notify
所以我们如果希望某线程 多分配一些时间,给一些线程 少分配一些时间,可以通过设置线程优先级来完成,JAVA的线程的优先级,以1到10的整数指定,当多个线程可以运行时,JVM一般会运行最高优先级的线程。(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY)
注意:在两个线程同时处于就绪runnable状态时,优先级越高的线程越容易被系统系统执行,但是优先级100%可以优先获得,只不过是机会更大而已。
有人会说wait,notify不是线程本身控制吗?其实不是,不是协同式的调度,也是抢占式的调度。wait是可以让出执行时间,notify后无法获取执行时间,随机等待队列里面获取而已。




