Java多线程0:核心理论
Posted 吹灭读书灯
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程0:核心理论相关的知识,希望对你有一定的参考价值。
并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能。它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰、思维缜密,这样才能写出高效、安全、可靠的多线程并发程序。本系列会从线程间协调的方式(wait、notify、notifyAll)、Synchronized及Volatile的本质入手,详细解释JDK为我们提供的每种并发工具和底层实现机制。在此基础上,我们会进一步分析java.util.concurrent包的工具类,包括其使用方式、实现源码及其背后的原理。本文是该系列的第一篇文章,是这系列中最核心的理论部分,之后的文章都会以此为基础来分析和解释。
一、共享性
数据共享是为什么要考虑线程安全的主要原因之一。如果所有的数据只是在当前线程内有效,那就不需要考虑线程安全问题。但是,在多线程编程中,数据共享是不可避免的。比如夫妻双方一人在柜台取钱,一人在ATM上取钱,两个取钱线程共享账户中的余额这一变量,这时候就要考虑线程安全问题了。
举例1:以银行取钱为例说明多线程之间的数据共享
定义一个账户Account,成员变量为账户编号和余额,以及一个取钱的方法,方法中对取钱金额做了判断,只有取的钱<=账户余额的时候,才能取钱成功。
public class Account { //账户编号 private int accountNo; //账户余额 private int balance; public Account(int accountNo, int balance) { this.accountNo = accountNo; this.balance = balance; } //取钱方法 public void drawMoney(int drawMoneyCount){ if(balance >= drawMoneyCount){ try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "取款金额为 = " + drawMoneyCount + ",取款成功"); this.balance = balance - drawMoneyCount; System.out.println("账户余额为===" + (this.balance)); } catch (InterruptedException e) { e.printStackTrace(); } }else{ System.out.println(Thread.currentThread().getName() + "取款金额为 = " + drawMoneyCount + ",取款失败,余额不足"); } } }
定义一个取钱线程类:
public class Thread03 extends Thread{ private Account account; private int drawMoneyCount; //初始化账户余额和取钱金额 public Thread03(int drawMoneyCount,Account account){ this.drawMoneyCount = drawMoneyCount; this.account = account; } @Override public void run() { account.drawMoney(drawMoneyCount); } }
测试,定义两个取钱线程
public class Test { public static void main(String[] args) { Account account = new Account(123456789,800); //取钱线程1 Thread thread1 = new Thread03(500,account); thread1.setName("张三"); //取钱线程2 Thread thread2 = new Thread03(500,account); thread2.setName("李四"); thread1.start(); thread2.start(); } }
结果:
张三取款金额为 = 500,取款成功 账户余额为===300 李四取款金额为 = 500,取款成功 账户余额为===-200
说明:可以看到,初始账户余额为800,张三取了500,账户余额还剩300,李四取500的时候,按说已经对取钱金额进行校验,不应该取钱成功,但李四还是取出了500。账户余额还剩-300。这是因为两个取钱的线程同时进入到Account的drawMoney方法内部,校验金额的时候都是800>=500,所以都能取钱成功。
解决方法就是对取钱方法进行同步,用synchronized修饰,此处先看一下结果,后续会详细解释synchronized锁机制。
public class Account { //账户编号 private int accountNo; //账户余额 private int balance; public Account(int accountNo, int balance) { this.accountNo = accountNo; this.balance = balance; } //取钱方法 public synchronized void drawMoney(int drawMoneyCount){ if(balance >= drawMoneyCount){ try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "取款金额为 = " + drawMoneyCount + ",取款成功"); this.balance = balance - drawMoneyCount; System.out.println("账户余额为===" + (this.balance)); } catch (InterruptedException e) { e.printStackTrace(); } }else{ System.out.println(Thread.currentThread().getName() + "取款金额为 = " + drawMoneyCount + ",取款失败,余额不足"); } } }
其余都不变,看一下结果:
张三取款金额为 = 500,取款成功 账户余额为===300 李四取款金额为 = 500,取款失败,余额不足
所以,多线程情况下对共享变量的操作,要考虑线程安全问题。
举例1:以银行存钱取钱为例说明多线程之间的数据共享
public class Account { //账户编号 private int accountNo; //账户余额 private int balance; public Account(int accountNo, int balance) { this.accountNo = accountNo; this.balance = balance; } //取钱方法 public void drawMoney(int drawMoneyCount){ if(balance >= drawMoneyCount){ System.out.println(Thread.currentThread().getName() + "取款金额为 = " + drawMoneyCount + ",取款成功"); this.balance = balance - drawMoneyCount; System.out.println("账户余额为===" + (this.balance)); }else{ System.out.println(Thread.currentThread().getName() + "取款金额为 = " + drawMoneyCount + ",取款失败,余额不足"); } } //存钱方法 public void depositMoney(int depositMoneyCount){ System.out.println(Thread.currentThread().getName() + "存钱成功,余额为 = " + (this.balance + depositMoneyCount)); this.balance = balance + depositMoneyCount; } }
public class Thread03 extends Thread{ private Account account; private int drawMoneyCount; //初始化账户余额和取钱金额 public Thread03(int drawMoneyCount,Account account){ this.drawMoneyCount = drawMoneyCount; this.account = account; } @Override public void run() { account.drawMoney(drawMoneyCount); } }
public class Thread04 extends Thread{ private Account account; private int depositMoneyCount; //初始化账户余额和存钱金额 public Thread04(int depositMoneyCount, Account account){ this.depositMoneyCount = depositMoneyCount; this.account = account; } @Override public void run() { account.depositMoney(depositMoneyCount); } }
public class Thread04 extends Thread{ private Account account; private int depositMoneyCount; //初始化账户余额和存钱金额 public Thread04(int depositMoneyCount, Account account){ this.depositMoneyCount = depositMoneyCount; this.account = account; } @Override public void run() { account.depositMoney(depositMoneyCount); } }
public class Test { public static void main(String[] args) { Account account = new Account(123456789,0); //存钱线程 Thread thread1 = new Thread04(200,account); thread1.setName("张三"); //取钱线程 Thread thread2 = new Thread03(100,account); thread2.setName("李四"); thread1.start(); thread2.start(); } }
张三存钱成功,余额为 = 200
李四取款金额为 = 100,取款失败,余额不足
public class Account { //账户编号 private int accountNo; //账户余额 private int balance; public Account(int accountNo, int balance) { this.accountNo = accountNo; this.balance = balance; } //取钱方法 public synchronized void drawMoney(int drawMoneyCount){ if(balance >= drawMoneyCount){ System.out.println(Thread.currentThread().getName() + "取款金额为 = " + drawMoneyCount + ",取款成功"); this.balance = balance - drawMoneyCount; System.out.println("账户余额为===" + (this.balance)); }else{ System.out.println(Thread.currentThread().getName() + "取款金额为 = " + drawMoneyCount + ",取款失败,余额不足"); } } //存钱方法 public synchronized void depositMoney(int depositMoneyCount){ System.out.println(Thread.currentThread().getName() + "存钱成功,余额为 = " + (this.balance + depositMoneyCount)); this.balance = balance + depositMoneyCount; } }
张三存钱成功,余额为 = 200 李四取款金额为 = 100,取款成功 账户余额为===100
public class ShareData { private int num; public int getNum() { return num; } public void addNum(){ try { Thread.sleep(100); for(int i = 1; i <= 10; i++) { System.out.println(" " + Thread.currentThread().getName() + "进入方法时间===" + System.currentTimeMillis() + ",共享的num===" + (num + 1) + ",该线程对应i===" + i); num++; } } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "的num===" + num); } }
public class Thread01 extends Thread{ private ShareData shareData; public Thread01(ShareData shareData) { this.shareData = shareData; } @Override public void run() { shareData.addNum(); } }
public class Test { public static void main(String[] args) { ShareData shareData = new ShareData(); Thread thread1 = new Thread01(shareData); Thread thread2 = new Thread01(shareData); Thread thread3 = new Thread01(shareData); Thread thread4 = new Thread01(shareData); Thread thread5 = new Thread01(shareData); Thread thread6 = new Thread01(shareData); Thread thread7 = new Thread01(shareData); thread1.start(); thread2.start(); thread3.start(); thread4.start(); thread5.start(); thread6.start(); thread7.start(); } }
Thread-0进入方法时间===1553238253266,共享的num===1,该线程对应i===1 Thread-2进入方法时间===1553238253266,共享的num===1,该线程对应i===1 Thread-1进入方法时间===1553238253266,共享的num===1,该线程对应i===1 Thread-3进入方法时间===1553238253266,共享的num===1,该线程对应i===1 Thread-1进入方法时间===1553238253266,共享的num===4,该线程对应i===2 Thread-2进入方法时间===1553238253266,共享的num===3,该线程对应i===2 Thread-0进入方法时间===1553238253266,共享的num===2,该线程对应i===2 Thread-0进入方法时间===1553238253267,共享的num===8,该线程对应i===3 Thread-0进入方法时间===1553238253267,共享的num===9,该线程对应i===4 Thread-0进入方法时间===1553238253267,共享的num===10,该线程对应i===5 Thread-0进入方法时间===1553238253267,共享的num===11,该线程对应i===6 Thread-0进入方法时间===1553238253267,共享的num===12,该线程对应i===7 Thread-0进入方法时间===1553238253267,共享的num===13,该线程对应i===8 Thread-0进入方法时间===1553238253267,共享的num===14,该线程对应i===9 Thread-0进入方法时间===1553238253267,共享的num===15,该线程对应i===10 Thread-0的count===15 Thread-5进入方法时间===1553238253267,共享的num===16,该线程对应i===1 Thread-5进入方法时间===1553238253267,共享的num===17,该线程对应i===2 Thread-5进入方法时间===1553238253267,共享的num===18,该线程对应i===3 Thread-2进入方法时间===1553238253267,共享的num===7,该线程对应i===3 Thread-4进入方法时间===1553238253267,共享的num===6,该线程对应i===1 Thread-1进入方法时间===1553238253266,共享的num===6,该线程对应i===3 Thread-3进入方法时间===1553238253266,共享的num===5,该线程对应i===2 Thread-1进入方法时间===1553238253268,共享的num===22,该线程对应i===4 Thread-4进入方法时间===1553238253268,共享的num===21,该线程对应i===2 Thread-2进入方法时间===1553238253268,共享的num===20,该线程对应i===4 Thread-5进入方法时间===1553238253267,共享的num===19,该线程对应i===4 Thread-6进入方法时间===1553238253267,共享的num===18,该线程对应i===1 Thread-5进入方法时间===1553238253268,共享的num===27,该线程对应i===5 Thread-2进入方法时间===1553238253268,共享的num===26,该线程对应i===5 Thread-4进入方法时间===1553238253268,共享的num===25,该线程对应i===3 Thread-1进入方法时间===1553238253268,共享的num===24,该线程对应i===5 Thread-3进入方法时间===1553238253268,共享的num===23,该线程对应i===3 Thread-1进入方法时间===1553238253268,共享的num===32,该线程对应i===6 Thread-4进入方法时间===1553238253268,共享的num===31,该线程对应i===4 Thread-2进入方法时间===1553238253268,共享的num===30,该线程对应i===6 Thread-5进入方法时间===1553238253268,共享的num===29,该线程对应i===6 Thread-6进入方法时间===1553238253268,共享的num===28,该线程对应i===2 Thread-5进入方法时间===1553238253268,共享的num===37,该线程对应i===7 Thread-2进入方法时间===1553238253268,共享的num===36,该线程对应i===7 Thread-4进入方法时间===1553238253268,共享的num===35,该线程对应i===5 Thread-1进入方法时间===1553238253268,共享的num===34,该线程对应i===7 Thread-3进入方法时间===1553238253268,共享的num===33,该线程对应i===4 Thread-1进入方法时间===1553238253268,共享的num===42,该线程对应i===8 Thread-4进入方法时间===1553238253268,共享的num===41,该线程对应i===6 Thread-2进入方法时间===1553238253268,共享的num===40,该线程对应i===8 Thread-5进入方法时间===1553238253268,共享的num===39,该线程对应i===8 Thread-6进入方法时间===1553238253268,共享的num===38,该线程对应i===3 Thread-5进入方法时间===1553238253269,共享的num===47,该线程对应i===9 Thread-2进入方法时间===1553238253269,共享的num===46,该线程对应i===9 Thread-4进入方法时间===1553238253268,共享的num===45,该线程对应i===7 Thread-1进入方法时间===1553238253268,共享的num===44,该线程对应i===9 Thread-3进入方法时间===1553238253268,共享的num===43,该线程对应i===5 Thread-1进入方法时间===1553238253269,共享的num===52,该线程对应i===10 Thread-4进入方法时间===1553238253269,共享的num===51,该线程对应i===8 Thread-2进入方法时间===1553238253269,共享的num===50,该线程对应i===10 Thread-2的count===55 Thread-5进入方法时间===1553238253269,共享的num===49,该线程对应i===10 Thread-5的count===56 Thread-6进入方法时间===1553238253269,共享的num===48,该线程对应i===4 Thread-6进入方法时间===1553238253271,共享的num===58,该线程对应i===5 Thread-6进入方法时间===1553238253271,共享的num===59,该线程对应i===6 Thread-6进入方法时间===1553238253271,共享的num===60,该线程对应i===7 Thread-4进入方法时间===1553238253269,共享的num===55,该线程对应i===9 Thread-1的count===53 Thread-3进入方法时间===1553238253269,共享的num===53,该线程对应i===6 Thread-4进入方法时间===1553238253271,共享的num===62,该线程对应i===10 Thread-6进入方法时间===1553238253271,共享的num===61,该线程对应i===8 Thread-4的count===63 Thread-3进入方法时间===1553238253271,共享的num===63,该线程对应i===7 Thread-3进入方法时间===1553238253271,共享的num===66,该线程对应i===8 Thread-6进入方法时间===1553238253271,共享的num===65,该线程对应i===9 Thread-6进入方法时间===1553238253272,共享的num===68,该线程对应i===10 Thread-6的count===68 Thread-3进入方法时间===1553238253271,共享的num===67,该线程对应i===9 Thread-3进入方法时间===1553238253272,共享的num===70,该线程对应i===10 Thread-3的num===70
以上是关于Java多线程0:核心理论的主要内容,如果未能解决你的问题,请参考以下文章