160711、Java 多线程核心技术梳理
本文对多线程基础知识进行梳理,主要包括多线程的基本使用,对象及变量的并发访问,线程间通信,lock 的使用,定时器,单例模式,以及线程状态与线程组。
java 多线程
基础知识
创建线程的两种方式:1.继承 Thread 类,2.实现 Runnable 接口。具体两者的联系可以参考我之前的博文《java 基础巩固笔记(5)-多线程之传统多线程》
一些基本API:isAlive(),sleep(),getId(),yield()等。
isAlive()
测试线程是否处于活动状态sleep()
让“正在执行的线程”休眠getId()
取得线程唯一标识yield()
放弃当前的 CPU 资源弃用的API:
stop()
,suspend()
,resume()
等,已经弃用了,因为可能产生数据不同步等问题。停止线程的几种方式:
使用退出标识,使线程正常退出,即 run 方法完成。
使用 interrupt 方法中断线程
线程的优先级:继承性,规则性,随机性
线程的优先级具有继承性. 如,线程 A 启动线程 B,则 B 和 A 优先级一样
线程的优先级具有规则性. CPU 尽量倾向于把资源优先级高的线程
线程的优先级具有随机性. 优先级不等同于执行顺序,二者关系不确定
java 中的两种线程:用户线程和守护(Daemon)线程。
守护线程:进程中不存在非守护线程时,守护线程自动销毁。典型例子如:垃圾回收线程。
比较和辨析
某个线程与当前线程:当前线程则是指正在运行的那个线程,可由
currentThread()
方法返回值确定。例如,直接在main方法里调用run方法,和调用线程的start方法,打印出的当前线程结果是不同的。interrupted()
和isInterrupted()
interrupted()
是类的静态方法,测试当前线程是否已经是中断状态,执行后具有将状态标志清除为false的功能。isInterrupted()
是类的实例方法,测试Thread对象是否已经是中断状态,但不清楚状态标志。sleep()
和wait()
区别:sleep()是 Thread 类的 static(静态)的方法;wait() 方法是 Object 类里的方法
sleep()睡眠时,保持对象锁,仍然占有该锁;wait()睡眠时,释放对象锁
在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级;wait() 使用 notify 或者 notifyAlll 或者指定睡眠时间来唤醒当前等待池中的线程
wait()必须放在 synchronized block 中,否则会在 runtime 时扔出
java.lang.IllegalMonitorStateException
异常
方法 | 是否释放锁 | 备注 |
---|---|---|
wait | 是 | wait和notify/notifyAll是成对出现的, 必须在synchronize块中被调用 |
sleep | 否 | 可使低优先级的线程获得执行机会 |
yield | 否 | yield方法使当前线程让出 CPU 占有权, 但让出的时间是不可设定的 |
对象及变量的并发访问
synchronized
关键字调用用关键字 synchronized 声明的方法是排队运行的。但假如线程 A 持有某对象的锁,那线程 B 异步调用非 synchronized 类型的方法不受限制。
synchronized 锁重入:一个线程得到对象锁后,再次请求此对象锁时是可以得到该对象的锁的。同时,子类可通过“可重入锁”调用父类的同步方法。
同步不具有继承性。
synchronized 使用的“对象监视器”是一个,即必须是同一个对象
synchronized 同步方法和 synchronized 同步代码块。
对其他 synchronized 同步方法或代码块调用呈阻塞状态。
同一时间只有一个线程可执行 synchronized 方法/代码块中的代码
synchronized(非 this 对象 x),将 x 对象作为“对象监视器”
当多个线程同时执行
synchronized(x){}
同步代码块时呈同步效果当其他线程执行 x 对象中 synchronizd 同步方法时呈同步效果
当其他线程执行 x 对象方法里的 synchronized(this) 代码块时呈同步效果
静态同步 synchronized 方法与 synchronized(class) 代码块:对当前对应的 class 类进行持锁。
线程的私有堆栈图
volatile 关键字:主要作用是使变量在多个线程间可见。加 volatile 关键字可强制性从公共堆栈进行取值,而不是从线程私有数据栈中取得变量的值
在方法中 while 循环中设置状态位(不加 volatile 关键字),在外面把状态位置位并不可行,循环不会停止,比如 JVM 在 -server 模式。
原因:是私有堆栈中的值和公共堆栈中的值不同步
volatile 增加了实例变量在多个线程间的可见性,但不支持原子性
原子类:一个原子类型就是一个原子操作可用的类型,可在没有锁的情况下做到线程安全。但原子类也不是完全安全,虽然原子操作是安全的,可方法间的调用却不是原子的,需要用同步。
读取公共内存图
辨析和零散补充
synchronized 静态方法与非静态方法:synchronized 关键字加 static 静态方法上是给 Class 类上锁,可以对类的所有实例对象起作用;synchronized 关键字加到非 static 静态方法上是给对象上锁,对该对象起作用。这两个锁不是同一个锁。
synchronized 和 volatile 比较
1)关键字 volatile 是线程同步的轻量级实现,性能比 synchronized 好,且 volatile 只能修饰变量,synchronized 可修饰方法和代码块。
2)多线程访问 volatile 不会发生阻塞,synchronized 会出现阻塞
3)volatile 能保证数据可见性,不保证原子性;synchronized 可以保证原子性,也可以间接保证可见性,因为 synchronized 会将私有内存和公共内存中的数据做同步。
4)volatile 解决的是变量在多个线程间的可见性,synchronized 解决的是多个线程访问资源的同步性。
String 常量池特性,故大多数情况下,synchronized 代码块都不适用 String 作为锁对象。
多线程死锁。使用JDK自带工具,jps 命令+jstack命令监测是否有死锁。
内置类与静态内置类。
锁对象的的改变。
一个线程出现异常时,其所持有的锁会自动释放。
变量在内存中的工作过程图
线程间通信
等待/通知机制:
wait()
和notify()
/notifyAll()
。wait 使线程停止运行,notify 使停止的线程继续运行。在调用 notify()之前,线程必须获得该对象的对象级别锁;
执行完 notify()方法后,不会马上释放锁,要直到退出 synchronized 代码块,当前线程才会释放锁。
notify()一次只随机通知一个线程进行唤醒
在调用 wait()之前,线程必须获得该对象的对象级别锁;
执行 wait()方法后,当前线程立即释放锁;
从 wait()返回前,线程与其他线程竞争重新获得锁
当线程呈 wait()状态时,调用线程的 interrup()方法会出现 InterrupedException 异常
wait(long)
是等待某一时间内是否有线程对锁进行唤醒,超时则自动唤醒。wait()
:将当前执行代码的线程进行等待,置入”预执行队列”。notify()
:通知可能等待该对象的对象锁的其他线程。随机挑选一个呈wait状态的线程,使它等待获取该对象的对象锁。notifyAll()
和notify()
差不多,只不过是使所有正在等待队中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。每个锁对象有两个队列:就绪队列和阻塞队列。
就绪队列:存储将要获得锁的线程
阻塞队列:存储被阻塞的的线程
生产者/消费者模式
假死的主要原因:有可能连续唤醒同类。notify 唤醒的不一定是异类,也许是同类,如“生产者”唤醒“生产者”。
解决假死:将 notify()改为 notifyAll()
wait条件改变,可能出现异常,需要将if改成while
“假死”:线程进入 WAITING 等待状态,呈假死状态的进程中所有线程都呈 WAITING 状态。
通过管道进行线程间通信:一个线程发送数据到输出管道,另一个线程从输入管道读数据。
字节流:
PipedInputStream
和PipedOutputStream
字符流:
PipedReader
和PipedWriter
join()
:等待线程对象销毁,具有使线程排队运行的作用。join()与interrupt()方法彼此遇到会出现异常。
join(long)
可设定等待的时间join
与synchronized
的区别:join在内部使用wait()方法进行等待;synchronized使用的是“对象监视器”原理作为同步join(long)
与sleep(long)
的区别:join(long)内部使用 wait(long)实现,所以 join(long)具有释放锁的特点;Thread.sleep(long)不释放锁。ThreadLocal
类:每个线程绑定自己的值覆写该类的
initialValue()
方法可以使变量初始化,从而解决 get()返回 null 的问题InheritableThreadLocal
类可在子线程中取得父线程继承下来的值。
Lock 的使用
ReentrantLock
类:实现线程之间的同步互斥,比 synchronized 更灵活lock()
,调用了的线程就持有了“对象监视器”,效果和 synchronized 一样使用
Condition
实现等待/通知:比 wait()和 notify()/notyfyAll() 更灵活,比如可实现多路通知。调用 condition.await()前须先调用 lock.lock()获得同步监视器
Object 与 Condition 方法对比
Object | Condition |
---|---|
wait() | await() |
wait(long timeout) | await(long time,TimeUnit unit) |
notify() | signal() |
notifyAll() | signalAll() |
一些 API
方法 | 说明 |
---|---|
int getHoldCount() |
查询当前线程保持此锁定的个数,即调用lock()方法的次数 |
int getQueueLength() |
返回正在等待获取此锁定的线程估计数 |
int getWaitQueueLength(Condition condition) |
返回等待与此锁定相关的给定条件Conditon的线程估计数 |
boolean hasQueueThread(Thread thread) |
查询指定的线程是否正在等待获取此锁定 |
boolean hasQueueThreads() |
查询是否有线程正在等待获取此锁定 |
boolean hasWaiters(Condition) |
查询是否有线程正在等待与此锁定有关的condition条件 |
boolean isFair() |
判断是不是公平锁 |
boolean isHeldByCurrentThread() |
查询当前线程是否保持此锁定 |
boolean isLocked() |
查询此锁定是否由任意线程保持 |
void lockInterruptibly() |
如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常 |
boolean tryLock() |
仅在调用时锁定未被另一个线程保持的情况下,才获取该锁定 |
boolean tryLock(long timeout,TimeUnit unit) |
如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定 |
公平锁与非公平锁
公平锁表示线程获取锁的顺序是按照加锁的顺序来分配的,即FIFO先进先出。
非公平锁是一种获取锁的抢占机制,随机获得锁。
ReentrantReadWriteLock
类读读共享
写写互斥
读写互斥
写读互斥
定时器
常用 API
方法 | 说明 |
---|---|
schedule(TimerTask task, Date time) | 在指定的日期执行某一次任务 |
scheduleAtFixedRate(TimerTask task, Date firstTime, long period) | 在指定的日期之后按指定的间隔周期,无限循环的执行某一任务 |
schedule(TimerTask task, long delay) | 以执行此方法的当前时间为参考时间,在此时间基础上延迟指定的毫秒数后执行一次TimerTask任务 |
schedule(TimerTask task, long delay, long period) | 以执行此方法的当前时间为参考时间,在此时间基础上延迟指定的毫秒数,再以某一间隔时间无限次数地执行某一TimerTask任务 |
schedule
和scheduleAtFixedRate
的区别:schedule 不具有追赶执行性;scheduleAtFixedRate 具有追赶执行性
单例模式与多线程
立即加载/“饿汉模式”:调用方法前,实例已经被创建了。通过静态属性new实例化实现的
延迟加载/“懒汉模式”:调用 get()方法时实例才被创建。最常见的实现办法是在get()方法中进行new实例化
声明synchronized关键字,但运行效率非常低下
同步代码块,效率也低
针对某些重要代码(实例化语句)单独同步,效率提升,但会出问题
使用DCL双检查锁
使用enum枚举数据类型实现单例模式
缺点:多线程环境中,会出问题
解决方法
拾遗补增
方法与状态关系示意图
线程的状态:
Thread.State
枚举类,参考官网APIEnum Thread.State线程组:线程组中可以有线程对象,也可以有线程组,组中还可以有线程。可批量管理线程或线程组对象。
SimpleDateFormat
非线程安全,解决办法有:创建多个SimpleDateFormat类的实例
使用ThreadLocal类
线程组出现异常的处理
setUncaughtExceptionHandler()
给指定线程对象设置异常处理器setDefaultUncaughtExceptionHandler()
对所有线程对象设置异常处理器
160711、Java 多线程核心技术梳理的更多相关文章
- 从ConcurrentHashMap的演进看Java多线程核心技术 Java进阶(六)
本文分析了HashMap的实现原理,以及resize可能引起死循环和Fast-fail等线程不安全行为.同时结合源码从数据结构,寻址方式,同步方式,计算size等角度分析了JDK 1.7和JDK 1. ...
- Java多线程核心技术(六)线程组与线程异常
本文应注重掌握如下知识点: 线程组的使用 如何切换线程状态 SimpleDataFormat 类与多线程的解决办法 如何处理线程的异常 1.线程的状态 线程对象在不同运行时期有不同的状态,状态信息就处 ...
- Java多线程核心技术(五)单例模式与多线程
本文只需要考虑一件事:如何使单例模式遇到多线程是安全的.正确的 1.立即加载 / "饿汉模式" 什么是立即加载?立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接 ...
- Java多线程核心技术(四)Lock的使用
本文主要介绍使用Java5中Lock对象也能实现同步的效果,而且在使用上更加方便. 本文着重掌握如下2个知识点: ReentrantLock 类的使用. ReentrantReadWriteLock ...
- 四、java多线程核心技术——synchronized同步方法与synchronized同步快
一.synchronized同步方法 论:"线程安全"与"非线程安全"是多线程的经典问题.synchronized()方法就是解决非线程安全的. 1.方法内的变 ...
- java多线程核心技术——第四章总结
第一节使用ReentrantLock类 1.1使用ReentrantLock实现同步:测试1 1.2使用ReentrantLock实现同步:测试2 1.3使用Condition实现等待/同步错误用法与 ...
- 三、java多线程核心技术(笔记)——线程的优先级
概论: 在操作系统中,线程可以划分优先级,优先级高的获得的CPU资源较多,也就是CPU优先执行优先级较高的线程.在JAVA中线程的优先级 分1~~10个10个等级.大于或者小于会报异常. 一.线程优先 ...
- 《Java多线程核心技术》读书摘要
Chapter1: 进程是操作系统管理的基本单元,线程是CPU调到的基本单元. 调用myThread.run()方法,JVM不会生成新的线程,myThread.start()方法调用两次JVM会报错. ...
- Java多线程编程核心技术(三)多线程通信
线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时 ...
随机推荐
- Linux命令-网络命令:last
last 统计linux系统所有用户的登录时间(多次登录信息)
- POJ 3087 Shuffle'm Up(模拟退火)
Description A common pastime for poker players at a poker table is to shuffle stacks of chips. Shuff ...
- 【征文】Hadoop十周年特别策划——我与Hadoop不得不说的故事
2016年是Hadoop的十周年生日,在今年,CSDN将以技术和实战为主题与大家共同为Hadoop庆生.其主要内容包含Hadoop专业词典.系列视频技术解析.Hadoop行业实践.线上问答.线下沙龙. ...
- hdu2647(拓扑排序)
链接:点击打开链接 题意:每一个人的基本工资为888,给出两个人的关系a,b,代表a的工资比b高问满足全部条件的话,最少须要支付多少钱 代码: #include <map> #includ ...
- sklearn 随机森林方法
Notes The default values for the parameters controlling the size of the trees (e.g. max_depth, min_s ...
- myeclipse中配置schemaLocation路径,实现xml文件自动提示
在开发中,XML的xsi:schemaLocation路径都是指向网络,但是这个网络地址有时候很不给力导致工程检验XML格式缓慢.所以有必要再myeclipse中配置本地xsd文件路径,以免每次校验都 ...
- [svc]msmtp+mutt发附件,发邮件给多个人
环境:centos6.7 x86-64 内网有web服务器(curl可展示目录) #预安装软件 yum install lrzsz ntpdate sysstat dos2unix wget teln ...
- matplotlib极坐标系应用之雷达图
#!/usr/bin/env python3 #-*- coding:utf-8 -*- ############################ #File Name: test.py #Autho ...
- [转]C++11 标准新特性:Defaulted 和 Deleted 函数
http://www.ibm.com/developerworks/cn/aix/library/1212_lufang_c11new/
- sitemesh 学习之 meta 引入
在上篇笔记学习了sitemesh的基本用法,这里还有另一种用法 在sitemesh.jar有一个默认的sitemesh-default文件 ,这个文件是可以指定的 可以指定的文件名的sitemesh. ...