Java多线程编程 深入详解
《Java多线程编程 深入详解》读书笔记
第一章 多进程多线程概述
- 线程概述
- 进程(PROCESS):CPU的执行路径
- 多进程:系统中同时运行多个程序
- 线程(THREAD):运行在进程当中的运行单元
- 多线程:每个进程里面有多个独立的或者相互有协作关系的运行单元
第二章 多线程详解
线程创建
- 继承
Thread
类- 其中
Thread
实现Runnable
;符合模板设计模式(声明一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑);即继承Runnable 接口,实现其抽象方法run,而run方法是由JVM线程调用并运行
- 其中
- 实现
Runnable
接口- 优势:
- a.在 java 中单继承而多实现,如果通过继承
Thread
类来实现线程,就会打破原有的类间的父子关系;而通过实现Runnable
接口可避免此问题,接口可继承多个 - b.实现
Runnable
接口,可将业务逻辑与线程管理分离开,符合策略设计模式
- a.在 java 中单继承而多实现,如果通过继承
- 优势:
- 继承
Thread 和 Runnable 的区别与联系
Thread:负责线程本身的功能
Runnable:专注于业务逻辑的实现
两者职责功能不同,不具有可比性
Runnable中的run方法,即使不在Thread中使用,他在其他地方照样也能使用,并不能说Runnable就是一个线程
线程的状态:
- 初始化(被创建)、运行状态、冻结状态、终止状态(死亡)
其他信息:
- Main 函数本身就是一个线程,即主线程
- JVM 本身有很多后台线程在运行
- 示例:比较
extends Thread
与implements Runnable
创建线程方式- 需求描述:排队叫号系统,若10人排队,当前有3个窗口服务在服务
// 思路一:多线程处理,启动三个线程,分别处理同一个问题/*** 排队叫号系统;假设当前10人排队,共3个窗口工作 */
public class TicketWindowTest extends Thread{private int maxPersonCount ; // 最大排队人数@Overridepublic void run(){while(true){if(maxPersonCount > 10){ // 最多10人,超过不处理System.out.println("stop service");break ;}System.out.println(Thread.currentThread().getName()+"-"+maxPersonCount++);}}/*** @param args*/public static void main(String[] args) {TicketWindowTest thread = new TicketWindowTest();TicketWindowTest thread1 = new TicketWindowTest();TicketWindowTest thread2 = new TicketWindowTest();thread.start();thread1.start();thread2.start();}}
// console
Thread-0-0
Thread-1-0
..........// 分析:从运行结果中可以得出会出现同一个人被两个或多个窗口同时或重复叫号的问题
// 改进:将maxPersonCount 改为 类的属性,为该类创建的所有对象共享
// 思路一:劣势是同一段业务逻辑被不同的线程多次执行
// 思路二:只建立一个线程,启动三次
// 运行时抛出 IllegalThreadStateException 异常
// 从 Thread 类中的 start 方法可以得出一个线程只能被启动一次
// 此思路不可行
- 为何同一个线程多次启动会出现异常?参见:Thread 的 start()
public synchronized void start() {/*** This method is not invoked for the main method thread or "system"* group threads created/set up by the VM. Any new functionality added* to this method in the future may have to also be added to the VM.** A zero status value corresponds to state "NEW".*/if (threadStatus != 0)throw new IllegalThreadStateException();
}
// 思路三:使用Runnable ,Runnable 的作用,将业务逻辑与线程执行进行分离;且类只允许单继承,但可以多实现,所以实现多线程时建议使用 Runnable 接口;public class TicketWindowRunnableTest implements Runnable {private static int maxPersonCount ;public void run() {while(true){if(maxPersonCount > 10){break ;}System.out.println(Thread.currentThread().getName()+"-"+maxPersonCount++);}}}public class TicketWindowRunnableTestMain {/*** @param args*/public static void main(String[] args) {TicketWindowRunnableTest test = new TicketWindowRunnableTest();Thread thread1 = new Thread(test);Thread thread2 = new Thread(test);Thread thread3 = new Thread(test);thread1.start();thread2.start();thread3.start();}}
// 从运行结果上看,只有一个线程在运行,其他没有;没实现多线程?否,将 maxPersonCount 设置为全局变量,为多个对象共享;由于线程的运行机制,有JVM决定何时运行,可能导致每次运行结果均不一致,Thread-0 执行完毕前,Thread-1 是否已启动并运行
Thread-0-0
Thread-0-1
Thread-0-2
Thread-0-3
Thread-0-4
Thread-0-5
Thread-0-6
Thread-0-7
Thread-1-8
Thread-0-9
Thread-1-10
- Runnable就是一个可执行任务的标识而已,仅此而已;而Thread才是线程所有API的体现
第三章 线程的同步
示例:多线程之间的数据共享引发的安全问题
// 将上面思路三中
public class TicketWindowRunnableTest implements Runnable {private int maxPersonCount ; // 去掉 static 修饰
}// 运行后输出结果
Thread-1-0
Thread-0-1
Thread-2-1
Thread-0-2
Thread-1-2
Thread-2-2
Thread-2-3
Thread-1-4
Thread-0-3
Thread-0-5
Thread-2-6
Thread-1-5
Thread-1-7
Thread-2-7
Thread-0-7
Thread-0-9
Thread-2-8
Thread-1-9
Thread-2-11
Thread-1-10
Thread-0-12
- 运行结果分析:
- 整个执行流程分为多个步骤,判断、自增(分为两个步骤 s++ ⇒ s = s + 1 ,再获取 s 的值)、输出;由于CPU的时间片轮寻,可能每个线程都没有执行完毕整个流程,中途就有其他的线程插入进来进行执行,所以导致数据获取时线程安全问题;被访问的数据没有被保护起来,而引起数据共享的安全问题
- 为何出现超过上限的值?
- 假设max_value = 9 ,三个线程依次编号为1,2,3 ;三个线程都获取到 max_value = 9 ,都通过了判断条件;线程 1 通过判断条件,进行 max_value ++ ,在进行输出之前时间片轮询;线程 2 开始执行,max_value ++ 并输出 11,再次执行判断,不满足条件,线程 2 运行结束;时间片轮询切换回到线程 1 ,输出 10;线程3,max++并输出 12 ,再次判断退出,线程 3运行结束
- 为何出现重复值?
- 初始化值为0,线程并发执行,都拿到了此时的0值,进行 max_value ++ 并输出
第一节 同步代码块
- 关键字:synchronized
public class TicketWindowRunnableTest implements Runnable {private int maxPersonCount ;private Object obj = new Object(); // 任意对象public void run() {while(true){synchronized (obj) { // 同步代码块if(maxPersonCount > 10){break ;}try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"-"+maxPersonCount++);}}}}
// 运行输出结果:不会再出现重复号码或超过最大限度的编号
- 对比思路三的实现,尽量避免使用全局变量 static ,static 修饰的变量为类所有,生命周期长,占用内存空间,得不到GC的回收
- synchronized 代码块包含的内容,表示被线程互斥访问,应尽量短小,只包含会出现安全问题的代码;
- synchronized 同步代码块:同步代码块最小的粒度应该放在共享数据的上下文,或者说共享数据被操作的上下文中
- 加锁的规则
- 加锁的目的在于防止多个线程同时对同一份数据进行操作,如果多线程运行过程中操作的数据都是各自线程的,就没有必要加锁
- 线程共享数据的锁必须是同一份的,这样才会起到线程中互相争抢锁资源的效果;反之,若局部方法内部定义锁对象,每次线程运行都会生成新的锁对象,就不会出现锁竞争,也就起不到锁的效果
- 锁对象可以是任意对象,该对象可以不参与任务计算;只要保证对于正在访问的多个线程来说此对象是唯一的
第二节 同步方法
- 示例
// 将 synchronized 加到 多线程 访问的共享的数据上
public class TicketWindowRunnableTest implements Runnable {private int maxPersonCount ;private Object obj = new Object();public void run() {while(true){if(ticket()){break ;}}}public boolean ticket(){synchronized (obj) {if(maxPersonCount > 10){return true ;}try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"-"+maxPersonCount++);}return false ;}}
- 为什么不在
run()
方法上添加synchronized
关键字修饰?- CPU时分复用性,多线程间切换时间片运行,JVM分配时间片给某线程,当该线程获得CPU后,加锁,运行
run()
方法;此时其他线程同样想要执行,但即使获取了CPU,未获取到锁,所以只能等待;直到该线程运行结束释放了锁,其他线程才可以执行,如上面的例子,其他线程获取了锁,run()
方法中的判断条件已经不满足了,直接退出
- CPU时分复用性,多线程间切换时间片运行,JVM分配时间片给某线程,当该线程获得CPU后,加锁,运行
- 线程同步总结:当同一份数据被多线程操作时才考虑同步
- 访问的是共享数据
- 多线程并发访问
- 线程同步的代码块必须是同一个锁
第三节 this
锁与static
锁
- 代码块的锁:对象锁,如上述示例,在 synchronized 外声明任务的对象 object
- 同一个类中不同方法间的锁:this 锁
/*** 假设类中方法间不是 this 锁* 那么启动 thread1 与 thread2 时,methodA 与 methodB 是可以同时执行的* 所以,类中方法间是 this 锁*/
public class ThreadTest1 {public synchronized void methodA(){System.out.println("methodA run");while(true){// 死循环// 若此处使用其他方法,如 i++<50 ,起不到效果,会出现两个方法都有结果数据// 但是,是methodA执行完毕后再执行的methodB}}public synchronized void methodB(){System.out.println("methodB run");while(true){}}/*** @param args*/public static void main(String[] args) {final ThreadTest1 test = new ThreadTest1();Thread thread1 = new Thread(new Runnable(){public void run() {test.methodA();}});Thread thread2 = new Thread(new Runnable(){public void run() {test.methodB();}});thread1.start();thread2.start();}}
/*** obj 是代码块间的锁* 不同方法间需使用 this 锁* ticket() 使用 obj 对象锁* ticketMethod() 使用 synchronized 修饰方法,this 锁* 运行结果:maxPersonCount 出现 11 ,大于 10 * 理论上不可能出现 11 ,即没有起到数据同步的作用*/
public class TicketWindowRunnableTest implements Runnable {private int maxPersonCount ;private Object obj = new Object();private boolean flag = true ;public void run() {if(flag){while(true){if(ticket()){break ;}}}else{while(true){if(ticketMethod()){break ;}}}}public void change() throws InterruptedException{Thread.sleep(3);flag = false ;}public synchronized boolean ticketMethod(){if(maxPersonCount > 10){return true ;}try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"-"+maxPersonCount++);return false ;} public boolean ticket(){synchronized (obj) {if(maxPersonCount > 10){return true ;}try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"-"+maxPersonCount++);}return false ;}}public class TicketWindowRunnableTestMain {/*** @param args* @throws InterruptedException */public static void main(String[] args) throws InterruptedException {TicketWindowRunnableTest test = new TicketWindowRunnableTest();Thread thread1 = new Thread(test);Thread thread2 = new Thread(test);Thread thread3 = new Thread(test);thread1.start();test.change();thread2.start();thread3.start();}}
// 将上述示例中的
synchronized(object) 改为 synchronized(this) ,解决上述的问题,maxPersonCount 不会出现 11的值// 方法间应使用 this 锁来同步数据
- 为什么Java匿名内部类的方法中用到的局部变量都必须定义为final?见上例中 new Runnable (){} 调用 test 对象的方法,强制将 test 对象使用 fianl 修饰
- 为了延长对象的生命周期,主线程main方法执行完毕后,局部变量就会运行结束等待GC,而main中的其他线程如果未执行且需要使用此局部变量,那么就需要延长生命周期
- 内部匿名类访问外部类中的局部变量,若是引用类型,由于其引用值不变,保证内部类与外部类访问的永远都是同一个内容
- static 锁
/*** 类中方法带有 static 关键字修饰时,加锁使用类锁* 该类创建的所有对象,共享该类锁*/
public class TicketWindowStaticRunnable implements Runnable {private static int max_value = 0 ;private boolean flag = true ;public void run() {if(flag){while(true){synchronized (TicketWindowStaticRunnable.class) {if(max_value > 10){break ;}try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"_"+max_value++);}}}else{while(true){if(ticket()){break ;}}}}public synchronized static boolean ticket(){if(max_value > 10){return true ;}try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"_"+max_value++);return false ;}public void change() throws InterruptedException{Thread.sleep(3);flag = false ;}
}
public class TicketWindowStaticRunnableMain {/*** @param args* @throws InterruptedException */public static void main(String[] args) throws InterruptedException {TicketWindowStaticRunnable test = new TicketWindowStaticRunnable();Thread t1 = new Thread(test);Thread t2 = new Thread(test);t1.start();test.change();t2.start();}}
- 静态锁,锁是类的字节码信息,因此如果一个类的函数为静态方法,那么我们需要通过该类的class信息进行加锁
参考资料
- 《Java多线程编程 深入详解》 - Version1.0 - 汪文君
Java多线程编程 深入详解相关推荐
- linux c多进程多线程,linux下的C\C++多进程多线程编程实例详解
linux下的C\C++多进程多线程编程实例详解 1.多进程编程 #include #include #include int main() { pid_t child_pid; /* 创建一个子进程 ...
- JAVA 多线程并发超详解
JAVA 多线程并发超详解(未完,下一篇文章还有) 1. JAVA 多线程并发 1.1.1. JAVA 并发知识库 1.1.2. JAVA 线程实现/创建方式 1.1.2.1. 继承 Thread 类 ...
- Java——多线程之方法详解
Java多线程系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多 ...
- python java混合编程_详解java调用python的几种用法(看这篇就够了)
java调用python的几种用法如下: 在java类中直接执行python语句 在java类中直接调用本地python脚本 使用Runtime.getRuntime()执行python脚本文件(推荐 ...
- Java多线程(超详解)
目录 1. 线程简介 1.1 程序 1.2 进程 1.3 线程 1.4 多线程 1.5 普通方法调用和多线程 2. 线程创建 2.1 继承Thread类 2.2 实现Runnable接口 2.3 实现 ...
- java 多线程同步_详解Java多线程编程中的线程同步方法
1.多线程的同步: 1.1.同步机制:在多线程中,可能有多个线程试图访问一个有限的资源,必须预防这种情况的发生.所以引入了同步机制:在线程使用一个资源时为其加锁,这样其他的线程便不能访问那个资源了,直 ...
- Java 并发编程_详解 synchronized 和 volatile
文章目录 1. synchronized 的应用 1.1 基础知识 1.2 synchronized 语法 2. Monitor概念 3. Synchronized原理进阶 3.1 对象头格式 3.2 ...
- Java并发编程AQS详解
本文内容及图片代码参考视频:https://www.bilibili.com/video/BV12K411G7Fg/?spm_id_from=333.788.recommend_more_video. ...
- Java并发编程synchronized详解
一.关于临界区.临界资源.竞态条件和解决方法 首先看如下代码,thread1对变量i++做500次运算,thread2对i--做500次运算,但是最终的结果却可能为是正数,负数,0不一样的结果. pa ...
- Java并发编程——ConcurrentHashMap详解
引出 场景:针对用户来做一个访问次数的记录. 通过HashMap进行记录,key为用户名,value为访问次数. public class ConcurrentHashMapDemo {private ...
最新文章
- python大数据分析实例-用Python整合的大数据分析实例
- Asp.net序中常用代码汇总(五)
- 面试提问vue中v-if与v-show的区别以及使用场景
- 模式分享 公众号_微信公众号+()模式营销!公众号还可以这样玩?
- 矩池云上如何修改cuda版本
- python 正则表达式 速查表
- spark 持久化机制入门
- 《优化算法》人工鱼群算法学习 超详细解析 附JAVA和matlab示例
- [RK3399][Android7.1] 移植笔记 --- GT9XX系列Touch添加
- WORD文本框和文本框之间,文本框和文字相互不覆盖
- 使用wxpy实现在微信定时发送文件和消息
- grep查找的内容输出到文件
- 目前ipad协议和安卓协议能实现微信百分之90功能 扫码进群 注册 阅读 关注支付功能等都能实现吗?ipad协议源码
- 云栖大会100位顶级大咖演讲PPT+视频全分享
- A1010. Radix
- 【源码】应用于各类工业控制的通用PID调谐器仿真设计
- SPEC2000程序编译和性能热点分析
- matlab 数组语法
- WinMount开发者刘涛涛
- 我们必须要相信那些不可能的事情