[Java复习] 多线程 Multithreading
Q1多线程基础
进程和线程?
进程: 1. 一段程序执行过程,动态的,相对而言程序是静态的。(与类和对象的关系类似)
2. CPU资源分配最小单元,包括CPU调度和资源管理。
3. 一个进程可以有多个线程。
线程: 1. 程序执行流的最小单元。
2. CPU调度的最小单位,只负责CPU调度,不负责资源管理。
3. 可以拥有自己的堆栈,程序计数器和局部变量。
多线程:并不是多个线程一起执行,而是线程之间切换速度非常快,看起像不间断的执行。可以使同一个进程中可以同时并发处理多个任务。
并发(Concurrency)与并行(Parallel)?
并发:同一时刻只有一条指令执行,但多个线程的指令被快速切换执行,在宏观上具体有多个进程同时执行的效果。
并行:同一时刻多条指令在多个处理上同时执行。
高并发与多线程区别?
高并发包括硬件、网络、系统架构、开发语言的选取、数据结构的运用、算法优化、数据库优化等等,多线程只是其中的解决方法之一。
多线程对应的是CPU,高并发对应的是访问请求。
多线程是处理高并发的一种编程方法,即并发需要用多线程实现。
创建线程的几种方法?
1. 继承Thread类:重写run方法,调用对象引用的start方法进入Runnable状态。
2. 实现Runnable接口:重写run方法,创建Runnable实例,并作为Thread类的target来创建Thread对象。
3. 使用Callable和Future接口: 使用FutureTask类包装Callable实现类的对象,并且以FutureTask对象作为Thread对象的target来创建线程。
start()和run()的区别?
start() 方法则是 Thread类的方法,用来异步启动一个线程,然后主线程立刻返回。
该启动的线程不会马上运行,会放到等待队列中等待 CPU 调度,只有线程真正被 CPU 调度时才会调用 run() 方法执行。
所以 start() 方法只是标识线程为就绪状态的一个方法。
run()可重复调用,单独调用run会在当前线程执行,不会启新线程。
Object的wait, notify, notifyAll的理解?
wait:让持有该对象锁的线程等待;
notify: 唤醒任何一个持有该对象锁的线程;
notifyAll: 唤醒所有持有该对象锁的线程;
它们 3 个的关系是,调用对象的 wait 方法使线程暂停运行,通过 notify/ notifyAll 方法唤醒调用 wait 暂时的线程。
它们并不是 Thread 类中的方法,而是 Object 类中的,为什么呢!? 因为每个对象都有监视锁,线程要操作某个对象当然是要获取某个对象的锁了,而不是线程的锁。
注意点:
1、调用对象的 wait, notify, notifyAll 方法需要拥有对象的监视器锁,即它们只能在同步方法(块)中使用;
2、调用 wait 方法会使用线程暂停并让出 CPU 资源,同时释放持有的对象的锁;
3、多线程使用 notify 容易发生死锁,一般使用 notifyAll;
让步yield()的理解?
让当前线程由运行状态进入就绪状态,让其它线程获取执行权,但并不保证其它同优先级线程一定能获取执行权。
与wait区别:1. Yield是运行->就绪,Wait是运行->等待阻塞。2. Yield不会释放锁,wait要释放锁。
休眠sleep()的理解?
让线程从运行->休眠(阻塞)。
sleep()与wait()的区别?
1. sleep()属于Thread类,wait()属于Object类。
2. slee()线程不会释放锁,wait()线程会放弃对象锁。
Join()的理解?
让”主线程”(当前线程)等待“子线程”(join进来的线程)结束之后才能继续运行。
join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是:
程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕,
结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会。
终止线程的方式?
1. 终止处于阻塞状态的线程:
当线程由于被调用了sleep(), wait(), join()等方法而进入阻塞状态;若此时调用线程的interrupt()将线程的中断标记设为true。
由于处于阻塞状态,中断标记会被清除(即isInterrupted()会返回false),同时产生一个InterruptedException异常。
将InterruptedException放在适当的为止就能终止线程。
说明:在while(true)中不断的执行任务,当线程处于阻塞状态时,调用线程的interrupt()产生InterruptedException中断。中断的捕获在while(true)之外,这样就退出了while(true)循环!
@Override
public void run() {
try {
while (true) {
// 执行任务...
}
} catch (InterruptedException ie) {
// 由于产生InterruptedException异常,退出while(true)循环,线程终止!
}
}
2. 终止处于运行状态的线程:
1. java.lang.Thread#interrupt
中断目标线程,给目标线程发一个中断信号,线程被打上中断标记。
2. java.lang.Thread#isInterrupted()
判断目标线程是否被中断,不会清除中断标记。
3.java.lang.Thread#interrupted
判断目标线程是否被中断,会清除中断标记。
通过“标记”方式终止处于“运行状态”的线程。其中,包括“中断标记”和“额外添加标记”。
(01) 通过“中断标记”终止线程。
@Override
public void run() {
while (!isInterrupted()) {
// 执行任务...
}
}
说明:isInterrupted()是判断线程的中断标记是不是为true。当线程处于运行状态,并且我们需要终止它时;可以调用线程的interrupt()方法,使用线程的中断标记为true,即isInterrupted()会返回true。此时,就会退出while循环。
注意:interrupt()并不会终止处于“运行状态”的线程!它会将线程的中断标记设为true。
(02) 通过“额外添加标记”。
private volatile boolean flag= true;
protected void stopTask() {
flag = false;
} @Override
public void run() {
while (flag) {
// 执行任务...
}
}
说明:线程中有一个flag标记,它的默认值是true;并且我们提供stopTask()来设置flag标记。当我们需要终止该线程时,调用该线程的stopTask()方法就可以让线程退出while循环。
注意:将flag定义为volatile类型,是为了保证flag的可见性。即其它线程通过stopTask()修改了flag之后,本线程能看到修改后的flag的值。
非静态同步方法使用什么锁?
this锁
静态同步方法使用什么锁?
当前类的字节码文件
死锁和活锁?
死锁:多个线程因竞争资源而造成的一种僵局(互相等待的现象)。若无外力作用,他们都将无法推进下去。
线程1已经持有了A锁并想要获得B锁的同时,线程2持有B锁并尝试获取A锁,那么这两个线程将永远地等待下去。
活锁:为了彼此间的响应而相互礼让,使得没有一个线程能够继续前进,就发生活锁。
死锁的产生条件与避免?
死锁产生的4个必要条件:互斥条件、不可抢占条件、占有且申请条件和循环等待条件。
死锁的预防:只要破坏4个必要条件中的任意一个,死锁就不会发生。
预防常见方法:1.避免一个线程同时获取多个锁;2.避免一个线程在锁内同时占用多个资源;3.尝试使用定时锁,使用lock.tryLock来代替使用内置锁。
什么是伪共享以及如何解决?
定义:CPU缓存系统中是以缓存行(cache line)为单位存储的。目前主流的CPU Cache的Cache Line大小都是64Bytes。在多线程情况下,如果需要修改“共享同一个缓存行的变量”,就会无意中影响彼此的性能,这就是伪共享(False Sharing)。
例子:假设在多线程情况下,x,y两个共享变量在同一个缓存行中,核a修改变量x,会导致核b,核c中的x变量和y变量同时失效。
此时对于在核a上运行的线程,仅仅只是修改了了变量x,却导致同一个缓存行中的所有变量都无效,需要重新刷缓存。假设此时在核b上运行的线程,正好想要修改变量y,那么就会出现相互竞争,相互失效的情况,这就是伪共享。
Java对于伪共享的解决方案:
我们只需要填6个无用的长整型补上6*8=48字节, 让不同的VolatileLong对象处于不同的缓存行, 就可以避免伪共享了(64位系统超过缓存行的64字节也无所谓,只要保证不同线程不要操作同一缓存行就可以)。这个办法叫做补齐(Padding)。
Java8中新增了一个注解:@sun.misc.Contended。加上这个注解的类会自动补齐缓存行,需要注意的是此注解默认是无效的,需要在jvm启动时设置-XX:-RestrictContended才会生效。
Java内存模型(JMM)是什么?
内存模型:为保证共享内存的正确性(可见性、原子性、有序性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。
内存模型解决并发问题主要采用两种方式:限制处理器优化和使用内存屏障。
JMM是一种机制和规范。符合内存模型规范,屏蔽各种硬件和操作系统访问差异,保证Java程序在各平台下对内存的访问都能保证效果一致的机制和规范。
目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。
内存屏障是什么?
硬件层的内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。
内存屏障的作用:
1.阻止屏障两侧的指令重排序;
2.强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
为什么会有内存屏障(Memory barrier)?
每个CPU都会有自己的缓存(有的甚至L1,L2,L3),缓存的目的就是为了提高性能,避免每次都要向内存取。但是这样的弊端也很明显:不能实时的和内存发生信息交换,分在不同CPU执行的不同线程对同一个变量的缓存值不同。
用volatile关键字修饰变量可以解决上述问题,那么volatile是如何做到这一点的呢?那就是内存屏障,内存屏障是硬件层的概念,不同的硬件平台实现内存屏障的手段并不是一样,java通过屏蔽这些差异,统一由jvm来生成内存屏障的指令。
java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是上述两种的组合,完成一系列的屏障和数据同步功能。
什么是内存不可见性以及如何避免?
线程之间有互相独立的缓存, 当多个线程对共享数据进行操作时, 其操作彼此不可见。
例:A线程先读取共享变量a, B修改了共享变量a为a1, 推送给住内存并改写,但主内存不会推送给A线程,这样A线程和B线程的变量会不同步。
解决方案:1. synchronized关键字,互斥锁,在被锁代码块只能有一个线程访问共享变量。
2. volatile关键字, 会使主内存的共享变量每一次变更后都会推送给其他线程,其他线程会使用最新的变量副本。
volatile读写的内存语义?解决什么问题?
当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。
当读一个 volatile 变量时,JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
解决线程可见性问题。
volatile内存语义的实现?
当第二个操作为volatile写操作时,不管第一个操作是什么(普通读写或者volatile读写),都不能进行重排序。这个规则确保volatile写之前的所有操作都不会被重排序到volatile写之后;
当第一个操作为volatile读操作时,不管第二个操作是什么,都不能进行重排序。这个规则确保volatile读之后的所有操作都不会被重排序到volatile读之前;
当第一个操作是volatile写操作时,第二个操作是volatile读操作,不能进行重排序。
JMM中什么时数据依赖?
A和C之间存在数据依赖关系,同时B和C之间也存在数据依赖关系。因此在最终执行的指令序列中,C不能被重排序到A和B的前面(C排到A和B的前面,程序的结果将会被改变)。但A和B之间没有数据依赖关系,编译器和处理器可以重排序A和B之间的执行顺序。
JMM中什么是重排序,如何避免?
重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。
代码执行顺序会改变,但是执行结果不会变。
通过变量增加volatile关键字来避免。为了实现 volatile 的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
锁(同步关键字Synchronized)内存语义?解决什么问题?
当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中;
当线程获取锁时,JMM会当前线程拥有的本地内存共享变量置为无效,从而使得被监视器保护的临界区代码必须要从主内存中去读取共享变量;
解决线程安全问题。
JVM内存结构、 Java内存模型和Java对象模型 三个概念?
JVM内存结构,线程共享:堆和方法区(包括运行时常量池),线程独享:Java虚拟机栈,本地方法栈,程序计数器。
和Java虚拟机的运行时区域有关。描述的是Java程序执行过程中,由JVM管理的不同数据区域。各个区域有其特定的功能。
Java内存模型(JMM)是和多线程相关的,他描述了一组规则或规范,这个规范定义了一个线程对共享变量的写入时对另一个线程是可见的。
JMM定义了一些语法集,这些语法集映射到Java语言中就是volatile、synchronized等关键字。
Java对象模型,是指Java类在被JVM加载时,JVM为类创建instanceKlass保持在方法区,在JVM里表示该类。使用new创建一个对象时,JVM创建一个instanceOopDesc对象,包括对象头和实例数据等。
什么是CAS,为解决什么问题?ABA问题是什么?
CAS: CompareAndSwap or CompareAndSet(比较并交换). CAS操作涉及到三个操作数,一个是内存值,一个是旧的预期值,一个是更新后的值,如果内存值和旧的预期值没有发生变化,才设置成新的值。
(解决什么问题)用途:可以用CAS在无锁的情况下实现原子操作,但要明确应用场合,非常简单的操作且又不想引入锁可以考虑使用CAS操作,当想要非阻塞地完成某一操作也可以考虑CAS。
ABA问题:如果一个值原来A, 变成B,又变成了A,使用CAS检查是会发现它的值没有变化,但实际上却变化了。
解决思路:使用版本号。A->B->A 变为1A->2B->3A。
JUC里提供的AtomicStampedReference来解决ABA问题,即检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志。
独占锁,共享锁,公平锁,非公平锁?
独占锁:该锁每一次只能被一个线程所持有,ReentrantReadWriteLock里的写锁。
共享锁:该锁可被多个线程共有, ReentrantReadWriteLock里的读锁。
共享锁:其内部维护了一个FIFO的队列,先申请的线程优先获取锁。
非公平锁:申请锁的线程可能插队,后申请锁的线程有可能先拿到锁。
为什么弃用stop, suspend, resume方法?
Stop方法天生就不安全。该方法终止所有未结束的方法,包括run方法。当线程被终止,立即释放被它锁住的所有对象锁,会导致对象处于不一致的状态。例子,转账中途被终止,钱款已转出,但未转入目标账户,现在银行对象就被破坏了。
在希望停止线程的时候应该中断线程interrupt(),被中断的线程会在安全的时候停止。
Suspend与stop不同,它不会破坏对象。但如果线程t1调用suspend阻塞一个持有一个锁的线程t2,那么,该锁在线程t2恢复之前是不可用的。如果t1试图获得同一个锁,那么t1也阻塞,程序死锁。
suspend(),resume()--线程会停下来,但该线程,并没有放弃对象的锁。
destroy()--强制终止线程,但该线程不会释放对象锁。
附上思维导图:
参考资料:《Java并发编程的艺术》
[Java复习] 多线程 Multithreading的更多相关文章
- java复习-多线程
和线程之间的关系: 进程:进程是程序的一次动态执行过程,他经理了代码加载,执行到执行完毕的一个完整过程,这个过程也是进程本身从产生,发展到最终消亡的过程. 线程:线程是实现并发机制的一种有效手段,进程 ...
- [Java复习] 多线程&并发 知识点补充
0. wait/notify/notifyAll的理解? wait:让持有该对象锁的线程等待: notify: 唤醒任何一个持有该对象锁的线程: notifyAll: 唤醒所有持有该对象锁的线程: 它 ...
- Java复习——多线程与并发库
开启一个线程 实现一个线程的方式有两种:继承Thread类.实现Runnable接口(也存在说三种的情况,第三种是使用线程并发库中的线程池创建一个线程).这两种方法都需要重写Run方法,具体的线程逻辑 ...
- [Java复习] 多线程 并发 JUC 补充
线程安全问题? 当多个线程共享同一个全局变量,做写的操作时,可能会受到其他线程的干扰.读不会发生线程安全问题. -- Java内存模型. 非静态同步方法使用什么锁? this锁 静态同步方法使用什么 ...
- Java复习8.多线程
Java复习8 多线程知识 20131007 前言: 在Java中本身就是支持多线程程序的,而不是像C++那样,对于多线程的程序,需要调用操作系统的API 接口去实现多线程的程序,而Java是支持多线 ...
- Java简明教程 12.多线程(multithreading)
单线程和多线程 关于它们的区别,zhihu上有一个回答,我认为十分不错,如下: . 单进程单线程:一个人在一个桌子上吃菜. . 单进程多线程:多个人在同一个桌子上一起吃菜. . 多进程单线程:多个人每 ...
- Java复习1.基本知识
Java语言概述 20131003 开头语: 开发领域,重要的编程语言基本都是C/C++,然后就是Java, C/C++就不用说了,另外掌握Java对你是有很大的帮助的,而且也会扩宽你的择业范围.同时 ...
- 【Java】多线程入门
Java多线程学习(入门) 前言 目前对于线程的了解仅仅停留在学习python的threading库,很多线程的概念没有真正弄清楚,所以选择来系统性的学习多线程.那么这次选择的是Java的多线程学习, ...
- Java的多线程机制系列:不得不提的volatile及指令重排序(happen-before)
一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...
随机推荐
- 【Struts2】 国际化
一.概述 二.Struts2中国际化: 2.1 问题1 全局 局部 2.2 问题2 2.3 问题3 2.4 问题4 在Action中怎样使用 在JSP页面上怎样使用 一.概述 同一款软件 可以为不同用 ...
- AJAX配置csrf
// 从COokie取CSRF TOKEN的值 function getCookie(name) { var cookieValue = null; if (document.cookie & ...
- sql查询数据结果发送到邮箱
#!/bin/bash user=root password=xx dbname=xx DATE=`date +%F` #注意:此处mysql要用全路劲,否则计划任务会执行失败 /mydata/mys ...
- leaflet的使用
LeafLet 简单使用 WangKane 关注 0.4 2018.12.03 16:08 字数 887 阅读 3773评论 0喜欢 4 Leaflet 使用 最近在Angular项目中,用到了地图 ...
- MyBatis-13-缓存
13.缓存(了解) 13.1.简介 查询 : 连接数据库,耗资源! 一次查询的结果,给他暂存在一个可以直接取到的地方!--->内存 : 缓存 我们再次查询相同数据的时候,直接走缓存,就不用走数据 ...
- 【CF1218E】Product Tuples
题目大意:给定一个长度为 \(N\) 的序列,求从序列中选出 \(K\) 个数的集合乘积之和是多少. 题解: 由于是选出 \(K\) 个数字组成的集合,可知对于要计算的 \(K\) 元组来说是没有标号 ...
- 《SVG精髓》笔记(一)
本文是基于<SVG精髓>一书的简单总结,文中的demo均为该书提供,目的是方便大家使用时快速查阅. 1. 坐标系统 视口(viewport):文档使用的画布区域,表示SVG可见区域的大小, ...
- Qt5 使用lambda
c11新特性中加入了lambda表达式,所以Qt 也支持 需在.pro文件中加入 CONFIG += c++11 m_timer = new QTimer(); m_timer->start() ...
- Chrome禁用隐藏www和m
解决方案 打开chrome://flags 启动控制台输入并执行以下内容 [ 'omnibox-ui-hide-steady-state-url-path-query-and-ref', 'omnib ...
- Connect AS400 through firewall(JDBC will require ports: 449, 8470, 8471, and 8476)
What TCP ports are used by ODBC to connect to the DB2/400? 8471/9471 http://search400.techtarget.co ...