05.java多线程问题

39次阅读

共计 14193 个字符,预计需要花费 36 分钟才能阅读完成。

目录介绍

5.0.0.1 线程池具有什么优点和缺点?为什么说开启大量的线程, 会降低程序的性能,那么该如何做才能降低性能?
5.0.0.3 线程中 start 和 run 方法有什么区别?wait 和 sleep 方法的不同?sleep()、join()、yield()有什么区别?
5.0.0.4 用 Java 手写一个会导致死锁的程序,遇到这种问题解决方案是什么?那些场景用到了死锁机制?
5.0.0.5 ThreadLocal(线程变量副本)这个类的作用是什么?
5.0.0.6 什么是线程安全?线程安全有那几个级别?保障线程安全有哪些手段?ReentrantLock 和 synchronized 的区别?
5.0.0.7 Volatile 和 Synchronized 各自用途是什么?有哪些不同点?Synchronize 在编译时如何实现锁机制?
5.0.0.8 wait()和 sleep()的区别?各自有哪些使用场景?怎么唤醒一个阻塞的线程?Thread.sleep(0)的作用是啥?
5.0.0.9 同步和非同步、阻塞和非阻塞的概念?分别有哪些使用场景?
5.0.1.0 线程的有哪些状态?请绘制该状态的流程图?讲一下线程的执行生命周期流程?线程如果出现了运行时异常会怎么样?
5.0.1.1 synchronized 锁什么?synchronized 同步代码块还有同步方法本质上锁住的是谁?为什么?
5.0.1.2 Volatile 实现原理?一个 int 变量,用 volatile 修饰,多线程去操作 ++,线程安全吗?那如何才能保证 i ++ 线程安全?
5.0.1.3 CAS 原理是什么?CAS 实现原子操作会出现什么问题?
5.0.1.4 假如有 n 个网络线程,需要当 n 个网络线程完成之后,再去做数据处理,你会怎么解决?
5.0.1.5 Runnable 接口和 Callable 接口的区别?
5.0.1.6 如果提交任务时,线程池队列已满,这时会发生什么?线程调度算法是什么?
5.0.1.7 什么是乐观锁和悲观锁?
5.0.1.8 线程类的构造方法、静态块是被哪个线程调用的?同步方法和同步块,哪个是更好的选择?同步的范围越少越好吗?
5.0.1.9 synchonized(this)和 synchonized(object)区别?Synchronize 作用于方法和静态方法区别?

好消息

博客笔记大汇总【15 年 10 月到至今】,包括 Java 基础及深入知识点,Android 技术博客,Python 学习笔记等等,还包括平时开发中遇到的 bug 汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是 markdown 格式的!同时也开源了生活博客,从 12 年起,积累共计 500 篇[近 100 万字],将会陆续发表到网上,转载请注明出处,谢谢!
链接地址:https://github.com/yangchong2…
如果觉得好,可以 star 一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!所有博客将陆续开源到 GitHub!

5.0.0.1 线程池具有什么优点和缺点?为什么说开启大量的线程, 会降低程序的性能,那么该如何做才能降低性能?

线程池好处:

1)降低资源消耗;
2)提高相应速度;
3)提高线程的可管理性。技术博客大总结

线程池的实现原理:

当提交一个新任务到线程池时,判断核心线程池里的线程是否都在执行。如果不是,则创建一个新的线程执行任务。如果核心线程池的线程都在执行任务,则进入下个流程。
判断工作队列是否已满。如果未满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
判断线程池是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果满了,则交给饱和策略来处理这个任务。

5.0.0.3 线程中 start 和 run 方法有什么区别?wait 和 sleep 方法的不同?sleep()、join()、yield()有什么区别?

线程中 start 和 run 方法有什么区别
为什么我们调用 start()方法时会执行 run()方法,为什么我们不能直接调用 run()方法?这是一个非常经典的 java 多线程面试问题。当你调用 start()方法时你将创建新的线程,并且执行在 run()方法里的代码。但是如果你直接调用 run()方法,它不会创建新的线程也不会执行调用线程的代码。

wait 和 sleep 方法的不同
最大的不同是在等待时 wait 会释放锁,而 sleep 一直持有锁。Wait 通常被用于线程间交互,sleep 通常被用于暂停执行。

1、sleep()方法

在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。让其他线程有机会继续执行,但它并不释放对象锁。也就是如果有 Synchronized 同步块,其他线程仍然不能访问共享数据。注意该方法要捕获异常
比如有两个线程同时执行 (没有 Synchronized),一个线程优先级为 MAX_PRIORITY,另一个为 MIN_PRIORITY,如果没有 Sleep() 方法,只有高优先级的线程执行完成后,低优先级的线程才能执行;但当高优先级的线程 sleep(5000)后,低优先级就有机会执行了。
总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。

2、yield()方法技术博客大总结
yield()方法和 sleep()方法类似,也不会释放“锁标志”,区别在于,它没有参数,即 yield()方法只是使当前线程重新回到可执行状态,所以执行 yield()的线程有可能在进入到可执行状态后马上又被执行,另外 yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也和 sleep()方法不同。

3、join()方法

Thread 的非静态方法 join()让一个线程 B“加入”到另外一个线程 A 的尾部。在 A 执行完毕之前,B 不能工作。
Thread t = new MyThread(); t.start(); t.join(); 保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。

Thread 的 join()有什么作用?
Thread 的 join()的含义是等待该线程终止,即将挂起调用线程的执行,直到被调用的对象完成它的执行。比如存在两个线程 t1 和 t2,下述代码表示先启动 t1,直到 t1 的任务结束,才轮到 t2 启动。
t1.start();
t1.join();
t2.start();

5.0.0.4 用 Java 手写一个会导致死锁的程序,遇到这种问题解决方案是什么?那些场景用到了死锁机制?

死锁是怎么一回事
线程 A 和线程 B 相互等待对方持有的锁导致程序无限死循环下去。

深入理解死锁的原理

两个线程里面分别持有两个 Object 对象:lock1 和 lock2。这两个 lock 作为同步代码块的锁;
线程 1 的 run()方法中同步代码块先获取 lock1 的对象锁,Thread.sleep(xxx),时间不需要太多,50 毫秒差不多了,然后接着获取 lock2 的对象锁。这么做主要是为了防止线程 1 启动一下子就连续获得了 lock1 和 lock2 两个对象的对象锁
线程 2 的 run)(方法中同步代码块先获取 lock2 的对象锁,接着获取 lock1 的对象锁,当然这时 lock1 的对象锁已经被线程 1 锁持有,线程 2 肯定是要等待线程 1 释放 lock1 的对象锁的

5.0.0.5 ThreadLocal(线程变量副本)这个类的作用是什么?

ThreadLocal 即线程变量

ThreadLocal 为每个线程维护一个本地变量。
采用空间换时间,它用于线程间的数据隔离,它为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。ThreadLocal 的实现是以 ThreadLocal 对象为键。任意对象为值得存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个 ThreadLocal 对象查询到绑定在这个线程上的一个值。

ThreadLocal 类是一个 Map

ThreadLocal 类中维护一个 Map,用于存储每一个线程的变量副本,Map 中元素的键为线程对象,而值为对应线程的变量副本。
ThreadLocal 在 Spring 中发挥着巨大的作用,在管理 Request 作用域中的 Bean、事务管理、任务调度、AOP 等模块都出现了它的身影。
Spring 中绝大部分 Bean 都可以声明成 Singleton 作用域,采用 ThreadLocal 进行封装,因此有状态的 Bean 就能够以 singleton 的方式在多线程中正常工作了。

更多详细参考博客:深入研究 java.lang.ThreadLocal 类

5.0.0.6 什么是线程安全?线程安全有那几个级别?保障线程安全有哪些手段?ReentrantLock 和 synchronized 的区别?

什么是线程安全
线程安全就是当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。

线程安全也是有几个级别

技术博客大总结

不可变:
像 String、Integer、Long 这些,都是 final 类型的类,任何一个线程都改变不了它们的值,要改变除非新创建一个,因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用

绝对线程安全
不管运行时环境如何,调用者都不需要额外的同步措施。要做到这一点通常需要付出许多额外的代价,Java 中标注自己是线程安全的类,实际上绝大多数都不是线程安全的,不过绝对线程安全的类,Java 中也有,比方说 CopyOnWriteArrayList、CopyOnWriteArraySet

相对线程安全
相对线程安全也就是我们通常意义上所说的线程安全,像 Vector 这种,add、remove 方法都是原子操作,不会被打断,但也仅限于此,如果有个线程在遍历某个 Vector、有个线程同时在 add 这个 Vector,99% 的情况下都会出现 ConcurrentModificationException,也就是 fail-fast 机制。

线程非安全技术博客大总结
ArrayList、LinkedList、HashMap 等都是线程非安全的类.

保障线程安全有哪些手段。保证线程安全可从多线程三特性出发:

原子性(Atomicity):单个或多个操作是要么全部执行,要么都不执行

Lock:保证同时只有一个线程能拿到锁,并执行申请锁和释放锁的代码
synchronized:对线程加独占锁,被它修饰的类 / 方法 / 变量只允许一个线程访问

可见性(Visibility):当一个线程修改了共享变量的值,其他线程能够立即得知这个修改

volatile:保证新值能立即同步到主内存,且每次使用前立即从主内存刷新;
synchronized:在释放锁之前会将工作内存新值更新到主存中

有序性(Ordering):程序代码按照指令顺序执行

volatile:本身就包含了禁止指令重排序的语义
synchronized:保证一个变量在同一个时刻只允许一条线程对其进行 lock 操作,使得持有同一个锁的两个同步块只能串行地进入

ReentrantLock 和 synchronized 的区别

ReentrantLock 与 synchronized 的不同在于 ReentrantLock:

等待可中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。
公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。而 synchronized 是非公平的,即在锁被释放时,任何一个等待锁的线程都有机会获得锁。ReentrantLock 默认情况下也是非公平的,但可以通过带布尔值的构造函数改用公平锁。
锁绑定多个条件:一个 ReentrantLock 对象可以通过多次调用 newCondition()同时绑定多个 Condition 对象。而在 synchronized 中,锁对象 wait()和 notify()或 notifyAl()只能实现一个隐含的条件,若要和多于一个的条件关联不得不额外地添加一个锁。

Synchronized 是悲观锁机制,独占锁。而 Locks.ReentrantLock 是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

ReentrantLock 适用场景
某个线程在等待一个锁的控制权的这段时间需要中断
需要分开处理一些 wait-notify,ReentrantLock 里面的 Condition 应用,能够控制 notify 哪个线程,锁可以绑定多个条件。
具有公平锁功能,每个到来的线程都将排队等候。

更多详细参考博客:Lock 与 synchronized 的区别

5.0.0.7 Volatile 和 Synchronized 各自用途是什么?有哪些不同点?Synchronize 在编译时如何实现锁机制?

Volatile 和 Synchronized 各自用途是什么?有哪些不同点?

1 粒度不同,前者针对变量,后者锁对象和类
2 syn 阻塞,volatile 线程不阻塞
3 syn 保证三大特性,volatile 不保证原子性

4 syn 编译器优化,volatile 不优化 volatile 具备两种特性:

1. 保证此变量对所有线程的可见性,指一条线程修改了这个变量的值,新值对于其他线程来说是可见的,但并不是多线程安全的。
2. 禁止指令重排序优化。

Volatile 如何保证内存可见性:

1. 当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量刷新到主内存。
2. 当读一个 volatile 变量时,JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

同步:就是一个任务的完成需要依赖另外一个任务,只有等待被依赖的任务完成后,依赖任务才能完成。
异步:不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,只要自己任务完成了就算完成了,被依赖的任务是否完成会通知回来。(异步的特点就是通知)。打电话和发短信来比喻同步和异步操作。
阻塞:CPU 停下来等一个慢的操作完成以后,才会接着完成其他的工作。
非阻塞:非阻塞就是在这个慢的执行时,CPU 去做其他工作,等这个慢的完成后,CPU 才会接着完成后续的操作。
非阻塞会造成线程切换增加,增加 CPU 的使用时间能不能补偿系统的切换成本需要考虑。

Synchronize 在编译时如何实现锁机制?
Synchronized 进过编译,会在同步块的前后分别形成 monitorenter 和 monitorexit 这个两个字节码指令。在执行 monitorenter 指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计算器加 1,相应的,在执行 monitorexit 指令时会将锁计算器就减 1,当计算器为 0 时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释放为止。

5.0.0.8 wait()和 sleep()的区别?各自有哪些使用场景?怎么唤醒一个阻塞的线程?Thread.sleep(0)的作用是啥?

sleep 来自 Thread 类,和 wait 来自 Object 类

调用 sleep()方法的过程中,线程不会释放对象锁。而调用 wait 方法线程会释放对象锁
sleep 睡眠后不出让系统资源,wait 让出系统资源其他线程可以占用 CPU
sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒

通俗解释
Java 程序中 wait 和 sleep 都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而 sleep()方法仅仅释放 CPU 资源或者让当前线程停止执行一段时间,但不会释放锁。

怎么唤醒一个阻塞的线程?

如果线程是因为调用了 wait()、sleep()或者 join()方法而导致的阻塞,可以中断线程,并且通过抛出 InterruptedException 来唤醒它;如果线程遇到了 IO 阻塞,无能为力,因为 IO 是操作系统实现的,Java 代码并没有办法直接接触到操作系统。
技术博客大总结

Thread.sleep(0)的作用是啥?
由于 Java 采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到 CPU 控制权的情况,为了让某些优先级比较低的线程也能获取到 CPU 控制权,可以使用 Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡 CPU 控制权的一种操作。

5.0.0.9 同步和非同步、阻塞和非阻塞的概念?分别有哪些使用场景?

同步和非同步
同步和异步体现的是消息的通知机制:所谓同步,方法 A 调用方法 B 后必须等到方法 B 返回结果才能继续后面的操作;所谓异步,方法 A 调用方法 B 后可让方法 B 在调用结束后通过回调等方式通知方法 A

阻塞和非阻塞
阻塞和非阻塞侧重于等待消息时的状态:所谓阻塞,就是在结果返回之前让当前线程挂起;所谓非阻塞,就是在等待时可做其他事情,通过轮询去询问是否已返回结果

5.0.1.0 线程的有哪些状态?请绘制该状态的流程图?讲一下线程的执行生命周期流程?线程如果出现了运行时异常会怎么样?

在任意一个时间点,一个线程只能有且只有其中的一种状态

新建(New):线程创建后尚未启动
技术博客大总结
运行(Runable):包括正在执行(Running)和等待着 CPU 为它分配执行时间(Ready)两种

无限期等待(Waiting):该线程不会被分配 CPU 执行时间,要等待被其他线程显式地唤醒。以下方法会让线程陷入无限期等待状态:
“`
没有设置 Timeout 参数的 Object.wait()
没有设置 Timeout 参数的 Thread.join()
LockSupport.park()
“`
– 限期等待(Timed Waiting):该线程不会被分配 CPU 执行时间,但在一定时间后会被系统自动唤醒。以下方法会让线程进入限期等待状态:
“`
Thread.sleep()
设置了 Timeout 参数的 Object.wai()
设置了 Timeout 参数的 Thread.join()
LockSupport.parkNanos()
LockSupport.parkUntil()
“`
– 阻塞(Blocked):线程被阻塞。和等待状态不同的是,阻塞状态表示在等待获取到一个排他锁,在另外一个线程放弃这个锁的时候发生;而等待状态表示在等待一段时间或者唤醒动作的发生,在程序等待进入同步区域的时候发生。
– 结束(Terminated):线程已经结束执行

绘制该状态的流程图

线程如果出现了运行时异常会怎么样?
如果这个异常没有被捕获的话,这个线程就停止执行了。另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放

5.0.1.1 synchronized 锁什么?synchronized 同步代码块还有同步方法本质上锁住的是谁?为什么?

synchronized 锁什么

对于普通同步方法,锁是当前实例对象;
对于静态同步方法,锁是当前类的 Class 对象;
对于同步方法块,锁是括号中配置的对象;
当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。synchronized 用的锁是存在 Java 对象头里的 MarkWord,通常是 32bit 或者 64bit,其中最后 2bit 表示锁标志位。

本质上锁住的是对象。
在 java 虚拟机中,每个对象和类在逻辑上都和一个监视器相关联,synchronized 本质上是对一个对象监视器的获取。当执行同步代码块或同步方法时,执行方法的线程必须先获得该对象的监视器,才能进入同步代码块或同步方法;而没有获取到的线程将会进入阻塞队列,直到成功获取对象监视器的线程执行结束并释放锁后,才会唤醒阻塞队列的线程,使其重新尝试对对象监视器的获取。

5.0.1.2 Volatile 实现原理?一个 int 变量,用 volatile 修饰,多线程去操作 ++,线程安全吗?那如何才能保证 i ++ 线程安全?

volatile 的作用和原理

Java 代码在编译后会变成 Java 字节码,字节码被类加载器加载到 JVM 里,JVM 执行字节码,最终需要转化为汇编指令在 CPU 上执行。
volatile 是轻量级的 synchronized(volatile 不会引起线程上下文的切换和调度),它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
由于内存访问速度远不及 CPU 处理速度,为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存后在进行操作,但操作完不知道何时会写到内存。普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。如果对声明了 volatile 的变量进行写操作,JVM 就会想处理器发送一条 Lock 前缀的指令,表示将当前处理器缓存行的数据写回到系统内存。

一个 int 变量,用 volatile 修饰,多线程去操作 ++,线程安全吗

技术博客大总结
不安全
案例代码,至于打印结果就不展示呢
volatile 只能保证可见性,并不能保证原子性。

i++ 实际上会被分成多步完成:

1)获取 i 的值;
2)执行 i +1;
3)将结果赋值给 i。

volatile 只能保证这 3 步不被重排序,多线程情况下,可能两个线程同时获取 i,执行 i +1,然后都赋值结果 2,实际上应该进行两次 + 1 操作。

private volatile int a = 0;
for (int x=0 ; x<=100 ; x++){
new Thread(new Runnable() {
@Override
public void run() {
a++;
Log.e(“ 小杨逗比 Thread————-“,””+a);
}
}).start();
}

如何才能保证 i ++ 线程安全
可以使用 java.util.concurrent.atomic 包下的原子类,如 AtomicInteger。其实现原理是采用 CAS 自旋操作更新值。
for (int x=0 ; x<=100 ; x++){
new Thread(new Runnable() {
@Override
public void run() {
AtomicInteger atomicInteger = new AtomicInteger(a++);
int i = atomicInteger.get();
Log.e(“ 小杨逗比 Thread————-“,””+i);
}
}).start();
}

5.0.1.3 CAS 原理是什么?CAS 实现原子操作会出现什么问题?

CAS 原理是什么
CAS 即 compare and swap 的缩写,中文翻译成比较并交换。CAS 有 3 个操作数,内存值 V,旧的预期值 A,要修改的新值 B。当且仅当预期值 A 和内存值 V 相同时,将内存值 V 修改为 B,否则什么都不做。自旋就是不断尝试 CAS 操作直到成功为止。

CAS 实现原子操作会出现什么问题

ABA 问题。因为 CAS 需要在操作之的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是 A,变成,有变成 A,那么使用 CAS 进行检查时会发现它的值没有发生变化,但实际上发生了变化。ABA 问题可以通过添加版本号来解决。Java 1.5 开始,JDK 的 Atomic 包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。
循环时间长开销大。pause 指令优化。
只能保证一个共享变量的原子操作。可以合并成一个对象进行 CAS 操作。

5.0.1.4 假如有 n 个网络线程,需要当 n 个网络线程完成之后,再去做数据处理,你会怎么解决?
多线程同步的问题。这种情况可以可以使用 thread.join();join 方法会阻塞直到 thread 线程终止才返回。更复杂一点的情况也可以使用 CountDownLatch,CountDownLatch 的构造接收一个 int 参数作为计数器,每次调用 countDown 方法计数器减一。做数据处理的线程调用 await 方法阻塞直到计数器为 0 时。
5.0.1.5 Runnable 接口和 Callable 接口的区别?

Runnable 接口和 Callable 接口的区别

Runnable 接口中的 run()方法的返回值是 void,它做的事情只是纯粹地去执行 run()方法中的代码而已;Callable 接口中的 call()方法是有返回值的,是一个泛型,和 Future、FutureTask 配合可以用来获取异步执行的结果。
这其实是很有用的一个特性,因为多线程相比单线程更难、更复杂的一个重要原因就是因为多线程充满着未知性,某条线程是否执行了?某条线程执行了多久?某条线程执行的时候我们期望的数据是否已经赋值完毕?无法得知,我们能做的只是等待这条多线程的任务执行完毕而已。而 Callable+Future/FutureTask 却可以获取多线程运行的结果,可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务,真的是非常有用。

5.0.1.6 如果提交任务时,线程池队列已满,这时会发生什么?线程调度算法是什么?

如果提交任务时,线程池队列已满,这时会发生什么?

如果使用的是无界队列 LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为 LinkedBlockingQueue 可以近乎认为是一个无穷大的队列,可以无限存放任务
技术博客大总结
如果使用的是有界队列比如 ArrayBlockingQueue,任务首先会被添加到 ArrayBlockingQueue 中,ArrayBlockingQueue 满了,会根据 maximumPoolSize 的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue 继续满,那么则会使用拒绝策略 RejectedExecutionHandler 处理满了的任务,默认是 AbortPolicy

线程调度算法是什么?
抢占式。一个线程用完 CPU 之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

5.0.1.7 什么是乐观锁和悲观锁?

什么是乐观锁和悲观锁?

乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较 - 替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像 synchronized,直接上了锁就操作资源。

5.0.1.8 线程类的构造方法、静态块是被哪个线程调用的?同步方法和同步块,哪个是更好的选择?同步的范围越少越好吗?

线程类的构造方法、静态块是被哪个线程调用的?
线程类的构造方法、静态块是被 new 这个线程类所在的线程所调用的,而 run 方法里面的代码才是被线程自身所调用的。

举个例子

假设 Thread2 中 new 了 Thread1,main 函数中 new 了 Thread2,那么:

Thread2 的构造方法、静态块是 main 线程调用的,Thread2 的 run()方法是 Thread2 自己调用的
Thread1 的构造方法、静态块是 Thread2 调用的,Thread1 的 run()方法是 Thread1 自己调用的

同步方法和同步块,哪个是更好的选择?

同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率。请知道一条原则:同步的范围越小越好。
技术博客大总结

同步的范围越少越好吗?
是的。虽说同步的范围越少越好,但是在 Java 虚拟机中还是存在着一种叫做锁粗化的优化方法,这种方法就是把同步范围变大。这是有用的,比方说 StringBuffer,它是一个线程安全的类,自然最常用的 append()方法是一个同步方法,我们写代码的时候会反复 append 字符串,这意味着要进行反复的加锁 -> 解锁,这对性能不利,因为这意味着 Java 虚拟机在这条线程上要反复地在内核态和用户态之间进行切换,因此 Java 虚拟机会将多次 append 方法调用的代码进行一个锁粗化的操作,将多次的 append 的操作扩展到 append 方法的头尾,变成一个大的同步块,这样就减少了加锁 –> 解锁的次数,有效地提升了代码执行的效率。

5.0.1.9 synchonized(this)和 synchonized(object)区别?Synchronize 作用于方法和静态方法区别?

synchonized(this)和 synchonized(object)区别技术博客大总结

其实并没有很大的区别,synchonized(object)本身就包含 synchonized(this)这种情况,使用的场景都是对一个代码块进行加锁,效率比直接在方法名上加 synchonized 高一些(下面分析),唯一的区别就是对象的不同。

对 synchronized(this)的一些理解

一、当两个并发线程访问同一个对象 object 中的这个 synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
二、然而,当一个线程访问 object 的一个 synchronized(this)同步代码块时,另一个线程仍然可以访问该 object 中的非 synchronized(this)同步代码块。
三、尤其关键的是,当一个线程访问 object 的一个 synchronized(this)同步代码块时,其他线程对 object 中所有其它 synchronized(this)同步代码块的访问将被阻塞。
四、当一个线程访问 object 的一个 synchronized(this)同步代码块时,它就获得了这个 object 的对象锁。结果,其它线程对该 object 对象所有同步代码部分的访问都被暂时阻塞。

Synchronize 作用于方法和静态方法区别
测试代码如下所示
private void test() {
final TestSynchronized test1 = new TestSynchronized();
final TestSynchronized test2 = new TestSynchronized();
Thread t1 = new Thread(new Runnable() {

@Override
public void run() {
test1.method01(“a”);
//test1.method02(“a”);
}
});
Thread t2 = new Thread(new Runnable() {

@Override
public void run() {
test2.method01(“b”);
//test2.method02(“a”);
}
});
t1.start();
t2.start();
}

private static class TestSynchronized{
private int num1;
public synchronized void method01(String arg) {
try {
if(“a”.equals(arg)){
num1 = 100;
System.out.println(“tag a set number over”);
Thread.sleep(1000);
}else{
num1 = 200;
System.out.println(“tag b set number over”);
}
System.out.println(“tag = “+ arg + “;num =”+ num1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

private static int num2;
public static synchronized void method02(String arg) {
try {
if(“a”.equals(arg)){
num2 = 100;
System.out.println(“tag a set number over”);
Thread.sleep(1000);
}else{
num2 = 200;
System.out.println(“tag b set number over”);
}
System.out.println(“tag = “+ arg + “;num =”+ num2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

// 调用 method01 方法打印日志【普通方法】
tag a set number over
tag b set number over
tag = b;num =200
tag = a;num =100

// 调用 method02 方法打印日志【static 静态方法】
tag a set number over
tag = a;num =100
tag b set number over
tag = b;num =200

在 static 方法前加 synchronized:静态方法属于类方法,它属于这个类,获取到的锁,是属于类的锁。
在普通方法前加 synchronized:非 static 方法获取到的锁,是属于当前对象的锁。技术博客大总结

结论:类锁和对象锁不同,synchronized 修饰不加 static 的方法,锁是加在单个对象上,不同的对象没有竞争关系;修饰加了 static 的方法,锁是加载类上,这个类所有的对象竞争一把锁。

其他介绍
01. 关于博客汇总链接

1. 技术博客汇总

2. 开源项目汇总

3. 生活博客汇总

4. 喜马拉雅音频汇总

5. 其他汇总

02. 关于我的博客

我的个人站点:www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211

知乎:https://www.zhihu.com/people/…

简书:http://www.jianshu.com/u/b7b2…

csdn:http://my.csdn.net/m0_37700275

喜马拉雅听书:http://www.ximalaya.com/zhubo…

开源中国:https://my.oschina.net/zbj161…

泡在网上的日子:http://www.jcodecraeer.com/me…

邮箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a… 239.headeruserinfo.3.dT4bcV
segmentfault 头条:https://segmentfault.com/u/xi…

掘金:https://juejin.im/user/593943…

正文完
 0