day13. 多线程
课前回顾:Math:数学类方法:数学计算ceil()->向上取整round()->四舍五入max()->取较大值min()->取较小值abs()->绝对值BigInteger:处理比long型还大的数据方法:add:加法subtract:减法multiply:乘法divide:除法BigDecimal:处理小数,防止精度损失方法:add:加法subtract:减法multiply:乘法divide:除法->如果除不尽会报错divide(BigDecimal,scala,舍入方式)scala:保留几位小数舍入方式:直接舍去四舍五入向上+1Date:日期类,精确到毫秒构造:Date():获取当前系统时间Date(long time):设置时间,从时间原点开始算(我们是东八区,会比时间原点多8个小时)方法:setTime(long time):设置时间,从时间原点开始算(我们是东八区,会比时间原点多8个小时)getTime:获取时间毫秒值Calendar:日历类,抽象类获取:getInstance方法:add:给指定的字段设置偏移量get:获取指定字段的值set:给指定字段设置值SimpleDateFormat:日期格式化类构造:SimpleDateFormat("日期格式规则")y:年 M:月 d:天 H:时 m:分 s:秒yyyy-MM-dd HH:mm:ss-> 连接符可以随意,但是字母不能随意写方法:String format(Date) 将日期按照指定的格式去转成字符串Date parse(String s):将符合规则的字符串转成Date对象jdk8新日期类LocalDate获取:now() of(年,月,日)get开头的:获取日期字段with开头的:设置日期puls:向后偏移minus:向前偏移Period:计算日期差值Duration:计算时间差值between:计算差值DateTimeFormatter:日期格式化Arrays:专门操作数组sort:排序toString:按照指定格式打印binarySearch:二分查找copyOf:数组扩容->底层依靠System.arrayCopySystem:exit:退出jvmcurrentTimeMills:获取系统时间毫秒值gc:调用垃圾回收器arrayCopy:数组赋值今日重点:1.三种多线程创建方式(继承,实现,匿名内部类)2.会使用同步代码块去解决线程安全问题3.会使用同步方法去解决线程安全问题4.知道什么情况下出现死锁5.知道wait方法和sleep方法的区别6.完成等待唤醒案例
第一章.多线程基本了解
1.多线程之线程和进程
进程:进入到内存中运行的应用程序
![day13[多线程] - 图1](/uploads/projects/liuye-6lcqc@vk53cd/50fad9bca84ade6b306e6a3df909fdd9.png)
线程:是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序
![day13[多线程] - 图2](/uploads/projects/liuye-6lcqc@vk53cd/287c8bdccb6ae212a2e37ad51f6f8290.png)
并发: 同一个时刻多个线程同时操作了同一个数据
并行: 同一个时刻多个线程同时执行不同的程序
2.CPU调度
1.分时调度:指让所有的线程轮流获得cpu的使用权,并且平均分配每个线程占用的cpu的时间片2.抢占式调度:java程序多个线程去抢占CPU使用权,谁先抢到CPU使用权,谁先执行优先级高的线程抢到CPU使用权几率大
3.主线程介绍
1.cpu和内存之间开辟一个专门为main方法服务的通道->主线程
public class Test01 {public static void main(String[] args) {for (int i = 0; i < 5; i++) {System.out.println("哈哈哈哈");}System.out.println(Math.abs(-1));}}
![day13[多线程] - 图3](/uploads/projects/liuye-6lcqc@vk53cd/7bc2cd4c08077ee98bd3995c35c97038.png)
第二章.创建线程的方式
1.创建线程的第一种方式(extends Thread)(重点)
1.创建一个类,extends Thread类2.重写Thread中的run方法->在run中设置线程任务(该线程能干啥)3.创建自己定义的线程类对象,调用start方法(开启线程,jvm会自动执行run方法)4.start方法和run方法的区别run()->仅仅是设置线程任务,没有开启线程的功能start()->先开启线程,然后jvm自动调用run方法,执行线程任务
public class MyThread extends Thread{@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("MyThread线程执行了....."+i);}}}
public class Test02 {public static void main(String[] args) {//创建线程类对象MyThread myThread = new MyThread();//调用Thread类中的start方法,开启线程,并执行run方法myThread.start();//myThread.run();// ==============================for (int i = 0; i < 5; i++) {System.out.println("Main执行了..."+i);}}}
2.多线程在内存中的运行原理
![day13[多线程] - 图4](/uploads/projects/liuye-6lcqc@vk53cd/b6106ffb3f6db49ef69d8ff79e603a93.png)
注意:一个线程对象,不能连续start多次如果还想创建新的线程,那么重新new一次线程对象,再调用start方法
3.Thread类中的方法
String getName() -> 获取线程名字static Thread currentThread() -> 获取当前正在执行的线程对象在哪个线程中用,获取的就是哪个线程对象void setName(String name) -> 给线程设置名字static void sleep(long millis) -> 线程睡眠millis->设置的是线程睡多长时间->毫秒值
public class MyThread extends Thread{@Overridepublic void run() {for (int i = 0; i < 5; i++) {try {Thread.sleep(1000L);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName()+"...执行了"+i);}}}
public class Test01 {public static void main(String[] args) {//创建线程类对象MyThread myThread = new MyThread();myThread.setName("尼古拉斯赵四");//调用Thread类中的start方法,开启线程,并执行run方法myThread.start();// ==============================for (int i = 0; i < 5; i++) {try {Thread.sleep(2000L);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"执行了..."+i);}}}
问题:在run方法中为什么不能throws异常?答案:因为Thread中的run方法没有抛异常,所以重写之后不能throws
4.创建线程的第二种方式(实现Runnable接口)(重点)
1.定义一个类,实现(implements)Runnable接口2.重写Runnable接口中的run方法,设置线程任务3.创建自定义类对象,将对象放到Thread对象中4.调用Thread类中的start方法开启线程Thread类中的构造:Thread(Runnable target)Thread(Runnable target, String name) -> 根据实现类创建Thread对象,设置线程名字
public class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+"...执行了"+i);}}}
public class Test01 {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable,"广坤");thread.start();for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+"...执行了"+i);}}}
5.两种实现多线程的方式(区别)
1.方式1:继承Thread方式 -> 单继承,不能多继承,有局限性2.方式2:实现Runnable接口 -> 解决了单继承的限制-> 一个类可以继承父类的同时实现一个或者多个接口
6.使用匿名内部类方式创建多线程
1.匿名内部类回顾:new 父类/接口(){重写方法}.重写的方法;或者:父类/接口类型 对象名 = new 父类/接口(){重写方法}对象名.重写的方法
public class Test01 {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("线程1执行了");}}}).start();new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("线程2执行了");}}}).start();}}
![day13[多线程] - 图5](/uploads/projects/liuye-6lcqc@vk53cd/e9ba3d160474035a111b102ad72ffa82.png)
小结:
创建线程三种方式:
- 继承Thread类
a.创建一个类,继承Thread类
b.重写run方法,设置线程任务
c.创建线程类对象,调用start方法- 实现Runnable接口
a.创建一个类,实现Runnable接口
b.重写run方法,设置线程任务
c.创建实现类对象,将对象传递到Thread对象中
d.调用start方法开启线程- 匿名内部类形式:
new Thread(new Runnable(){
run(){}
}).start();
第三章.线程安全
1.线程安全问题的概述
1.当多个线程访问同一个资源的时候就会出现线程安全问题
2.线程安全问题的代码实现(重点)->有线程安全问题
public class Ticket implements Runnable{int ticket = 100;@Overridepublic void run() {while(true){if (ticket>0){//买票System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");ticket--;}}}}
public class Test {public static void main(String[] args) {Ticket ticket = new Ticket();Thread t1 = new Thread(ticket, "无忌");Thread t2 = new Thread(ticket, "三丰");Thread t3 = new Thread(ticket, "翠山");//开启三个线程t1.start();t2.start();t3.start();}}
3.线程安全问题的产生原理
cpu在多个线程之间做高速切换
4.解决线程安全问题的第一种方式使用同步代码块(重点)
1.格式:synchronized(任意对象){可能会出现线程安全的代码}2.任意对象->充当的就是锁对象3.注意:想要实现线程安全问题,必须要保证是多个线程使用的是同一个锁对象
public class Ticket implements Runnable{int ticket = 100;//创建对象Object obj = new Object();@Overridepublic void run() {while(true){try {Thread.sleep(100L);} catch (InterruptedException e) {e.printStackTrace();}synchronized (obj){if (ticket>0){//买票System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");ticket--;}}}}}
public class Test {public static void main(String[] args) {Ticket ticket = new Ticket();Thread t1 = new Thread(ticket, "无忌");Thread t2 = new Thread(ticket, "三丰");Thread t3 = new Thread(ticket, "翠山");//开启三个线程t1.start();t2.start();t3.start();}}
5.同步的原理
![day13[多线程] - 图6](/uploads/projects/liuye-6lcqc@vk53cd/eea2db5bb78a02397d0c92d54677ab19.png)
进了同步代码块就相当于抢到了锁,其他的线程就抢不到锁;出了同步代码块,相当于释放锁,其他的线程才有资格抢锁
6.解决线程安全问题的第二种方式:使用同步方法(重点)
1.1普通的同步方法
1.格式:修饰符 synchronized 返回值类型 方法名(参数){可能出现线程安全的代码}2.默认锁:this
public class Ticket implements Runnable{int ticket = 100;//创建对象Object obj = new Object();@Overridepublic void run() {while(true){try {Thread.sleep(100L);} catch (InterruptedException e) {e.printStackTrace();}method();}}//同步方法public synchronized void method(){if (ticket>0){//买票System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");ticket--;}}/*public void method(){synchronized (this){if (ticket>0){//买票System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");ticket--;}}}*/}
public class Test {public static void main(String[] args) {Ticket ticket = new Ticket();Thread t1 = new Thread(ticket, "无忌");Thread t2 = new Thread(ticket, "三丰");Thread t3 = new Thread(ticket, "翠山");//开启三个线程t1.start();t2.start();t3.start();}}
1.2.静态同步方法
1.格式:修饰符 static synchronized 返回值类型 方法名(参数){可能出现线程安全的代码}2.默认锁:当前类.class
public class Ticket implements Runnable{static int ticket = 100;//创建对象Object obj = new Object();@Overridepublic void run() {while(true){try {Thread.sleep(100L);} catch (InterruptedException e) {e.printStackTrace();}method();}}//同步方法public static synchronized void method(){if (ticket>0){//买票System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");ticket--;}}/*public static void method(){synchronized (Ticket.class){if (ticket>0){//买票System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");ticket--;}}}*/}
public class Test {public static void main(String[] args) {Ticket ticket = new Ticket();Thread t1 = new Thread(ticket, "无忌");Thread t2 = new Thread(ticket, "三丰");Thread t3 = new Thread(ticket, "翠山");//开启三个线程t1.start();t2.start();t3.start();}}
第四章.死锁(了解)
1.死锁介绍(锁嵌套就有可能产生死锁)
死锁指的是两个或者两个以上的线程在执行的过程中,由于竞争同步锁而产生的一种阻塞现象;如果没有外力的作用,他们将无法继续执行下去,这种情况就称之为死锁.
![day13[多线程] - 图7](/uploads/projects/liuye-6lcqc@vk53cd/0355d8034b02772c55166b4f09604ff4.png)
根据上图所示:线程T1正在持有R1锁,但是T1线程必须再拿到R2锁,才能继续执行;而线程T2正在持有R2锁,但是T2线程需要再次拿到R1锁,才能继续执行.这时两个线程会处于互相等待的状态,即死锁.在程序中的死锁将出现在同步代码块的嵌套中
2.死锁的分析
![day13[多线程] - 图8](/uploads/projects/liuye-6lcqc@vk53cd/7dee9a5e61864c239079f2b34cf1fb93.png)
3.代码实现
![day13[多线程] - 图9](/uploads/projects/liuye-6lcqc@vk53cd/b716d0a3a55d2d29bfbd7d75496bb333.png)
public class LockA {static LockA lockA = new LockA();}public class LockB {static LockB lockB = new LockB();}
public class DieThread implements Runnable{private boolean flag;public DieThread(boolean flag) {this.flag = flag;}@Overridepublic void run() {//线程1执行的if (flag){synchronized (LockA.lockA){System.out.println("if...lockA");synchronized (LockB.lockB){System.out.println("if...lockB");}}//线程2执行的}else{synchronized (LockB.lockB){System.out.println("else...lockB");synchronized (LockA.lockA){System.out.println("else...lockA");}}}}}
public class Test {public static void main(String[] args) {DieThread dieThread = new DieThread(true);new Thread(dieThread).start();DieThread dieThread1 = new DieThread(false);new Thread(dieThread1).start();}}
知道什么情况下会产生死锁即可:锁嵌套
第五章.线程状态
1.线程状态介绍
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State这个枚举中给出了六种线程状态:这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析。
| 线程状态 | 导致状态发生条件 |
|---|---|
| NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
| Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
| Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
| Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
| Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
| Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。或者调用过时方法stop() |
2.线程状态图
![day13[多线程] - 图10](/uploads/projects/liuye-6lcqc@vk53cd/36e3495b8ba11e7f6bd8e0946c11ee22.png)
第六章.等待唤醒
一.等待唤醒案例分析(线程之间的通信)
![day13[多线程] - 图11](/uploads/projects/liuye-6lcqc@vk53cd/ca3af01af6ce819c870a03ed463d6425.png)
二.等待唤醒案例实现
public class BaoZiPu {//定义count,表示包子private int count;//定义flag,证明有么有包子private boolean flag;public BaoZiPu() {}public BaoZiPu(int count, boolean flag) {this.count = count;this.flag = flag;}//消费线程要调用的方法,证明消费包子public void getCount() {System.out.println("消费了第..."+count+"个包子");}//生产者要调用的方法,证明生产包子public void setCount() {count++;System.out.println("生产了第........."+count+"个包子");}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}}
//生产者public class Product implements Runnable{private BaoZiPu baoZiPu;public Product(BaoZiPu baoZiPu) {this.baoZiPu = baoZiPu;}@Overridepublic void run() {while(true){try {Thread.sleep(100L);} catch (InterruptedException e) {e.printStackTrace();}synchronized (baoZiPu){if (baoZiPu.isFlag()==true){//证明有包子,生产线程waittry {baoZiPu.wait();} catch (InterruptedException e) {e.printStackTrace();}}//出了if就要生产包子baoZiPu.setCount();//改变flag状态,为true,证明生产完毕,有包子了baoZiPu.setFlag(true);//唤醒消费线程baoZiPu.notify();}}}}
//消费者public class Consumer implements Runnable{private BaoZiPu baoZiPu;public Consumer(BaoZiPu baoZiPu) {this.baoZiPu = baoZiPu;}@Overridepublic void run() {while(true){try {Thread.sleep(100L);} catch (InterruptedException e) {e.printStackTrace();}synchronized (baoZiPu){if (baoZiPu.isFlag()==false){//证明没有包子,消费线程waittry {baoZiPu.wait();} catch (InterruptedException e) {e.printStackTrace();}}//出了if就要消费包子baoZiPu.getCount();//改变flag状态,为false,证明消费完毕,没有包子了baoZiPu.setFlag(false);//唤醒生产线程baoZiPu.notify();}}}}
public class Test {public static void main(String[] args) {BaoZiPu baoZiPu = new BaoZiPu();Product product = new Product(baoZiPu);Consumer consumer = new Consumer(baoZiPu);new Thread(product).start();new Thread(consumer).start();}}
![day13[多线程] - 图12](/uploads/projects/liuye-6lcqc@vk53cd/2d6a39649220b76ae11e5a19547e6fd6.png)
三.用同步方法改造等待唤醒案例
public class BaoZiPu {//定义count,表示包子private int count;//定义flag,证明有么有包子private boolean flag;public BaoZiPu() {}public BaoZiPu(int count, boolean flag) {this.count = count;this.flag = flag;}//消费线程要调用的方法,证明消费包子public synchronized void getCount() {if (flag == false) {//证明没有包子,消费线程waittry {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//出了if就要消费包子System.out.println("消费了第..." + count + "个包子");//改变flag状态,为false,证明消费完毕,没有包子了flag = false;//唤醒生产线程this.notify();}//生产者要调用的方法,证明生产包子public synchronized void setCount() {if (flag == true) {//证明有包子,生产线程waittry {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//出了if就要生产包子count++;System.out.println("生产了第........." + count + "个包子");//改变flag状态,为true,证明生产完毕,有包子了flag = true;//唤醒消费线程this.notify();}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}}
//生产者public class Product implements Runnable{private BaoZiPu baoZiPu;public Product(BaoZiPu baoZiPu) {this.baoZiPu = baoZiPu;}@Overridepublic void run() {while(true){try {Thread.sleep(100L);} catch (InterruptedException e) {e.printStackTrace();}//生产baoZiPu.setCount();}}}
//消费者public class Consumer implements Runnable{private BaoZiPu baoZiPu;public Consumer(BaoZiPu baoZiPu) {this.baoZiPu = baoZiPu;}@Overridepublic void run() {while(true){try {Thread.sleep(100L);} catch (InterruptedException e) {e.printStackTrace();}//消费baoZiPu.getCount();}}}
public class Test {public static void main(String[] args) {BaoZiPu baoZiPu = new BaoZiPu();Product product = new Product(baoZiPu);Consumer consumer = new Consumer(baoZiPu);new Thread(product).start();new Thread(consumer).start();}}
