文章目录
1.多线程相关概念
-
进程:cpu从硬盘中读取一段程序到内存中,该执行程序的实例就叫做进程。一个程序如果被cpu多次被读取到内存中,则变成多个独立的进程(多进程)
-
线程:线程是程序执行的最小单位,在一个进程中可以有多个不同的线程。
进程是资源分配最小单位,线程是程序执行的最小单位
-
串行与并行的概念
串行也就是单线程执行,代码效率比较低,代码自上而下执行
并行就是多个线程一起执行,效率比较高 -
并行和并发的概念
并行
:多个线程在不同CPU上同时执行
并发
:线程在CPU上进行交替执行
-
使用多线程一定提高效率吗?
不一定,需要了解cpu调度的算法
就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务如果生产环境中开启几百个或者上千个线程,而我们的服务器核数8核
16核 32核,这么多线程都会在我们这些cpu上做上下文切换
上下文切换:
从该线程执行切换到另外的线程 该线程—运行切换为就绪状态
。 -
同步与异步的概念
- 同步概念:就是代码从上向下执行
- 异步的概念:单独分支执行 相互之间没有任何影响
2.创建线程的三种方式
2.1方式一:继承Thread类进行实现
- 自定义类并继承Thread类
- 重写run方法
- 创建子类对象,并启动线程
void 线程对象.setName(String name)
:为线程设置名字
String getName()
:获取线程的名字
方法名 | 说明 |
---|---|
void run() | 在线程开启后,此方法将被调用执行 |
void start() | 使此线程开始执行,Java虚拟机会调用run方法() |
MyThread.java
package com.zhxd.test;
public class MyThread extends Thread{
@Override
public void run() {
//书写线程执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "Hello World");
}
}
}
ThreadDemo.java
package com.zhxd.test;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程第一种启动方式
* 1. 自定义类并继承Thread类
* 2. 重写run方法
* 3. 创建子类对象,并启动线程
* */
MyThread myThread1 = new MyThread();
myThread1.setName("线程1");
MyThread myThread2 = new MyThread();
myThread2.setName("线程2");
myThread1.start();
myThread2.start();
}
}
2.2方式二:实现Runnable接口实现
- 自定义一个类实现Runnable接口
- 重写run方法
- 创建自己的类对象
- 创建Thread类对象,并启动线程
Thread.currentThread()
:获得当前线程
MyRunnable.java
package com.zhxd.test;
public class MyRunnale implements Runnable {
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "HelloWorld");
}
}
}
RunnableTest.java
package com.zhxd.test;
public class RunnableTest {
public static void main(String[] args) {
/*
* 多线程实现的第二种方式
* 1. 自定义一个类实现Runnable接口
* 2. 重写run方法
* 3. 创建自己的类对象
* 4. 创建Thread类对象,并启动线程
* */
MyRunnale myRunnale = new MyRunnale();
Thread thread = new Thread(myRunnale);
thread.setName("线程1");
thread.start();
Thread thread2 = new Thread(myRunnale);
thread2.setName("线程2");
thread2.start();
}
}
2.3方式三:实现Callable接口
- 方法介绍
方法名 | 说明 |
---|---|
V call() | 计算结果,如果无法计算结果,则抛出一个异常 |
FutureTask(Callable<·V> callable) | 创建一个 FutureTask,一旦运行就执行给定的 Callable |
V get() | 如有必要,等待计算完成,然后获取其结果 |
- 实现步骤
- 定义一个类MyCallable实现Callable接口
- 在MyCallable类中重写call()方法
- 创建MyCallable类的对象
- 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
- 创建Thread类的对象,把FutureTask对象作为构造方法的参数
- 启动线程
- 再调用get方法,就可以获取线程结束之后的结果。
package com.zhxd.test;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("跟女孩表白" + i);
}
//返回值表示线程运行完毕之后的结果
return "答应";
}
}
package com.zhxd.test;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//线程开启之后需要执行里面的call方法
MyCallable myCallable = new MyCallable();
//可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
FutureTask<String> futureTask = new FutureTask<String>(myCallable);
//创建线程对象
Thread thread = new Thread(futureTask);
//开启线程
thread.start();
//获取线程执行结果
System.out.println(futureTask.get());
}
}
2.4三种创建线程的方式对比
三种实现方式的对比
- 实现Runnable、Callable接口
- 好处: 扩展性强,实现该接口的同时还可以继承其他的类
- 缺点: 编程相对复杂,不能直接使用Thread类中的方法
- 继承Thread类
- 好处: 编程比较简单,可以直接使用Thread类中的方法
- 缺点: 可以扩展性较差,不能再继承其他的类
2.5Thread常用方法
方法名 | 说明 |
---|---|
void setName(String name) | 将此线程的名称更改为等于参数name |
String getName() | 返回此线程的名称 |
static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
static void sleep(int mills) | 线程睡眠,参数为睡眠等待时间,单位为毫秒 |
-线程调度
-
两种调度方式
分时调度
模型:所有线程轮流使用 CPU 的使用权,平均分配
每个线程占用 CPU 的时间片抢占式调度
模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
-
Java使用的是
抢占式调度模型
-
随机性
假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的。
方法名 | 说明 |
---|---|
final int getPriority() | 返回此线程的优先级 |
final void setPriority(int newPriority) | 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10 |
package com.zhxd.test;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Demo {
public static void main(String[] args) {
//优先级1-10.默认是5
System.out.println(Thread.currentThread().getPriority());//5
MyCallale myCallale = new MyCallale();
FutureTask futureTask = new FutureTask(myCallale);
Thread thread1 = new Thread(futureTask);
thread1.setName("飞机");
//设置优先级
thread1.setPriority(10);
thread1.start();
MyCallale myCallale2 = new MyCallale();
FutureTask futureTask2 = new FutureTask(myCallale2);
Thread thread2 = new Thread(futureTask2);
thread2.setName("坦克");
thread2.setPriority(2);
thread2.start();
}
}
class MyCallale implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
return "线程执行完毕";
}
}
- 线程守护
方法名 | 说明 |
---|---|
void setDaemon(boolean on) | 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 |
package com.zhxd.test;
public class Test {
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1("女神");
MyThread2 myThread2 = new MyThread2("备胎");
//设置备胎线程为守护线程,当女神线程结束时,备胎线程也会跟着陆续结束
//当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
myThread2.setDaemon(true);
myThread1.start();
myThread2.start();
}
}
class MyThread1 extends Thread {
MyThread1(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "----" + i);
}
}
}
class MyThread2 extends Thread {
MyThread2(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "----" + i);
}
}
}
- 礼让/插入线程
方法名 | 说明 |
---|---|
void static yield() | 礼让线程,出让当前CPU的使用权 |
void final join() | 插入/插队线程,合并到当前线程 |
package com.zhxd.test;
/*
* author zhxd
* ClassName Demo
*/
public class Demo {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("飞机");
MyThread myThread2 = new MyThread("坦克");
myThread1.start();
myThread2.start();
}
}
class MyThread extends Thread{
MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@" + i);
Thread.yield();// 飞机 坦克(表示出让当前CPU使用权)
}
}
}
package com.zhxd.test;
/*
* author zhxd
* ClassName Demo
*/
public class Demo {
//public final void join() 插入/插队线程(抢占线程执行完毕后,再执行其他线程)
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread("飞机");
myThread.start();
//合并到当前线程
//myThread 飞机线程
//当前线程 main
myThread.join();
for (int i = 0; i < 10; i++) {
System.out.println("main线程" + i);
}
}
}
class MyThread extends Thread{
MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}
3.线程的生命周期
- 创建
- 就绪状态(有执行资格,没有执行权)–正在抢占CPU
- 运行(有执行资格以及执行权)
- 状态在2和3之间切换,如果遇到意外状态(例如阻塞)这时没有执行资格以及执行权,直到意外状态结束后才切换回就绪状态
- 死亡–线程的run方法执行完毕,线程死亡,变成垃圾
4.线程同步
场景:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
- 实现步骤:
- 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
- 在SellTicket类中重写run()方法实现卖票,代码步骤如下
- 判断票数大于0,就卖票,并告知是哪个窗口卖的
- 卖了票之后,总票数要减1
- 票卖没了,线程停止
- 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
1)创建SellTicket类的对象
2)创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
3)启动线程
package com.zhxd.test;
public class Test{
public static void main(String[] args) {
//创建三个售票窗口
SellTicket sellTicket = new SellTicket();
Thread thread = new Thread(sellTicket, "窗口1");
Thread thread2 = new Thread(sellTicket,"窗口2");
Thread thread3 = new Thread(sellTicket,"窗口3");
//三个窗口开始售票
thread.start();
thread2.start();
thread3.start();
}
}
class SellTicket implements Runnable {
private int ticket = 100;
//售卖电影票的方法
@Override
public void run() {
while(true) {
if(ticket <= 0) {
//卖完票了结束线程
break;
}else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "在卖" + ticket + "张票");
}
ticket--;
}
}
}
卖票案例的问题
- 相同的票出现了多次
- 出现了负数的票
问题产生原因
- 线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题
安全问题出现的条件
- 是多线程环境
- 有共享数据
- 有多条语句操作共享数据
如何解决多线程安全的问题
- 基本思想:让程序没有安全问题的环境
如何实现?
- 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
- Java提供了同步代码块的方式来解决
同步代码块格式
synchronized(任意对象) {
多条语句操作共享数据的代码
}
- synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
- 好处:解决了多线程的数据安全问题
- 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
package com.zhxd.test;
public class Test {
public static void main(String[] args) {
//创建三个售票窗口
SellTicket sellTicket = new SellTicket();
Thread thread = new Thread(sellTicket, "窗口1");
Thread thread2 = new Thread(sellTicket, "窗口2");
Thread thread3 = new Thread(sellTicket, "窗口3");
//三个窗口开始售票
thread.start();
thread2.start();
thread3.start();
}
}
class SellTicket implements Runnable {
private int ticket = 1;
//创建锁对象,前面加上static表示唯一的,无论哪个线程用到该对象都是同一个
static Object object = new Object();
//售卖电影票的方法
@Override
public void run() {
while (true) {
//线程同步,防止线程同时修改资源,造成票号重复
//线程进来后,就会把这段代码给锁起来
synchronized (object) {
if (ticket > 100) {
//卖完票了结束线程
break;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "在卖" + ticket + "张票");
}
ticket++;
}
//线程出来了,这段代码的锁就被释放了
}
}
}
同步方法
- 同步方法的格式
就是把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
}
- 同步方法的锁对象是
this
- 同步静态方法的锁对象是
类名.class
(class对象)
卖票案例改造成同步方法的写法
package com.zhxd.test;
public class Test {
public static void main(String[] args) {
//创建三个售票窗口
SellTicket sellTicket = new SellTicket();
Thread thread = new Thread(sellTicket, "窗口1");
Thread thread2 = new Thread(sellTicket, "窗口2");
Thread thread3 = new Thread(sellTicket, "窗口3");
//三个窗口开始售票
thread.start();
thread2.start();
thread3.start();
}
}
class SellTicket implements Runnable {
private int ticket = 1;
//创建锁对象,前面加上static表示唯一的,无论哪个线程用到该对象都是同一个
static Object object = new Object();
//售卖电影票的方法
@Override
public void run() {
while (true) {
if (isFinish()) break;
}
}
public synchronized boolean isFinish() {
if (ticket > 100) {
return true;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "在卖" + ticket + "张票");
}
ticket++;
return false;
}
}
5.Lock锁
虽然我们可以理解同步代码块和同步方法(自动上锁)的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock(手动上锁)。Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
- ReentrantLock构造方法
方法名 | 说明 |
---|---|
ReentrantLock() | 创建一个ReentrantLock的实例 |
-
加锁解锁方法
方法名 说明 void lock() 获得锁 void unlock() 释放锁
package com.zhxd.test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) {
//创建三个售票窗口
SellTicket sellTicket = new SellTicket();
Thread thread = new Thread(sellTicket, "窗口1");
Thread thread2 = new Thread(sellTicket, "窗口2");
Thread thread3 = new Thread(sellTicket, "窗口3");
//三个窗口开始售票
thread.start();
thread2.start();
thread3.start();
}
}
class SellTicket implements Runnable {
private int ticket = 1;
//创建锁对象,多线程锁对象必须唯一
private static Lock lock = new ReentrantLock();
//售卖电影票的方法
@Override
public void run() {
while (true) {// 1 //2 //3
lock.lock();//手动上锁
try{
if(ticket == 100) {
break;
}else {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "在卖" + ticket + "张票");
ticket++;
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();//释放锁 如果不把释放锁放在finally之内,第100张票的时候锁将不释放
}
}
}
}
6.死锁
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
什么情况下会产生死锁
- 资源有限
- 同步嵌套
package com.zhxd.test;
public class Test {
public static void main(String[] args) {
MyThread t1 = new MyThread("线程A");
MyThread t2 = new MyThread("线程B");
t1.start();
t2.start();
}
}
package com.zhxd.test;
public class MyThread extends Thread {
private static Object objA = new Object();
private static Object objB = new Object();
MyThread(String name) {
super(name);
}
@Override
public void run() {
if ("线程A".equals(getName())) {// A
synchronized (objA) {//A线程拿到A锁 B锁被B线程持有
System.out.println("线程A拿到A锁,准备拿B锁");
synchronized (objB) {
System.out.println("线程A拿到A锁和B锁,完成第一轮");
}
}
} else if ("线程B".equals(getName())) {// B
synchronized (objB) {//B线程拿到B锁 A锁被A线程持有
System.out.println("线程B拿到B锁,准备拿A锁");
synchronized (objA) {
System.out.println("线程B拿到A锁和B锁,完成第一轮");
}
}
}
}
}
7.等待唤醒机制
生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。所谓生产者消费者问题,实际上主要是包含了两类线程:一类是生产者线程用于生产数据,一类是消费者线程用于消费数据
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库,生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为,消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为。
- Object类的等待和唤醒方法
方法名 | 说明 |
---|---|
void wait() | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
package com.zhxd.test;
//桌子(属于生产者和消费者的第三者)
public class Desk {
//判断桌子是否有食物
static boolean isHaveFood = false;
//count:判断线程是否要结束,吃面条的总碗数量
static int noodlesNumber = 10;
//线程锁对象
static Object lock = new Object();
}
package com.zhxd.test;
//厨师 生产者
public class Cooker extends Thread {
Cooker(String name) {
super(name);
}
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
//判断线程的状态是否结束
if(Desk.noodlesNumber == 0) {
break;
}else {
//如果桌子上有面条,厨师则等待
if(Desk.isHaveFood) {
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {//如果桌子上没有面条,则开始做面条
System.out.println("厨师开始做面条");
//更改桌子上食物状态
Desk.isHaveFood = true;
//唤醒
Desk.lock.notify();
}
}
}
}
}
}
package com.zhxd.test;
//消费者 吃货
public class Foodie extends Thread {
Foodie(String name) {
super(name);
}
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
//判断线程是否是结束状态
if (Desk.noodlesNumber == 0) {
break;
} else {
if (!Desk.isHaveFood) {
try {
//如果桌子没食物,则吃货进行等待
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {//如果桌子上有食物,吃货开始
Desk.noodlesNumber--;
Desk.isHaveFood = false;
System.out.println("我开始吃面条了,我还可以吃" + Desk.noodlesNumber + "碗");
//吃完后唤醒厨师
Desk.lock.notify();
}
}
}
}
}
}
package com.zhxd.test;
public class Test {
public static void main(String[] args) {
//创建消费者线程
Foodie foodie = new Foodie("吃货");
//创建生产者线程
Cooker cook = new Cooker("厨师");
//启动生产者和消费者的线程
foodie.start();
cook.start();
}
}
8.阻塞队列实现等待唤醒机制
- 阻塞队列继承结构
- 常见的BlockingQueue(阻塞队列):
- ArrayBlockingQueue: 底层是数组,有界
- LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int的最大值
- BlockingQueue的核心方法:
- put(anObject): 将参数放入队列,如果放不进去会阻塞(自带锁)
- take(): 取出第一个数据,取不到会阻塞(自带锁)
案例需求:
- 生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务
- 构造方法中接收一个阻塞队列对象
- 在run方法中循环向阻塞队列中添加面条
- 打印添加结果
- 消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务
- 构造方法中接收一个阻塞队列对象
- 在run方法中循环获取阻塞队列中的面条
- .打印获取结果
- 测试类(Demo):里面有main方法,main方法中的代码步骤如下
- 创建阻塞队列对象
- 创建生产者线程和消费者线程对象,构造方法中传入阻塞队列对象
- 分别开启两个线程
package com.zhxd.test;
import java.util.concurrent.ArrayBlockingQueue;
//厨师 生产者
public class Cooker implements Runnable {
private ArrayBlockingQueue arrayBlockingQueue;
Cooker(ArrayBlockingQueue arrayBlockingQueue) {
this.arrayBlockingQueue = arrayBlockingQueue;
}
@Override
public void run() {
while (true) {
try {
arrayBlockingQueue.put("面条");
System.out.println("厨师正在面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.zhxd.test;
import java.util.concurrent.ArrayBlockingQueue;
//消费者 吃货
public class Foodie extends Thread {
private ArrayBlockingQueue arrayBlockingQueue;
Foodie(ArrayBlockingQueue arrayBlockingQueue) {
this.arrayBlockingQueue = arrayBlockingQueue;
}
@Override
public void run() {
while(true) {
try {
arrayBlockingQueue.take();
System.out.println("吃货吃了一碗面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.zhxd.test;
import java.util.concurrent.ArrayBlockingQueue;
public class Test {
public static void main(String[] args) {
//创建阻塞队列
//细节:生产者与消费者要使用相同的阻塞队列
ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<String>(10);
//创建消费者线程
Foodie foodie = new Foodie(arrayBlockingQueue);
Thread t1 = new Thread(foodie, "吃货");
//创建生产者线程
Cooker cooker = new Cooker(arrayBlockingQueue);
Thread t2 = new Thread(cooker, "吃货");
//启动生产者和消费者的线程
t1.start();
t2.start();
}
}
9.多线程的六种状态
多线程的生命周期严格来说不包括运行
,因为Java虚拟机把执行交出去后,就是操作系统在运行了
- 六种状态
10.多线程综合练习
10.1抢红包案例
- 模拟群聊抢红包
- 假设:100块钱,分成3个红包,5个人抢
- 其中红包金额是共享数据
- 5个人是5个线程
- 打印结果如下:
- XXX抢到了XXX元
- XXX抢到了XXX元
- XXX抢到了XXX元
- 抱歉,XXX没抢到
- 抱歉,XXX没抢到
package com.zhxd.test;
import java.util.Random;
public class RedPacket implements Runnable {
//红包数量
private int count = 3;
//红包金额
private double money = 100;
//最小红包金额
private final double MIN = 0.01;
@Override
public void run() {
synchronized (RedPacket.class) {
//判断是否到结束(红包抢光)
if(count == 0) {
//抢光
System.out.println("抱歉" + Thread.currentThread().getName() + ",红包抢光了");
}else {//没有抢光红包
//需要一个0-100的随机数,并且红包金额要为后续的红包预留最小金额(0.01)
//100 - (3-1) * 2
Random random = new Random();
double bouns = money - (count - 1) * MIN;
double redpacket = random.nextDouble() * bouns;
//如果抽中的金额比MIN小,则需要把金额编程最小值
if(redpacket < MIN) {
redpacket = 0.01;
}else {//如果比MIN大
//是否是第三个人抢红包,如果是则剩下的金额都给第三个人
if(count == 1) {
redpacket = money;
}
//计算剩余金额
money = money - redpacket;
//红包数量-1
count--;
System.out.println(Thread.currentThread().getName() + "抢到" + redpacket + "元");
}
}
}
}
}
package com.zhxd.test;
public class Test {
public static void main(String[] args) {
/**
* 模拟群聊抢红包
* 假设:100块钱,分成3个红包,5个人抢
* 其中红包金额是共享数据
* 5个人是5个线程
* 打印结果如下:
* XXX抢到了XXX元
* XXX抢到了XXX元
* XXX抢到了XXX元
* 抱歉,XXX没抢到
* 抱歉,XXX没抢到
* */
//创建五个线程
RedPacket redPacket = new RedPacket();
Thread t1 = new Thread(redPacket);
Thread t2 = new Thread(redPacket);
Thread t3 = new Thread(redPacket);
Thread t4 = new Thread(redPacket);
Thread t5 = new Thread(redPacket);
//分别命名5个线程
t1.setName("小红");
t2.setName("小明");
t3.setName("小刚");
t4.setName("小军");
t5.setName("小玲");
//启动五个线程
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
10.2抽奖箱案例
-
需求:
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为 {10,5,20,50,100,200,500,800,2,80,300,700};创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
抽奖箱1 又产生了一个 100 元大奖 抽奖箱1 又产生了一个 200 元大奖 抽奖箱1 又产生了一个 800 元大奖 抽奖箱2 又产生了一个 700 元大奖
package com.zhxd.test;
/*
* author zhxd
* ClassName MyThread
*/
import java.util.ArrayList;
import java.util.Collections;
public class MyThread extends Thread {
ArrayList<Integer> list;
MyThread(String name, ArrayList<Integer> list) {
super(name);
this.list = list;//传入同一个ArrayList对象达到线程数据共享
}
@Override
public void run() {
//循环
//同步代码块
//判断
//判断
while (true) {
synchronized (MyThread.class) {
if(list.size() == 0) {//如果奖箱为空
break;
}else {
Collections.shuffle(list);//打乱奖箱的奖项
int prize = list.remove(0);//抽出一个
System.out.println(getName() + "又产生了一个" + prize + "元大奖");
}
}
try {
Thread.sleep(10);//线程睡眠,让其他线程有抢占CPU的机会
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.zhxd.test;
/*
* author zhxd
* ClassName Test
*/
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) {
//创建奖池
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);
//创建奖箱并命名
MyThread t1 = new MyThread("抽奖箱1", list);
MyThread t2 = new MyThread("抽奖箱2", list);
//启动线程
t1.start();
t2.start();
}
}
- 任务升级
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为 {10,5,20,50,100,200,500,800,2,80,300,700};
创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
每次抽的过程中,不打印,抽完时一次性打印(随机) 在此次抽奖过程中,抽奖箱1总共产生了6个奖项。
分别为:10,20,100,500,2,300最高奖项为300元,总计额为932元
在此次抽奖过程中,抽奖箱2总共产生了6个奖项。
分别为:5,50,200,800,80,700最高奖项为800元,总计额为1835元
在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元
package com.zhxd.test;
/*
* author zhxd
* ClassName MyCallable
*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.Callable;
public class MyCallable implements Callable {
ArrayList<Integer> list;
public MyCallable(ArrayList<Integer> list) {
this.list = list;
}
@Override
public Integer call() throws Exception {
ArrayList<Integer> boxList = new ArrayList<>();
//循环
//同步代码块
//判断
//判断
while (true) {
synchronized (MyCallable.class) {
if (list.size() == 0) {//如果奖箱为空
break;
} else {
Collections.shuffle(list);//打乱奖箱的奖项
int prize = list.remove(0);//抽出一个
//判断是哪个线程抽取的奖项,并添加至对应线程的奖箱中
boxList.add(prize);
}
}
Thread.sleep(10);//线程睡眠,让其他线程有抢占CPU的机会
}
if(boxList.size() == 0) {
return null;
}else {
return Collections.max(boxList);
}
}
}
package com.zhxd.test;
/*
* author zhxd
* ClassName Test
*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建奖池
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);
//创建奖箱并命名
MyCallable myCallable = new MyCallable(list);
//创建多线程运行结果的管理者对象
FutureTask<Integer> ft1 = new FutureTask<>(myCallable);
FutureTask<Integer> ft2 = new FutureTask<>(myCallable);
//创建抽奖的2条线程
Thread t1 = new Thread(ft1);
Thread t2 = new Thread(ft2);
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
//启动线程
t1.start();
t2.start();
//获取线程返回的最大值
Integer max1 = ft1.get();
Integer max2 = ft2.get();
if (max1 == null) {
System.out.println(" 在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为" + max2 + "元");
} else if (max2 == null) {
System.out.println(" 在此次抽奖过程中,抽奖箱1中产生了最大奖项,该奖项金额为" + max1 + "元");
} else if (max1 > max2) {
System.out.println(" 在此次抽奖过程中,抽奖箱1中产生了最大奖项,该奖项金额为" + max1 + "元");
}else if (max1 < max2) {
System.out.println(" 在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为" + max2 + "元");
}else {
System.out.println("两者的最大奖项是一样的");
}
}
}
11.线程池
提到池,大家应该能想到的就是水池。水池就是一个容器,在该容器中存储了很多的水。那么什么是线程池呢?线程池也是可以看做成一个池子,在该池子中存储很多个线程。
- 线程池的主要核心原理
- 线程池实现步骤
JDK对线程池也进行了相关的实现,在真实企业开发中我们也很少去自定义线程池,而是使用JDK中自带的线程池。我们可以使用Executors中所提供的静态方法来创建线程池
Executors静态方法
static ExecutorService newCachedThreadPool()
创建一个默认的线程池static newFixedThreadPool(int nThreads)
创建一个指定最多线程数量的线程池
ExecutorService对象方法:
submit([Runable|Callabel])
getPoolSize()
:获取线程池中线程的数量
package com.zhxd.test;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
//返回值表示线程运行完毕之后的结果
return "完成";
}
}
package com.zhxd.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) throws InterruptedException {
//创建一个默认的线程池对象.池子中默认是空的.默认最多可以容纳int类型的最大值
ExecutorService pool = Executors.newCachedThreadPool();
//Executors --- 可以帮助我们创建线程池对象
//ExecutorService --- 可以帮助我们控制线程池
//提交任务至线程池
pool.submit(new MyCallable());
Thread.sleep(1000);
pool.submit(new MyCallable());
Thread.sleep(1000);
pool.submit(new MyCallable());
Thread.sleep(1000);
pool.submit(new MyCallable());
Thread.sleep(1000);
pool.submit(new MyCallable());
Thread.sleep(1000);
pool.submit(new MyCallable());
//销毁线程池
pool.shutdown();
}
}
11.2线程池-参数详解
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize: 核心线程的最大值,不能小于0
maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime: 空闲线程最大存活时间,不能小于0
unit: 时间单位
workQueue: 任务队列,不能为null
threadFactory: 创建线程工厂,不能为null
handler: 任务的拒绝策略,不能为null
** 线程池-非默认任务拒绝策略**
RejectedExecutionHandler是jdk提供的一个任务拒绝策略接口,它下面存在4个子类:
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy: 抛弃队列中等待最久的任务(阻塞队列的第一个任务) 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy: 调用任务的run()方法绕过线程池直接执行。
自定义线程池:饭店的故事
什么时候分配临时线程?
当核心线程和阻塞队列已经容纳不下任务的数量
时则创建临时线程,如果任务数量超过核心线程、临时线程以及阻塞队列长度之和时则会选择拒绝服务