十一、Java多线程
- day08-线程安全,死锁,状态,通讯,线程池
- 今日目标
- 1 线程安全
- 1.1 线程安全产生的原因
- 注意 : 以上代码是有问题 , 接下来继续改进
- 通过上述代码的执行结果 , 发现了出现了负号票 , 和相同的票 , 数据有问题
- 1.2 线程的同步
- 1.3 同步代码块
- 1.4 同步方法
- 1.5 Lock锁
- 2 线程死锁
- 2.1 概述 :
- 2.2 产生条件 :
- 2.3 代码实践
- 3 线程的状态
- 4 线程通信
- 生产者和消费者案例
- 5 线程池
- 5.1 线程使用存在的问题
- 5.2 线程池的介绍
- 5.3 线程池使用的大致流程
- 5.4 线程池的好处
- 5.4 Java提供好的线程池
- 5.5 线程池处理Runnable任务
- 5.6 线程池处理Callable任务
- 模块16.多线程
- 第一章.多线程基本了解
- 1.多线程_线程和进程
- 2.并发和并行
- 3.CPU调度
- 4.主线程介绍
- 第二章.创建线程的方式(重点)
- 1.第一种方式_extends Thread
- 2.多线程在内存中的运行原理
- 3.Thread类中的方法
- 4.Thread中其他的方法
- 4.1.线程优先级
- 4.2.守护线程
- 4.3.礼让线程
- 4.4.插入线程
- 5.第二种方式_实现Runnable接口
- 6.两种实现多线程的方式区别
- 7.第三种方式_匿名内部类创建多线程
- 第三章.线程安全
- 1.线程安全问题-->线程不安全的代码
- 2.解决线程安全问题的第一种方式(使用同步代码块)
- 3.解决线程安全问题的第二种方式:同步方法
- 3.1.普通同步方法_非静态
- 3.2.静态同步方法
- 第四章.死锁(了解)
- 1.死锁介绍(锁嵌套就有可能产生死锁)
- 2.死锁的分析
- 3.代码实现
- 第五章.线程状态
- 1.线程状态介绍
- 2.线程状态图
- 模块17. 多线程
- 第一章.等待唤醒机制
- 1.等待唤醒案例分析(线程之间的通信)
- 2.等待唤醒案例实现
- 3.用同步方法改造等待唤醒案例
- 第二章.多等待多唤醒
- 1.解决多生产多消费问题(if改为while,将notify改为notifyAll)
- 第三章.Lock锁
- 1.Lock对象的介绍和基本使用
- 第四章.Callable接口_实现多线程方式三
- 第五章.线程池_实现多线程方式四
- 练习
- 第六章.定时器_Timer
day08-线程安全,死锁,状态,通讯,线程池
今日目标
- 线程安全
- 线程死锁
- 线程的状态
- 线程间通讯
- 线程池
1 线程安全
1.1 线程安全产生的原因
- 多个线程在对共享数据进行读改写的时候,可能导致的数据错乱就是线程的安全问题了
package com.itheima.ticket_demo;/*电影院*/
public class Ticket implements Runnable {private int ticketCount = 100; // 一共有一百张票@Overridepublic void run() {while (true) {// 如果票的数量为0 , 那么停止买票if (ticketCount == 0) {break;} else {// 有剩余的票 , 开始卖票ticketCount--;System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");}}}
}
package com.itheima.ticket_demo;/*1 定义一个类Ticket实现Runnable接口,里面定义一个成员变量:private int ticketCount = 100;2 在Ticket类中重写run()方法实现卖票,代码步骤如下A:判断票数大于0,就卖票,并告知是哪个窗口卖的B:票数要减1C:卖光之后,线程停止3 定义一个测试类TicketDemo,里面有main方法,代码步骤如下A:创建Ticket类的对象B:创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称C:启动线程*/
public class TicketDemo {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.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");// 开启三个线程t1.start();t2.start();t3.start();}
}
注意 : 以上代码是有问题 , 接下来继续改进
- 因为出票是有时间的 , 所有现在在每次买票之前, 休眠100毫秒 , 尝试执行代码
package com.itheima.ticket_demo;/*电影院*/
public class Ticket implements Runnable {private int ticketCount = 100; // 一共有一百张票@Overridepublic void run() {while (true) {// 如果票的数量为0 , 那么停止买票if (ticketCount <= 0) {break;} else {// 模拟出票的时间try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}// 有剩余的票 , 开始卖票ticketCount--;System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");}}}
}
package com.itheima.ticket_demo;/*1 定义一个类Ticket实现Runnable接口,里面定义一个成员变量:private int ticketCount = 100;2 在Ticket类中重写run()方法实现卖票,代码步骤如下A:判断票数大于0,就卖票,并告知是哪个窗口卖的B:票数要减1C:卖光之后,线程停止3 定义一个测试类TicketDemo,里面有main方法,代码步骤如下A:创建Ticket类的对象B:创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称C:启动线程*/
public class TicketDemo {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.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");// 开启三个线程t1.start();t2.start();t3.start();}
}
1.2 线程的同步
-
概述 : java允许多线程并发执行,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证该变量的唯一性和准确性
-
分类
- 同步代码块
- 同步方法
- 锁机制。Lock
1.3 同步代码块
同步代码块 : 锁住多条语句操作共享数据,可以使用同步代码块实现第一部分 : 格式synchronized(任意对象) {多条语句操作共享数据的代码}第二部分 : 注意1 默认情况锁是打开的,只要有一个线程进去执行代码了,锁就会关闭2 当线程执行完出来了,锁才会自动打开第三部分 : 同步的好处和弊端好处 : 解决了多线程的数据安全问题弊端 : 当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
public class Ticket implements Runnable {private int ticketCount = 100; // 一共有一百张票@Overridepublic void run() {while (true) {synchronized (Ticket.class) {// 如果票的数量为0 , 那么停止买票if (ticketCount <= 0) {break;} else {// 模拟出票的时间try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}// 有剩余的票 , 开始卖票ticketCount--;System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");}}}}
}
package com.itheima.synchronized_demo1;/*1 定义一个类Ticket实现Runnable接口,里面定义一个成员变量:private int ticketCount = 100;2 在Ticket类中重写run()方法实现卖票,代码步骤如下A:判断票数大于0,就卖票,并告知是哪个窗口卖的B:票数要减1C:卖光之后,线程停止3 定义一个测试类TicketDemo,里面有main方法,代码步骤如下A:创建Ticket类的对象B:创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称C:启动线程*/
public class TicketDemo {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.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");// 开启三个线程t1.start();t2.start();t3.start();}
}
1.4 同步方法
同步方法:就是把synchronized关键字加到方法上格式:修饰符 synchronized 返回值类型 方法名(方法参数) { }同步代码块和同步方法的区别:1 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码2 同步代码块可以指定锁对象,同步方法不能指定锁对象注意 : 同步方法时不能指定锁对象的 , 但是有默认存在的锁对象的。1 对于非static方法,同步锁就是this。2 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。 Class类型的对象
package com.itheima.synchronized_demo2;/*同步方法:就是把synchronized关键字加到方法上格式:修饰符 synchronized 返回值类型 方法名(方法参数) { }同步代码块和同步方法的区别:1 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码2 同步代码块可以指定锁对象,同步方法不能指定锁对象注意 : 同步方法时不能指定锁对象的 , 但是有默认存在的锁对象的。1 对于非static方法,同步锁就是this。2 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。 Class类型的对象*/
public class Ticket implements Runnable {private int ticketCount = 100; // 一共有一百张票@Overridepublic void run() {while (true) {if (method()) {break;}}}private synchronized boolean method() {// 如果票的数量为0 , 那么停止买票if (ticketCount <= 0) {return true;} else {// 模拟出票的时间try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}// 有剩余的票 , 开始卖票ticketCount--;System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");return false;}}
}
package com.itheima.synchronized_demo2;/*1 定义一个类Ticket实现Runnable接口,里面定义一个成员变量:private int ticketCount = 100;2 在Ticket类中重写run()方法实现卖票,代码步骤如下A:判断票数大于0,就卖票,并告知是哪个窗口卖的B:票数要减1C:卖光之后,线程停止3 定义一个测试类TicketDemo,里面有main方法,代码步骤如下A:创建Ticket类的对象B:创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称C:启动线程*/
public class TicketDemo {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.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");// 开启三个线程t1.start();t2.start();t3.start();}
}
1.5 Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象LockLock中提供了获得锁和释放锁的方法void lock():获得锁void unlock():释放锁Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化ReentrantLock的构造方法ReentrantLock():创建一个ReentrantLock的实例注意:多个线程使用相同的Lock锁对象,需要多线程操作数据的代码放在lock()和unLock()方法之间。一定要确保unlock最后能够调用
package com.itheima.synchronized_demo3;import java.util.concurrent.locks.ReentrantLock;/*虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象LockLock中提供了获得锁和释放锁的方法void lock():获得锁void unlock():释放锁Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化ReentrantLock的构造方法ReentrantLock():创建一个ReentrantLock的实例注意:多个线程使用相同的Lock锁对象,需要多线程操作数据的代码放在lock()和unLock()方法之间。一定要确保unlock最后能够调用*/
public class Ticket implements Runnable {private int ticketCount = 100; // 一共有一百张票ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true) {try {lock.lock();// 加锁// 如果票的数量为0 , 那么停止买票if (ticketCount <= 0) {break;} else {// 模拟出票的时间Thread.sleep(100);// 有剩余的票 , 开始卖票ticketCount--;System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();// 释放锁}}}
}
package com.itheima.synchronized_demo3;/*1 定义一个类Ticket实现Runnable接口,里面定义一个成员变量:private int ticketCount = 100;2 在Ticket类中重写run()方法实现卖票,代码步骤如下A:判断票数大于0,就卖票,并告知是哪个窗口卖的B:票数要减1C:卖光之后,线程停止3 定义一个测试类TicketDemo,里面有main方法,代码步骤如下A:创建Ticket类的对象B:创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称C:启动线程*/
public class TicketDemo {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.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");// 开启三个线程t1.start();t2.start();t3.start();}
}
2 线程死锁
2.1 概述 :
- 死锁是一种少见的,而且难于调试的错误,在两个线程对两个同步锁对象具有循环依赖时,就会大概率的出现死锁。我们要避免死锁的产生。否则一旦死锁,除了重启没有其他办法的
2.2 产生条件 :
- 多个线程
- 存在锁对象的循环依赖
2.3 代码实践
package com.itheima.deadlock_demo;/*死锁 :死锁是一种少见的,而且难于调试的错误,在两个线程对两个同步锁对象具有循环依赖时,就会大概率的出现死锁。我们要避免死锁的产生。否则一旦死锁,除了重启没有其他办法的*/
public class DeadLockDemo {public static void main(String[] args) {String 筷子A = "筷子A";String 筷子B = "筷子B";new Thread(new Runnable() {@Overridepublic void run() {while (true) {synchronized (筷子A) {System.out.println("小白拿到了筷子A ,等待筷子B....");synchronized (筷子B) {System.out.println("小白拿到了筷子A和筷子B , 开吃!!!!!");}}try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}, "小白").start();new Thread(new Runnable() {@Overridepublic void run() {while (true) {synchronized (筷子B) {System.out.println("小黑拿到了筷子B ,等待筷子A....");synchronized (筷子A) {System.out.println("小黑拿到了筷子B和筷子A , 开吃!!!!!");}}try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}, "小黑").start();}
}
3 线程的状态
4 线程通信
-
线程间的通讯技术就是通过等待和唤醒机制,来实现多个线程协同操作完成某一项任务,例如经典的生产者和消费者案例。等待唤醒机制其实就是让线程进入等待状态或者让线程从等待状态中唤醒,需要用到两种方法,如下:
-
等待方法 :
- void wait() 让线程进入无限等待。
- void wait(long timeout) 让线程进入计时等待
- 以上两个方法调用会导致当前线程释放掉锁资源。
-
唤醒方法 :
- void notify() 唤醒在此对象监视器(锁对象)上等待的单个线程。
- void notifyAll() 唤醒在此对象监视器上等待的所有线程。
- 以上两个方法调用不会导致当前线程释放掉锁资源
-
注意
- 等待和唤醒的方法,都要使用锁对象调用(需要在同步代码块中调用)
- 等待和唤醒方法应该使用相同的锁对象调用
-
package com.itheima.waitnotify_demo;/*1 线程进入无限等待注意:进入无限等待需要使用锁在同步代码中调用wait方法*/ public class Test1 {public static void main(String[] args) {Object obj = new Object(); // 作为锁对象new Thread(new Runnable() {@Overridepublic void run() {synchronized (obj) {System.out.println("线程开始执行");System.out.println("线程进入无线等待....");try {obj.wait(); // 进入无线等待状态 , 并释放锁} catch (InterruptedException e) {e.printStackTrace();}System.out.println("无线等待被唤醒....");}}}).start();} }
-
package com.itheima.waitnotify_demo;/*线程进入无限等待后被唤醒注意:等待和唤醒是两个或多个线程之间实现的。进入无限等待的线程是不会自动唤醒,只能通过其他线程来唤醒。*/ public class Test2 {public static void main(String[] args) {Object obj = new Object(); // 作为锁对象new Thread(new Runnable() {@Overridepublic void run() {synchronized (obj) {System.out.println("线程开始执行");System.out.println("线程进入无线等待....");try {obj.wait(); // 进入无线等待状态 , 并释放锁} catch (InterruptedException e) {e.printStackTrace();}System.out.println("无线等待被唤醒....");}}}).start();new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (obj) {obj.notify();// 随机唤醒此监视器中等待的线程 , 不会释放锁System.out.println("唤醒后 , 5秒钟后释放锁");try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}// 释放锁}}).start();} }
-
package com.itheima.waitnotify_demo;/*3 线程进入计时等待并唤醒注意:进入计时等待的线程,时间结束前可以被其他线程唤醒。时间结束后会自动唤醒*/ public class Test3 {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {synchronized (Test3.class) {System.out.println("获取到锁 , 开始执行");try {System.out.println("进入计时等待...3秒");Test3.class.wait(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("自动唤醒.");}}}).start();} }
-
生产者和消费者案例
package com.itheima.waitnotify_demo2;import sun.security.krb5.internal.crypto.Des;/*生产者步骤:1,判断桌子上是否有汉堡包如果有就等待,如果没有才生产。2,把汉堡包放在桌子上。3,叫醒等待的消费者开吃*/ public class Cooker implements Runnable {@Overridepublic void run() {while (true) {synchronized (Desk.lock) {if (Desk.count == 0) {break;} else {if (Desk.flag) {// 桌子上有食物try {Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}} else {// 桌子上没有食物System.out.println("厨师生产了一个汉堡包...");Desk.flag = true;Desk.lock.notify();}}}}} }
package com.itheima.waitnotify_demo2;import sun.security.krb5.internal.crypto.Des;/*消费者步骤:1,判断桌子上是否有汉堡包。2,如果没有就等待。3,如果有就开吃4,吃完之后,桌子上的汉堡包就没有了叫醒等待的生产者继续生产汉堡包的总数量减一*/ public class Foodie implements Runnable {@Overridepublic void run() {while (true) {synchronized (Desk.lock) {if (Desk.count == 0) {break;} else {if (Desk.flag) {// 桌子上有食物System.out.println("吃货吃了一个汉堡包...");Desk.count--; // 汉堡包的数量减少一个Desk.flag = false;// 桌子上的食物被吃掉 , 值为falseDesk.lock.notify();} else {// 桌子上没有食物try {Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}} }
package com.itheima.waitnotify_demo2;public class Test {public static void main(String[] args) {new Thread(new Foodie()).start();new Thread(new Cooker()).start();} }
5 线程池
5.1 线程使用存在的问题
- 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
如果大量线程在执行,会涉及到线程间上下文的切换,会极大的消耗CPU运算资源
5.2 线程池的介绍
- 其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
5.3 线程池使用的大致流程
- 创建线程池指定线程开启的数量
- 提交任务给线程池,线程池中的线程就会获取任务,进行处理任务。
- 线程处理完任务,不会销毁,而是返回到线程池中,等待下一个任务执行。
- 如果线程池中的所有线程都被占用,提交的任务,只能等待线程池中的线程处理完当前任
5.4 线程池的好处
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要等待线程创建 , 就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存 (每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
5.4 Java提供好的线程池
- java.util.concurrent.ExecutorService 是线程池接口类型。使用时我们不需自己实现,JDK已经帮我们实现好了
- 获取线程池我们使用工具类java.util.concurrent.Executors的静态方
- public static ExecutorService newFixedThreadPool (int num) : 指定线程池最大线程池数量获取线程池
- 线程池ExecutorService的相关方法
- Future submit(Callable task)
- Future<?> submit(Runnable task)
- 关闭线程池方法(一般不使用关闭方法,除非后期不用或者很长时间都不用,就可以关闭)
- void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务
5.5 线程池处理Runnable任务
package com.itheima.threadpool_demo;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*1 需求 :使用线程池模拟游泳教练教学生游泳。游泳馆(线程池)内有3名教练(线程)游泳馆招收了5名学员学习游泳(任务)。2 实现步骤:创建线程池指定3个线程定义学员类实现Runnable,创建学员对象给线程池*/
public class Test1 {public static void main(String[] args) {// 创建指定线程的线程池ExecutorService threadPool = Executors.newFixedThreadPool(3);// 提交任务threadPool.submit(new Student("小花"));threadPool.submit(new Student("小红"));threadPool.submit(new Student("小明"));threadPool.submit(new Student("小亮"));threadPool.submit(new Student("小白"));threadPool.shutdown();// 关闭线程池}
}class Student implements Runnable {private String name;public Student(String name) {this.name = name;}@Overridepublic void run() {String coach = Thread.currentThread().getName();System.out.println(coach + "正在教" + name + "游泳...");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(coach + "教" + name + "游泳完毕.");}
}
5.6 线程池处理Callable任务
package com.itheima.threadpool_demo;import java.util.concurrent.*;/*需求: Callable任务处理使用步骤1 创建线程池2 定义Callable任务3 创建Callable任务,提交任务给线程池4 获取执行结果<T> Future<T> submit(Callable<T> task) : 提交Callable任务方法 返回值类型Future的作用就是为了获取任务执行的结果。Future是一个接口,里面存在一个get方法用来获取值练一练:使用线程池计算 从0~n的和,并将结果返回*/
public class Test2 {public static void main(String[] args) throws ExecutionException, InterruptedException {// 创建指定线程数量的线程池ExecutorService threadPool = Executors.newFixedThreadPool(10);Future<Integer> future = threadPool.submit(new CalculateTask(100));Integer sum = future.get();System.out.println(sum);}
}// 使用线程池计算 从0~n的和,并将结果返回
class CalculateTask implements Callable<Integer> {private int num;public CalculateTask(int num) {this.num = num;}@Overridepublic Integer call() throws Exception {int sum = 0;// 求和变量for (int i = 0; i <= num; i++) {sum += i;}return sum;}
}
模块16.多线程
模块15回顾:1.Math:数学工具类abs ceil floor round max min2.BigInteger:处理超大整数a.构造:BigInteger(String s)b.方法:add subtract multiply divide3.BigDecimal:处理小数直接做运算而产生的精度损失问题a.构造:BigDecimal(String s)b.方法: add subtract multiply divide valueOf(double b) divide(BigDecimal b,保留几位小数,取舍方式) 4.Date:日期类a.构造:Date() Date(long time)b.方法:long getTime()获取时间对应的毫秒值void setTime(long time) 设置时间,从时间原点开始算5.Calendar:日历类a.获取:getInstance()b.方法:get(指定的时间字段)set(指定字段,值)add(指定的时间字段,值) -> 设置时间偏移量getTime()将Calendar转成Date6.SimpleDateFormat:日期格式化类a.构造:SimpleDateFormat(String pattern)b.方法:format parse7.jdk8新日期类:a.LocalDate:操作年月日now ofb.LocalDateTime:操作年月日时分秒now ofc.获取字段:get开头d.设置字段:with开头e.时间偏移plus开头 -> 向后偏移minus开头 -> 向前偏题f.计算时间偏差:PeriodDurationbetween方法g.DateTimeFormatter:日期格式化类获取:ofPattern()方法: format parse8.System类:arrayCopy(源数组,第源数组第几个索引开始复制,目标数组,从目标数组第几个索引开始粘贴,粘贴多少个)9.Arrays数组工具类:toString(数组)按照格式打印数组binarySearch(数组,要查找的元素) -> 二分查找sort(数组)升序排序copyOf(数组,指定新数组长度) -> 数组扩容10.包装类:基本类型对应的包装类byte Byteshort Shortint Integerlong Longfloat Float double Doublechar Charactorboolean Boolean11.Integer:a.构造:Integer(int i)Integer(String s)b.装箱:valueOf(int i)valueOf(String s)c.拆箱:intValue()12.基本类型和String之间的转换a.基本转String+"" valueOf()b.String转基本包装类中的parsexxx()13.javabean:定义的时候基本类型的属性要改成包装类模块16重点:1.会使用多线程方法,主要是start()2.会使用继承Thread的方式创建多线程3.会使用实现Runnable接口的方式实现多线程4.会使用同步代码块解决线程不安全问题5.会使用同步方法解决线程不安全问题
第一章.多线程基本了解
1.多线程_线程和进程
进程:在内存中执行的应用程序
线程:是进程中最小的执行单元
线程作用:负责当前进程中程序的运行.一个进程中至少有一个线程,一个进程还可以有多个线程,这样的应用程序就称之为多线程程序简单理解:一个功能就需要一条线程取去执行
1.使用场景: 软件中的耗时操作 -> 拷贝大文件, 加载大量的资源
所有的聊天软件
所有的后台服务器
一个线程可以干一件事,我们就可以同时做多件事了,提高了CPU的利用率
2.并发和并行
并行:在同一个时刻,有多个执行在多个CPU上(同时)执行(好比是多个人做不同的事儿)比如:多个厨师在炒多个菜
并发:在同一个时刻,有多个指令在单个CPU上(交替)执行比如:一个厨师在炒多个菜
细节:1.之前CPU是单核,但是在执行多个程序的时候好像是在同时执行,原因是CPU在多个线程之间做高速切换2.现在咱们的CPU都是多核多线程的了,比如2核4线程,那么CPU可以同时运行4个线程,此时不同切换,但是如果多了,CPU就要切换了,所以现在CPU在执行程序的时候并发和并行都存在
3.CPU调度
1.分时调度:值的是让所有的线程轮流获取CPU使用权,并且平均分配每个线程占用CPU的时间片
2.抢占式调度:多个线程轮流抢占CPU使用权,哪个线程先抢到了,哪个线程先执行,一般都是优先级高的先抢到CPU使用权的几率大,我们java程序就是抢占式调度
4.主线程介绍
主线程:CPU和内存之间开辟的转门为main方法服务的线程
第二章.创建线程的方式(重点)
1.第一种方式_extends Thread
1.定义一个类,继承Thread
2.重写run方法,在run方法中设置线程任务(所谓的线程任务指的是此线程要干的具体的事儿,具体执行的代码)
3.创建自定义线程类的对象
4.调用Thread中的start方法,开启线程,jvm自动调用run方法
public class Test01 {public static void main(String[] args) {//创建线程对象MyThread t1 = new MyThread();//调用start方法,开启线程,jvm自动调用run方法t1.start();for (int i = 0; i < 10; i++) {System.out.println("main线程..........执行了"+i);}}
}
public class MyThread extends Thread{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("MyThread...执行了"+i);}}
}
2.多线程在内存中的运行原理
注意:同一个线程对象不能连续调用多次start,如果想要再次调用start,那么咱们就new一个新的线程对象
3.Thread类中的方法
void start() -> 开启线程,jvm自动调用run方法
void run() -> 设置线程任务,这个run方法是Thread重写的接口Runnable中的run方法
String getName() -> 获取线程名字
void setName(String name) -> 给线程设置名字
static Thread currentThread() -> 获取正在执行的线程对象(此方法在哪个线程中使用,获取的就是哪个线程对象)
static void sleep(long millis)->线程睡眠,超时后自动醒来继续执行,传递的是毫秒值
public class MyThread extends Thread{@Overridepublic void run() {for (int i = 0; i < 10; i++) {//线程睡眠try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName()+"...执行了"+i);}}
}
public class Test01 {public static void main(String[] args) throws InterruptedException {//创建线程对象MyThread t1 = new MyThread();//给线程设置名字t1.setName("金莲");//调用start方法,开启线程,jvm自动调用run方法t1.start();for (int i = 0; i < 10; i++) {Thread.sleep(1000L);System.out.println(Thread.currentThread().getName()+"线程..........执行了"+i);}}
}
问题:为啥在重写的run方法中有异常只能try,不能throws
原因:继承的Thread中的run方法没有抛异常,所以在子类中重写完run方法之后就不能抛,只能try
4.Thread中其他的方法
void setPriority(int newPriority) -> 设置线程优先级,优先级越高的线程,抢到CPU使用权的几率越大,但是不是每次都先抢到int getPriority() -> 获取线程优先级void setDaemon(boolean on) -> 设置为守护线程,当非守护线程执行完毕,守护线程就要结束,但是守护线程也不是立马结束,当非守护线程结束之后,系统会告诉守护线程人家结束了,你也结束吧,在告知的过程中,守护线程会执行,只不过执行到半路就结束了static void yield() -> 礼让线程,让当前线程让出CPU使用权void join() -> 插入线程或者叫做插队线程
4.1.线程优先级
public class MyThread1 extends Thread{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"执行了......"+i);}}
}
public class Test01 {public static void main(String[] args) {MyThread1 t1 = new MyThread1();t1.setName("金莲");MyThread1 t2 = new MyThread1();t2.setName("阿庆");/*获取两个线程的优先级MIN_PRIORITY = 1 最小优先级 1NORM_PRIORITY = 5 默认优先级 5MAX_PRIORITY = 10 最大优先级 10*///System.out.println(t1.getPriority());//System.out.println(t2.getPriority());//设置优先级t1.setPriority(1);t2.setPriority(10);t1.start();t2.start();}
}
4.2.守护线程
public class Test01 {public static void main(String[] args) {MyThread1 t1 = new MyThread1();t1.setName("金莲");MyThread2 t2 = new MyThread2();t2.setName("阿庆");//将t2设置成守护线程t2.setDaemon(true);t1.start();t2.start();}
}
public class MyThread1 extends Thread{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"执行了......"+i);}}
}
public class MyThread2 extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName()+"执行了..."+i);}}
}
4.3.礼让线程
场景说明:如果两个线程一起执行,可能会执行一会儿线程A,再执行一会线程B,或者可能线程A执行完毕了,线程B在执行那么我们能不能让两个线程尽可能的平衡一点 -> 尽量让两个线程交替执行
注意:只是尽可能的平衡,不是绝对的你来我往,有可能线程A线程执行,然后礼让了,但是回头A又抢到CPU使用权了
public class Test01 {public static void main(String[] args) {MyThread1 t1 = new MyThread1();t1.setName("金莲");MyThread1 t2 = new MyThread1();t2.setName("阿庆");t1.start();t2.start();}
}
public class MyThread1 extends Thread{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"执行了......"+i);Thread.yield();}}
}
4.4.插入线程
public class MyThread1 extends Thread{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"执行了......"+i);}}
}
public class Test01 {public static void main(String[] args) throws InterruptedException {MyThread1 t1 = new MyThread1();t1.setName("金莲");t1.start();/*表示把t1插入到当前线程之前,t1要插到main线程之前,所以当前线程就是main线程*/t1.join();for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"执行了......"+i);}}
}
5.第二种方式_实现Runnable接口
1.创建类,实现Runnable接口
2.重写run方法,设置线程任务
3.利用Thread类的构造方法:Thread(Runnable target),创建Thread对象(线程对象),将自定义的类当参数传递到Thread构造中 -> 这一步是让我们自己定义的类成为一个真正的线程类对象
4.调用Thread中的start方法,开启线程,jvm自动调用run方法
public class Test01 {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();/*Thread(Runnable target)*/Thread t1 = new Thread(myRunnable);//调用Thread中的start方法,开启线程t1.start();for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"...执行了"+i);}}
}
public class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"...执行了"+i);}}
}
6.两种实现多线程的方式区别
1.继承Thread:继承只支持单继承,有继承的局限性
2.实现Runnable:没有继承的局限性, MyThread extends Fu implements Runnable
7.第三种方式_匿名内部类创建多线程
严格意义上来说,匿名内部类方式不属于创建多线程方式其中之一,因为匿名内部类形式建立在实现Runnable接口的基础上完成的
匿名内部类回顾: 1.new 接口/抽象类(){重写方法}.重写的方法();2.接口名/类名 对象名 = new 接口/抽象类(){重写方法}对象名.重写的方法();
public class Test02 {public static void main(String[] args) {/*Thread(Runnable r)Thread(Runnable target, String name) :name指的是给线程设置名字*/new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"...执行了"+i);}}},"阿庆").start();new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"...执行了"+i);}}},"金莲").start();}
}
第三章.线程安全
1.什么时候发生:当多个线程访问同一个资源时,导致了数据有问题
1.线程安全问题–>线程不安全的代码
public class MyTicket implements Runnable{//定义100张票int ticket = 100;@Overridepublic void run() {while(true){if (ticket>0){System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");ticket--;}}}
}
public class Test01 {public static void main(String[] args) {MyTicket myTicket = new MyTicket();Thread t1 = new Thread(myTicket, "赵四");Thread t2 = new Thread(myTicket, "刘能");Thread t3 = new Thread(myTicket, "广坤");t1.start();t2.start();t3.start();}
}
原因:CPU在多个线程之间做高速切换导致的
2.解决线程安全问题的第一种方式(使用同步代码块)
1.格式:synchronized(任意对象){线程可能出现不安全的代码}
2.任意对象:就是我们的锁对象
3.执行:一个线程拿到锁之后,会进入到同步代码块中执行,在此期间,其他线程拿不到锁,就进不去同步代码块,需要在同步代码块外面等待排队,需要等着执行的线程执行完毕,出了同步代码块,相当于释放锁了,等待的线程才能抢到锁,才能进入到同步代码块中执行
public class MyTicket implements Runnable{//定义100张票int ticket = 100;//任意new一个对象Object obj = new Object();@Overridepublic void run() {while(true){try {Thread.sleep(100L);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (obj){if (ticket>0){System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");ticket--;}}}}
}
public class Test01 {public static void main(String[] args) {MyTicket myTicket = new MyTicket();Thread t1 = new Thread(myTicket, "赵四");Thread t2 = new Thread(myTicket, "刘能");Thread t3 = new Thread(myTicket, "广坤");t1.start();t2.start();t3.start();}
}
3.解决线程安全问题的第二种方式:同步方法
3.1.普通同步方法_非静态
1.格式:修饰符 synchronized 返回值类型 方法名(参数){方法体return 结果}
2.默认锁:this
public class MyTicket implements Runnable{//定义100张票int ticket = 100;@Overridepublic void run() {while(true){try {Thread.sleep(100L);} catch (InterruptedException e) {throw new RuntimeException(e);}//method01();method02();}}/* public synchronized void method01(){if (ticket>0){System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");ticket--;}}*/public void method02(){synchronized(this){System.out.println(this+"..........");if (ticket>0){System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");ticket--;}}}}
public class Test01 {public static void main(String[] args) {MyTicket myTicket = new MyTicket();System.out.println(myTicket);Thread t1 = new Thread(myTicket, "赵四");Thread t2 = new Thread(myTicket, "刘能");Thread t3 = new Thread(myTicket, "广坤");t1.start();t2.start();t3.start();}
}
3.2.静态同步方法
1.格式:修饰符 static synchronized 返回值类型 方法名(参数){方法体return 结果}
2.默认锁:class对象
public class MyTicket implements Runnable{//定义100张票static int ticket = 100;@Overridepublic void run() {while(true){try {Thread.sleep(100L);} catch (InterruptedException e) {throw new RuntimeException(e);}//method01();method02();}}/*public static synchronized void method01(){if (ticket>0){System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");ticket--;}}*/public static void method02(){synchronized(MyTicket.class){if (ticket>0){System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");ticket--;}}}
}
public class Test01 {public static void main(String[] args) {MyTicket myTicket = new MyTicket();Thread t1 = new Thread(myTicket, "赵四");Thread t2 = new Thread(myTicket, "刘能");Thread t3 = new Thread(myTicket, "广坤");t1.start();t2.start();t3.start();}
}
第四章.死锁(了解)
1.死锁介绍(锁嵌套就有可能产生死锁)
指的是两个或者两个以上的线程在执行的过程中由于竞争同步锁而产生的一种阻塞现象;如果没有外力的作用,他们将无法继续执行下去,这种情况称之为死锁
根据上图所示:线程1正在持有锁1,但是线程1必须再拿到锁2,才能继续执行
而线程2正在持有锁2,但是线程2需要再拿到锁1,才能继续执行
此时两个线程处于互相等待的状态,就是死锁,在程序中的死锁将出现在同步代码块的嵌套中
2.死锁的分析
3.代码实现
public class LockA {public static LockA lockA = new LockA();
}
public class LockB {public static LockB lockB = new LockB();
}
public class DieLock implements Runnable{private boolean flag;public DieLock(boolean flag) {this.flag = flag;}@Overridepublic void run() {if (flag){synchronized (LockA.lockA){System.out.println("if...lockA");synchronized (LockB.lockB){System.out.println("if...lockB");}}}else{synchronized (LockB.lockB){System.out.println("else...lockB");synchronized (LockA.lockA){System.out.println("else...lockA");}}}}
}
public class Test01 {public static void main(String[] args) {DieLock dieLock1 = new DieLock(true);DieLock dieLock2 = new DieLock(false);new Thread(dieLock1).start();new Thread(dieLock2).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。 |
Terminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。或者调用过时方法stop() |
2.线程状态图
模块17. 多线程
模块16回顾:1.创建多线程:继承Threada.定义一个类,继承Threadb.重写run方法,设置线程任务c.创建自定义线程对象d.调用start方法,开启线程,jvm自动执行run方法实现Runnable接口:a.定义一个类,实现Runnableb.重写run方法,设置线程任务c.创建自定义线程对象,传递到Thread对象中d.调用start方法,开启线程,jvm自动调用run方法匿名内部类形式创建:new Thread(new Runnable(){重写run}).start();2.Thread中的方法:a.start():开启线程,jvm自动调用run方法b.getName():获取线程名字c.setName(String name):设置线程名字d.currentThread():获取当前正在执行的线程对象e.sleep(long time):线程睡眠f.setPriority(int n) :设置线程优先级g.getPriority():获取线程优先级h.setDaemon(true):设置为守护线程i.yield():礼让线程j:join():插队线程3.线程安全:a.同步代码块:synchronized(锁对象){}b.同步方法: -> 在定义方法的时候加上synchronized关键字非静态:默认锁this静态的:默认锁class对象模块17重点:1.会用wait和notify两个方法2.会使用Lock锁对象3.会利用Callable接口实现多线程4.会使用线程池完成多线程
第一章.等待唤醒机制
1.等待唤醒案例分析(线程之间的通信)
要求:一个线程生产,一个线程消费,不能连续生产,不能连续消费 -> 等待唤醒机制(生产者,消费者)(线程之间的通信)
方法 | 说明 |
---|---|
void wait() | 线程等待,等待的过程中线程会释放锁,需要被其他线程调用notify方法将其唤醒,重新抢锁执行 |
void notify() | 线程唤醒,一次唤醒一个等待线程;如果有多条线程等待,则随机唤醒一条等待线程 |
void notifyAll() | 唤醒所有等待线程 |
wait和notify方法需要锁对象调用,所以需要用到同步代码块中,而且必须是同一个锁对象
2.等待唤醒案例实现
/*count和flag可以定义成包装类但是要记得给count和flag手动赋值不然对于本案例来说,容易出现空指针异常*/
public class BaoZiPu {//代表包子的countprivate int count;//代表是否有包子的flagprivate boolean flag;public BaoZiPu() {}public BaoZiPu(int count, boolean flag) {this.count = count;this.flag = flag;}/*getCount 改造成消费包子方法直接输出count*/public void getCount() {System.out.println("消费了..............第"+count+"个包子");}/*setCount 改造成生产包子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) {throw new RuntimeException(e);}synchronized (baoZiPu){//1.判断flag是否为true,如果是true,证明有包子,生产线程等待if (baoZiPu.isFlag()==true){try {baoZiPu.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}//2.如果flag为false,证明没有包子,开始生产baoZiPu.setCount();//3.改变flag状态,为true,证明生产完了,有包子了baoZiPu.setFlag(true);//4.唤醒消费线程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) {throw new RuntimeException(e);}synchronized (baoZiPu){//1.判断flag是否为false,如果是false,证明没有包子,消费线程等待if (baoZiPu.isFlag()==false){try {baoZiPu.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}//2.如果flag为true,证明有包子,开始消费baoZiPu.getCount();//3.改变flag状态,为false,证明消费完了,没 有包子了baoZiPu.setFlag(false);//4.唤醒生产线程baoZiPu.notify();}}}
}
public class Test01 {public static void main(String[] args) {BaoZiPu baoZiPu = new BaoZiPu();Product product = new Product(baoZiPu);Consumer consumer = new Consumer(baoZiPu);Thread t1 = new Thread(product);Thread t2 = new Thread(consumer);t1.start();t2.start();}
}
3.用同步方法改造等待唤醒案例
/*count和flag可以定义成包装类但是要记得给count和flag手动赋值不然对于本案例来说,容易出现空指针异常*/
public class BaoZiPu {//代表包子的countprivate int count;//代表是否有包子的flagprivate boolean flag;public BaoZiPu() {}public BaoZiPu(int count, boolean flag) {this.count = count;this.flag = flag;}/*getCount 改造成消费包子方法直接输出count*/public synchronized void getCount() {//1.判断flag是否为false,如果是false,证明没有包子,消费线程等待if (this.flag == false) {try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}//2.如果flag为true,证明有包子,开始消费System.out.println("消费了..............第" + count + "个包子");//3.改变flag状态,为false,证明消费完了,没 有包子了this.flag = false;//4.唤醒生产线程this.notify();}/*setCount 改造成生产包子count++*/public synchronized void setCount() {//1.判断flag是否为true,如果是true,证明有包子,生产线程等待if (this.flag == true) {try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}//2.如果flag为false,证明没有包子,开始生产count++;System.out.println("生产了...第" + count + "个包子");//3.改变flag状态,为true,证明生产完了,有包子了this.flag = true;//4.唤醒消费线程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) {throw new RuntimeException(e);}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) {throw new RuntimeException(e);}baoZiPu.getCount();}}
}
public class Test01 {public static void main(String[] args) {BaoZiPu baoZiPu = new BaoZiPu();Product product = new Product(baoZiPu);Consumer consumer = new Consumer(baoZiPu);Thread t1 = new Thread(product);Thread t2 = new Thread(consumer);t1.start();t2.start();}
}
第二章.多等待多唤醒
1.解决多生产多消费问题(if改为while,将notify改为notifyAll)
public class Test01 {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(product).start();new Thread(product).start();new Thread(consumer).start();new Thread(consumer).start();new Thread(consumer).start();}
}
/*count和flag可以定义成包装类但是要记得给count和flag手动赋值不然对于本案例来说,容易出现空指针异常*/
public class BaoZiPu {//代表包子的countprivate int count;//代表是否有包子的flagprivate boolean flag;public BaoZiPu() {}public BaoZiPu(int count, boolean flag) {this.count = count;this.flag = flag;}/*getCount 改造成消费包子方法直接输出count*/public synchronized void getCount() {//1.判断flag是否为false,如果是false,证明没有包子,消费线程等待while (this.flag == false) {try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}//2.如果flag为true,证明有包子,开始消费System.out.println("消费了..............第" + count + "个包子");//3.改变flag状态,为false,证明消费完了,没 有包子了this.flag = false;//4.唤醒所有等待线程this.notifyAll();}/*setCount 改造成生产包子count++*/public synchronized void setCount() {//1.判断flag是否为true,如果是true,证明有包子,生产线程等待while (this.flag == true) {try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}//2.如果flag为false,证明没有包子,开始生产count++;System.out.println("生产了...第" + count + "个包子");//3.改变flag状态,为true,证明生产完了,有包子了this.flag = true;//4.唤醒所有等待线程this.notifyAll();}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) {throw new RuntimeException(e);}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) {throw new RuntimeException(e);}baoZiPu.getCount();}}
}
第三章.Lock锁
1.Lock对象的介绍和基本使用
1.概述:Lock是一个接口
2.实现类:ReentrantLock
3.方法:lock() 获取锁unlock() 释放锁
public class MyTicket implements Runnable {//定义100张票int ticket = 100;//创建Lock对象Lock lock = new ReentrantLock();@Overridepublic void run() {while (true) {try {Thread.sleep(100L);//获取锁lock.lock();if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");ticket--;}} catch (InterruptedException e) {throw new RuntimeException(e);}finally {//释放锁lock.unlock();}}}
}
public class Test01 {public static void main(String[] args) {MyTicket myTicket = new MyTicket();Thread t1 = new Thread(myTicket, "赵四");Thread t2 = new Thread(myTicket, "刘能");Thread t3 = new Thread(myTicket, "广坤");t1.start();t2.start();t3.start();}
}
synchronized:不管是同步代码块还是同步方法,都需要在结束一对{}之后,释放锁对象 Lock:是通过两个方法控制需要被同步的代码,更灵活
第四章.Callable接口_实现多线程方式三
1.概述:Callable<V>是一个接口,类似于Runnable
2.方法:V call() -> 设置线程任务的,类似于run方法
3.call方法和run方法的区别:a.相同点:都是设置线程任务的b.不同点:call方法有返回值,而且有异常可以throwsrun方法没有返回值,而且有异常不可以throws4.<V> a.<V>叫做泛型b.泛型:用于指定我们操作什么类型的数据,<>中只能写引用数据类型,如果泛型不写,默认是Object类型数据c.实现Callable接口时,指定泛型是什么类型的,重写的call方法返回值就是什么类型的5.获取call方法返回值: FutureTask<V>a. FutureTask<V> 实现了一个接口: Future <V>b. FutureTask<V>中有一个方法:V get() -> 获取call方法的返回值
public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {return "涛哥和金莲...的故事";}
}
public class Test {public static void main(String[] args) throws ExecutionException, InterruptedException {MyCallable myCallable = new MyCallable();/*FutureTask(Callable<V> callable)*/FutureTask<String> futureTask = new FutureTask<>(myCallable);//创建Thread对象-> Thread(Runnable target)Thread t1 = new Thread(futureTask);t1.start();//调用get方法获取call方法返回值System.out.println(futureTask.get());}
}
第五章.线程池_实现多线程方式四
1.问题:之前来一个线程任务,就需要创建一个线程对象去执行,用完还要销毁线程对象,如果线程任务多了,就需要频繁创建线程对象和销毁线程对象,这样会耗费内存资源,所以我们就想线程对象能不能循环利用,用的时候直接拿线程对象,用完还回去
1.如何创建线程池对象:用具类-> Executors
2.获取线程池对象:Executors中的静态方法:static ExecutorService newFixedThreadPool(int nThreads) a.参数:指定线程池中最多创建的线程对象条数b.返回值ExecutorService 是线程池,用来管理线程对象3.执行线程任务: ExecutorService中的方法Future<?> submit(Runnable task) 提交一个Runnable任务用于执行 Future<T> submit(Callable<T> task) 提交一个Callable任务用于执行 4.submit方法的返回值:Future接口用于接收run方法或者call方法返回值的,但是run方法没有返回值,所以可以不用Future接收,执行call方法需要用Future接收Future中有一个方法:V get() 用于获取call方法返回值5. ExecutorService中的方法:void shutdown() 启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务
public class MyRunnable implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"...执行了");}
}
public class Test01 {public static void main(String[] args) {//创建线程池对象ExecutorService es = Executors.newFixedThreadPool(2);es.submit(new MyRunnable());es.submit(new MyRunnable());es.submit(new MyRunnable());//es.shutdown();//关闭线程池对象}
}
public class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {return 1;}
}
public class Test02 {public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService es = Executors.newFixedThreadPool(2);Future<Integer> future = es.submit(new MyCallable());System.out.println(future.get());}
}
练习
需求:创建两个线程任务,一个线程任务完成1-100的和,一个线程任务返回一个字符串
public class MyString implements Callable<String> {@Overridepublic String call() throws Exception {return "那一夜,你没有拒绝我,那一夜,你伤害了我";}
}
public class MySum implements Callable<Integer> {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 1; i <= 100; i++) {sum+=i;}return sum;}
}
public class Test01 {public static void main(String[] args) throws ExecutionException, InterruptedException {//创建线程池对象ExecutorService es = Executors.newFixedThreadPool(2);Future<String> f1 = es.submit(new MyString());Future<Integer> f2 = es.submit(new MySum());System.out.println(f1.get());System.out.println(f2.get());}
}
第六章.定时器_Timer
1.概述:定时器
2.构造:Timer()
3.方法:void schedule(TimerTask task, Date firstTime, long period) task:抽象类,是Runnable的实现类firstTime:从什么时间开始执行period: 每隔多长时间执行一次,设置的是毫秒值
public class Demo01Timer {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("金莲对涛哥说:涛哥,快起床了~~~");}},new Date(),2000L);}
}