一文了解java线程
线程
概述
进程是程序的基本执行实体
线程是操作系统能够运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。也是软件中互相独立,也可以同时运行的功能
应用场景
软件的耗时操作,拷贝,迁移大文件,加载大量的资源文件(背景动图,音乐,加载栏),后台服务器
只要你想让多个事情同时运行就需要多线程
作用
- 有了多线程,我们可以让程序同时做多件事情
- 提高执行效率,比如正在拷贝文件,不能干其他事,这时就可以加入多线程,这样就可以边拷贝边执行其他任务
并发与并行
并发:在同一时间,多个指令在单个cpu上交替运行
并行: 在同一时间,多个指令在多个cpu上同时运行
多线程的实现方式
- 继承Thread类的方式进行实现
- 实现Runnable接口的方式进行实现
- 利用Callable接口和Future接口实现
1. 继承Thread类
2. 实现Runnable接口
当前执行的线程对象: 可以通过currentThread()来获取
3. 利用Callable与Future接口
实现线程3种方式的区别
- 第一种线程的方式比较简单,直接定义一个类继承
Thread类
,重写run()
方法里面放任务,再测试类里创建该类的对象,调用方法start()
即可 ,缺点是Java只能单继承,所以第一种就不能再继承类了- 第二种是将创建的类实现
Runable接口
,在重写的run()
方法里放线程任务,在测试类里创建任务类对象,将该对象传入新创建的线程类里,线程类再调用其start()方法
,这个可以继承类- 第三种很复杂,但可以获得返回值,首先创建一个类实现
Callable接口
,对返回的值进行泛型限制,里面重写call()方法
,里面放着线程任务,在测试类里创建任务对象,再创建FuterTasker类的对象,将任务对象传入 ,再创建Thread类对象,将FuterTasker对象传入,再调用start()方法
开始执行线程,最后再获取返回值通过FuterTasker类的对象.get()方法
构造器与方法
这里
Runable参数
是任务对象
- 默认线程优先级是5,最小是1,最大是10,优先级越大,抢占cpu越高
- 守护线程也被称为备胎线程
1. 方法setname与构造器
如果没有给线程设置名字,那么线程会有个初始名Thread-X(0->无线) ,当然也可以让继承类创建有参构造器
这里继承关系,不能继承父类的构造器,所以得创建本类的有参构造器
2. 方法currentThread
- 谁调用了这个
getName方法
,就是谁
- 当然main是一个线程,一般都是
main
运行代码- 当JVM虚拟机启动后,会自动的启动多条线程,其中一条就是main线程,他的作用就是调用main方法,执行里面的代码
// 2. 测试currentThread方法
Thread m = Thread.currentThread();
String name = m.getName();
System.out.println(name); //main
3. sleep
- 哪条线程执行到了这个方法,哪条线程就会在这里停留对应的时间
- 方法的参数:
- 表示睡眠的时间,单位毫秒 1s=1000毫秒
这里
System.out.println("你好");
的main线程
不一定最后运行 ,但可强制睡眠来改变顺序,就算释放了线程资源,这还得看程序执行的速度
4 . 线程的调度等级
4.1线程的调度
分为 抢占式调度 和 非抢占式调度
抢占式调度: 多个线程抢夺系统cpu的执行权,cpu在什么时候执行哪个线程也是不确定的,执行时间也是不确定的,所以充满随机性
非抢占式调度: 多个线程轮流执行,每个执行的时间都差不多
4.2线程的等级
优先级越大,抢占的cpu的执行权就越强
这里通过Ctrl+Alt+SHift+12,查看到Thread的源码里的常量最大优先级为10,最小为0,默认为5
线程的执行顺序不是绝对受线程优先级影响
5. 守护线程Daemon
守护线程也叫备胎线程,当其他非守护线程执行完毕后,守护线程会陆续结束(可能任务执行完,可能没执行完),一般用在比如杀毒软件正在杀毒,用户结束了这个软件,杀毒进程被迫停止 ,而杀毒线程就是守护线程,软件就是正常线程;还比如打王者的过程中水晶破碎,其他英雄的动作进程在那一刻也会加载几秒,结束进程
6. 出让线程yield
出让线程,也被礼貌线程,尽可能平均调用CPU
7. 插让线程
也被称为插入线程,插队线程,设置后将当前线程插入到所在位置的线程之前运行结束
package com.itheima.pack4All_Method.p4;
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread("汽车");
//执行线程
t1.start();
//将线程t1插入main线程之前
t1.join();
//这里是main线程
for (int i = 0; i < 10; i++) {
System.out.println("@main线程: " + i);
}
}
}
package com.itheima.pack4All_Method.p4;
public class MyThread extends Thread {
/*这里构造方法不能被继承,所以MyThread类没有MyThread(name)这个构造器,
所以需要创建这个构造器
*/
public MyThread(String name) {
super(name);
}
//线程任务是从输出1-100
@Override
public void run() {
for (int i = 0; i <10; i++) {
System.out.println( "@"+getName()+": "+i);
}
}
}
注意
1. 子类抛出异常
父类Thread类没有抛出异常,所以子类不能在run方法内抛出异常,所以在里面的出现的所有异常,只能捕获异常
线程的生命周期
人生的各种状态
线程的状态
- 创建线程对象,执行start方法,这样线程就有了执行资格,变为就绪状态,开始和其他线程抢cpu执行权,如果抢到了,则执行对应的任务,完毕后线程死亡,一直这样重复,直到线程任务完全结束。 如果中途没抢到,就为等待状态
- 如果有执行权,有执行资格,当遇到睡眠方法sleep( ),那么就会立马停止运行,会先去就绪状态等待抢夺资源,继续操作剩下的代码
线程代码模板
线程的安全问题
这里卖票会有异常,比如:线程1先进入睡眠了一会,线程2也进来睡了一会,线程3也睡了一会,这时线程1醒来开始执行
ticket++
刚结束 ,这时线程2也醒了, 抢占了cpu的执行权,也 开始票数自加ticket++
了,线程2继续执行在卖 第2张票,这时线程1又抢回了cpu的执行权,也开始卖第2张票,这时线程3醒了后也抢到了cpu的执行权,这时开始自加ticket++
,刚完事,这时线程1睡眠休息醒来了,又抢了cpu的执行权,这时执行了ticket++
,开始卖第4张票,这时线程3抢回了cpu的执行权,开始卖第5张票..................................
而对于getName()
的调用者和后面字段 正在卖第"+(i+1)+"张票"拼接一定是不匹配的,毕竟cpu的执行权随时有可能被其他线程抢走
package com.itheima.pack4;
public class MyThread extends Thread {
// Thread的构造器不能被继承,所以自己设置个,好传线程名
public MyThread(String name) {
super(name);
}
/*这里卖票会有异常,getName()的调用者和后面字段正在卖第"+(i+1)+"张票"不匹配,
毕竟线程执行的顺序是随机性的 */ //int count = 0; //定义票数 这里这样定义会出错,相当于每个对象都有一份票变量
static int count = 0; //随MyThread类创建,加载一次count,全局只有一份
@Override
public void run() {
while (true) {
//让卖票效果更真实,这里停顿100ms
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count<100) {
count++;
System.out.println(getName() + "正在卖第" + count + "张票");
}else {
break;
}
}
}
}
package com.itheima.pack4;
//测试线程安全的问题
public class Test {
public static void main(String[] args) throws InterruptedException {
// 1. 创建3个售票员小张,小芳,小刚
MyThread t1 = new MyThread("小张");
MyThread t2 = new MyThread("小芳");
MyThread t3 = new MyThread("小刚");
// 2. 执行这些线程看结果
t1.start();
t2.start();
t3.start();
}
}
1. 线程的上锁(同步代码块)
将执行的代码进行上锁,就算其他线程抢到了cpu控制权,也不能进入厕所,只有等里面线程上完厕所,才能把门打开,后面等待的线程才能上厕所
格式与特点
锁对象可以是任意任何类型 ,但得保证锁对象是唯一的, 可以定义对象时前面加个 static 静态修饰(一般都是写 本类的字节码文件对象MyThread.class) ,为什么要这样呢,你可以这样想若锁对象有多个,那么每个线程在查看不同的锁状态上厕所,反而引发异常,当上锁对象1其默认是开锁的,假如线程1进入上厕所,那么该锁对象1为关闭状态,而锁对象2是开启状态,这时线程2也会上厕所,这就引发数据异常。
这里需要注意每次上厕所上锁(同步代码块)在外排队的需要
sleep睡一会
,不然就是一个人一直卖票
package com.itheima.pack4;
public class MyThread extends Thread {
// Thread的构造器不能被继承,所以自己设置个,好传线程名
public MyThread(String name) {
super(name);
}
/*这里卖票会有异常,getName()的调用者和后面字段正在卖第"+(i+1)+"张票"不匹配,
毕竟线程执行的顺序是随机性的 */ //int count = 0; //定义票数 这里这样定义会出错,相当于每个对象都有一份票变量
//随MyThread类创建,加载一次count,全局只有一份
static int count = 0;
// 定义唯一的锁对象
//static Object o = new Object();
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (MyThread.class) {
//让卖票效果更真实,这里停顿100ms
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count<100) {
count++;
System.out.println(getName() + "正在卖第" + count + "张票");
}else {
break;
}
}
System.out.println("hello,world");
}
}
}
package com.itheima.pack4;
//测试线程安全的问题
public class Test {
public static void main(String[] args) throws InterruptedException {
// 1. 创建3个售票员小张,小芳,小刚
MyThread t1 = new MyThread("小张");
MyThread t2 = new MyThread("小芳");
MyThread t3 = new MyThread("小刚");
// 2. 执行这些线程看结果
t1.start();
t2.start();
t3.start();
}
}
2. 同步方法
将 同步代码块进行抽取成同步方法,这里共享变量不用设置静态,毕竟只会创建一次myRunable对象
这里的同步块方法是非静态,所以该锁对象是this ,全程指的是MyRunable对象
package com.itheima.同步方法;
import com.itheima.pack4.MyThread;
//这里使用线程第二个方式
public class MyRunable implements Runnable{
//1.卖票,这里票数必须是唯一的
int count = 0;
@Override
public void run() {
// 循环卖票
while (true){
while (true) {
if (method()) break;
}
}
}
//设置同步方法
private synchronized boolean method() {
if (count == 100){
return true;
}
count++;
System.out.println(Thread.currentThread().getName()+"正在卖第"+count+"张票");
return false; }
}
package com.itheima.同步方法;
public class Test {
public static void main(String[] args) {
//创建任务对象
MyRunable myRunable = new MyRunable();
//创建线程对象
Thread t1 = new Thread(myRunable);
Thread t2 = new Thread(myRunable);
Thread t3 = new Thread(myRunable);
t1.setName("小张");
t2.setName("小王");
t3.setName("小李");
t1.start();
t2.start();
t3.start();
}
}
线程安全StringBuilder与StringBuffer
StringBuilder的源码比StringBuffer一样,但StringBuffer类的方法前面都声明了synchronized,但StringBuilder没StringBuffer线程安全
3. Lock锁
- 这里lock锁,需注意如果是直接继承了Thread的方式,在测试类创建了了多个线程,那么创建锁对象得保证唯一性,前面用static修饰,
static Lock lock = new ReentrantLock()
关于共用的数据在多线程面前也得保证其唯一性,设置静态static int count = 0;
- 上锁就得关锁,不然其他线程一直会在门口等待,陷入死循环
- 当然可以不用写两遍关闭lock锁 ,直接
try catch()finally(){这里关锁 lock.unlock(); }
finally区域,代码一定会执行到
这个方式不建议
package com.itheima.lock锁;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread {
static int count = 0;//因为测试类创建了两个对象,那么count为了保证只有一份,设置为静态
//创建锁对象,这里测试类创建了多个个线程类,所以锁对象不唯一了,加上static
static Lock lock = new ReentrantLock();
//这里使用Lock锁,来保护线程数据问题
@Override
public void run() {
//卖票
while (true) {
//上锁
lock.lock();
/*这里的if在线程1=100时进入后先上锁后判断是否是100,这时会跳出while循环,而线程2,线程3还在第18行继续等待,
所以得在退出的时候解开锁 ,不然死循环*/
if (count == 100) {
lock.unlock();
break; } else {
count++;
System.out.println(getName() + "正在卖第" +count+ "张票");
}
//解锁
lock.unlock();
}
}
}
这种方式更好
package com.itheima.lock锁;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread {
static int count = 0;//因为测试类创建了两个对象,那么count为了保证只有一份,设置为静态
//创建锁对象,这里测试类创建了多个个线程类,所以锁对象不唯一了,加上static
static Lock lock = new ReentrantLock();
//这里使用Lock锁,来保护线程数据问题
@Override
public void run() {
//卖票
while (true) {
//上锁
lock.lock();
try {
/*这里的if在线程1=100时进入后先上锁后判断是否是100,这时会跳出while循环,而线程2,线程3还在第18行继续等待,
所以得在退出的时候解开锁 ,不然死循环*/
if (count == 100) {
break;
} else {
count++;
System.out.println(getName() + "正在卖第" +count+ "张票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
lock.unlock();
}
}
}
}
package com.itheima.lock锁;
public class Test {
public static void main(String[] args) {
// 创建多个线程
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("小张");
t2.setName("小李");
t3.setName("小赵");
t1.start();
t2.start();
t3.start();
}
}
4. 死锁
当出现锁的嵌套,就可能出现死锁 ,所以避免嵌套锁
这里 线程1假如先抢到cpu执行权,那么会先将A锁拿到,进行锁起,这时线程2抢到cpu执行权,他开始拿B锁,进行锁起;
这时候线程1会卡死在15行,因为线程1拿不到b锁,等着线程2释放,但线程2等着线程1释放A锁,才能释放B锁,这时候就死锁了
生产者和消费者(等待唤醒机制)
多个线程执行,会发生随机性,如果碰到等待唤醒机制,会让多个线程轮流执行
理想状态
生产者查看桌子没有面条,就立马做一碗面条,吃货看到桌子上有面条就去吃,吃完后就等着厨师做,厨师又看到桌子上没食物就去做,吃货看到有就去吃........................
生产者和消费者等待
消费者等待:吃货先抢到cpu的执行权,但这时桌子没有食物,则进入等待状态(wait) ,这时厨师就抢到cpu的执行权,看到桌子上没有食物,则开始做食物,等食物上桌子就唤醒( notify)吃货,吃货这时就开吃,吃完就唤醒厨师做饭
生产者等待: 生产者做好面条后,放在桌子上,看到桌子仍然有食物,就会进入等待状态,这时cpu执行权就会被吃货抢走,这时吃货看到桌子有食物就会开吃,然后又发现桌子没食物又开始进入等待状态,这时cpu的执行权就会被厨师抢走,他发现桌子没食物,就开始做食物。然后唤醒吃货开吃
线程方法
这里
notify
随机唤醒单个线程,一般使用notifyAll
唤醒所有线程
吃饭例子
- 定义吃货只能吃十次,每吃一次,将桌子饭的状态置为0,让吃货次数-1次, 让吃货等待,让厨师上岗,唤醒吃货;
- 厨师看桌子上没有饭则开始做饭,将桌子饭的状态置为1,让厨师等待,让吃货开吃,唤醒厨师开做
package com.itheima.等待唤醒实现;
//食物类
public class Foodie extends Thread {
public Foodie(String name) {
super(name);
}
//创建吃货名字构造器
@Override
public void run() {
// 1.循环
// 2.同步代码块
// 3. 判断共享数据是否到达末尾,到达末尾怎么办
// 4. 判断共享数据是否到达末尾,没到达末尾怎么办(执行核心代码)
while (true) {
//这里同步锁,锁多个食客同时吃
synchronized (Desk.lock) {
//当10个食物都被吃完了就退出
if (Desk.count == 0) {
break;
} else {
//表示桌子上没食物
if (Desk.foodFlag == 0) {
// 没有食物吃货就等待,这时cpu执行权会被厨师抢走
try {
Desk.lock.wait();//让当前线程跟锁进行绑定
} catch (InterruptedException e) {
e.printStackTrace();
}
Desk.lock.notifyAll();//唤醒所有该锁的所有线程,这里唤醒的是吃货,让他去抢cpu执行权去吃饭
//如果食物还有就吃
} else {
Desk.count--;//食物-1
System.out.println(getName() + "在吃食物,还能吃" + Desk.count + "个食物");
// 这里只要吃货吃了一碗,厨师就开始做
Desk.lock.notifyAll(); //唤醒厨师 ,在上面代码说明厨师进入等待了,当吃货吃完就唤醒厨师做饭
Desk.foodFlag = 0;//吃完食物,将食物状态变成0
}
}
}
}
}
}
package com.itheima.等待唤醒实现;
//厨师类
public class Cook extends Thread {
public Cook(String name) {
super(name);
}
@Override
public void run() {
//1. 循环
//2. 锁
//3. 共享数据到了末尾怎么办
//4. 共享数据没到末尾怎么办 核心代码
while (true) {
//如果食物已经有十份就不做了
//这里上锁,如果碰到多个厨师,访问共享数据
synchronized (Desk.lock) {
//表示吃货10碗饭已经吃完了
if (Desk.count== 0) {
break;
// 吃货还能吃
} else {
//如果桌子上有食物,厨师就等待
if ( Desk.foodFlag == 1) {
//将锁绑定这个等待厨师线程 ,这时吃货抢到cpu执行权开吃
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//这里让出cpu执行权给吃货
// 上面代码表示吃货已经吃了一碗, 唤醒厨师
Desk.lock.notifyAll();
//桌子上没食物
} else {
// 做食物,没做好食物,不唤醒吃货
System.out.println(getName() + "做好了食物");
// 唤醒吃货 上面代码表示饭已经做好了,这时吃货还在等待唤醒。
//将桌子状态为1
Desk.foodFlag = 1;
//唤醒吃货开吃
Desk.lock.notifyAll();
}
}
}
}
}
}
package com.itheima.等待唤醒实现;
//桌子
public class Desk {
// 1. 定义桌子上是否有面条 ,这里不用boolean,若多个线程就不行了
public static int foodFlag = 0; //0表示没有面条,1表示有面条
// 2. 限制一个吃货只能吃10次食物
public static int count = 10;
//3. 定义一个锁对象
public static Object lock = new Object();
}
package com.itheima.等待唤醒实现;
public class Test {
public static void main(String[] args) {
Cook c = new Cook("魏大厨");
Foodie f = new Foodie("小方");
c.start();
f.start();
}
}
堵塞队列
每一个饭属于队列的一个元素
取不到数据/数据超出队列的数量时都会阻塞
take取数据,put放数据
堵塞队列的继承结构
阻塞队列实现了以下接口iterable,Collection,Queue,BlockingQueue
,而其实现类 ArrayBlockingQueue
,LinkedBlockQueue
- iterable表示阻塞队列可以通过迭代器/增强for来遍历的
- Collection表示阻塞队列是一个单列集合
- Queue 表示队列
- BlockingQueue 表示阻塞队列
底层
- 会将put数据里面先创建一个锁,然后获取锁, 循环判断队列的长度与目前存在的队列个数是否相等,相等则进入等待状态,try-catch-finally里面放入 开锁代码,无论怎么样,都会执行到finally区域
结果出现连续的输出语句,是因为输出语句没有放在锁内,放在锁外就会出现这种情况 , 这是因为没锁保护的代码会被其他线程执行,而被锁的代码,线程只能等待唤醒执行 ,这样就出现了阻塞队列打印两次的结果
package com.itheima.阻塞队列;
import java.util.concurrent.ArrayBlockingQueue;
//创建继承线程Thread类
public class Cook extends Thread {
//创建有参阻塞队列的构造器
ArrayBlockingQueue<String> queue; //这里定义的变量不需要静态,因为测试类传参始终是那个类
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
// 重写任务方法
@Override
public void run() {
//这里循环放面
while (true){
try {
queue.put("日式拉面");
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.itheima.阻塞队列;
import java.util.concurrent.ArrayBlockingQueue;
//继承线程Thread类
public class Fooding extends Thread {
//创建有参阻塞队列的构造器
ArrayBlockingQueue<String> queue; //这里定义的变量不需要静态,因为测试类传参始终是那个类
public Fooding(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
// 重写任务方法
@Override
public void run() {
while (true) {
try {
//打印拿到的数据
String food = queue.take();
System.out.println("吃了一碗"+food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.itheima.阻塞队列;
import java.util.concurrent.ArrayBlockingQueue;
public class Test {
public static void main(String[] args) {
// 1. 创建一个阻塞队列对象实现等待唤醒机制 ,这里泛型限制阻塞队列集合的数据,里面队列的个数为1
/* 因为阻塞队列相当于厨师和吃货在放餐和取餐时,是相相对应的,所以必须保证阻塞队列对象唯一性,
所以可以通过构造器传参将这个对象传入 */ ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(1);
Cook c= new Cook(queue);
Fooding f = new Fooding(queue);
c.start();
f.start();
}
}
线程的七大状态
新建-> 就绪(start)-> 运行(抢到了cpu的执行权) -> 死亡(线程执行结束) -> 计时等待( sleep( )的线程) -> 等待(wait()) -> 阻塞( 没获得锁)
Thread.sleep()
会让当前线程暂时放弃CPU执行权,从而让其他线程有机会执行。wait()
会让当前线程等待(沉睡),一直放弃争抢CPU执行权,只能被唤醒后才会去争抢cpu的执行权- 没获得锁,就没办法执行锁里面代码,只能等锁释放
通过Thread.state
可以查看这个线程的各个状态
Java的Api为什么没有运行状态,那是因为 当线程抢到cpu执行权的时候,他会把当前的线程交给操作系统去管理,JVM虚拟机就不管了
例题
练习1
第一种实现
package com.itheima.练习.练习4.第三种实现;
import java.util.Random;
//定义一个线程实现类
public class RobRedTest extends Thread {
static double money = 100;
static int count = 3;// 抢红包的次数
static final double MIN = 0.01; //最小红包金额
/* public RobRedTest(String name) {
super(name); }*/
@Override
public void run() {
synchronized (RobRedTest.class) {
if (count == 0) { // 判断抽完时,共享数据是否到了末尾
System.out.println(getName() + "没有抢到红包");
} else {
//中奖的金额,重置
double prize = 0;
if (count == 1) {//如果最后一个人抽到
prize = money;
} else {
Random r = new Random();
//第一次抽红包的最大金额,99.98,第二次: money-第一次随机的金额
double bounds = money - (count - 1) * MIN;
prize = r.nextDouble(bounds);//0.1~99.98
//若第一次抽红包随机为0.01以下,则红包变为0.01
if (prize<MIN){
prize=MIN;
}
}
//迭代红包金额和红包的个数
money-=prize;
count--;
System.out.println(getName()+"抢到了"+prize+"元");
}
}
}
}
package com.itheima.练习.练习4.第四种实现;
public class Test {
public static void main(String[] args) {
/* RobRedTest t1 = new RobRedTest("小丽");
RobRedTest t2 = new RobRedTest("小飞");
RobRedTest t3 = new RobRedTest("小张");
RobRedTest t4 = new RobRedTest("小赵");
RobRedTest t5 = new RobRedTest("小磊");*/
RobRedTest t1 = new RobRedTest();
RobRedTest t2 = new RobRedTest();
RobRedTest t3 = new RobRedTest();
RobRedTest t4 = new RobRedTest();
RobRedTest t5 = new RobRedTest();
t1.setName("小丽");
t2.setName("小飞");
t3.setName("小灶");
t4.setName("蛋蛋");
t5.setName("笑笑");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
第二种实现
这里使用了BigDecimal,加减乘除运算都是方法,
double->BigDecimalBigDecimal.valueof()
BigDecimal->doubledouble.doubleValue()
package com.itheima.练习.练习4.第四种实现;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Random;
//改成规范的BigDecimal格式
//定义一个线程实现类
public class RobRedTest extends Thread {
static BigDecimal money = BigDecimal.valueOf(100.0);
static int count = 3;// 抢红包的次数
static final BigDecimal MIN = BigDecimal.valueOf(0.01); //最小红包金额
/* public RobRedTest(String name) {
super(name); } */ @Override
public void run() {
synchronized (RobRedTest.class) {
if (count == 0) { // 判断抽完时,共享数据是否到了末尾
System.out.println(getName() + "没有抢到红包");
} else {
//中奖的金额,重置
BigDecimal prize;
if (count == 1) {//如果最后一个人抽到
prize = money;
} else {
Random r = new Random();
//第一次抽红包的最大金额,99.98,第二次: money-第一次随机的金额
double bounds = money.subtract(BigDecimal.valueOf(count - 1).multiply(MIN)).doubleValue();
prize = BigDecimal.valueOf(r.nextDouble(bounds));//0.1~99.98
}
//设置抽中红包,保留两位,四舍五入
prize=prize.setScale(2, RoundingMode.HALF_UP);
//迭代红包金额和红包的个数
money = money.subtract(prize);
count--;
System.out.println(getName() + "抢到了" + prize + "元");
}
}
}
}
package com.itheima.练习.练习4.第四种实现;
public class Test {
public static void main(String[] args) {
/* RobRedTest t1 = new RobRedTest("小丽");
RobRedTest t2 = new RobRedTest("小飞");
RobRedTest t3 = new RobRedTest("小张");
RobRedTest t4 = new RobRedTest("小赵");
RobRedTest t5 = new RobRedTest("小磊");*/
RobRedTest t1 = new RobRedTest();
RobRedTest t2 = new RobRedTest();
RobRedTest t3 = new RobRedTest();
RobRedTest t4 = new RobRedTest();
RobRedTest t5 = new RobRedTest();
t1.setName("小丽");
t2.setName("小飞");
t3.setName("小灶");
t4.setName("蛋蛋");
t5.setName("笑笑");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
练习2
实现1
package com.itheima.练习.练习5.第二种实现;
import java.util.*;
import java.util.stream.Collectors;
public class PrizePools extends Thread {
ArrayList<Integer> pools;
public PrizePools(ArrayList<Integer> pools) {
this.pools = pools;
}
/*
* 抽奖池,两个抽奖盒,每次随机抽奖
* */
@Override
public void run() {
//循环
while (true) {
//同步代码块
synchronized (PrizePools.class) {
// 共享数据到了末尾
if (pools.size() == 0) {
break;
// 共享数据没到末尾
} else {
//随机集合的数据
// pools.stream().collect(Collectors.toList());
Collections.shuffle(pools);
//返回集合随机后的第一个元素,并删除他
int pool = pools.remove(0);
System.out.println(getName() + "产生了一个" + pool + "奖项");
}
}
//线程走完睡一会
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.itheima.练习.练习5.第二种实现;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
public class Test {
public static void main(String[] args) throws InterruptedException {
//这里要是为了打乱获取集合的第一个元素,还是换成ArrayList集合比较好
ArrayList<Integer> pools = new ArrayList<>();
Collections.addAll(pools, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);
PrizePools p1 = new PrizePools(pools);
PrizePools p2 = new PrizePools(pools);
p1.setName("抽奖箱1");
p2.setName("抽奖箱2");
p1.start();
p2.start();
p2.join();//在main线程之前执行
System.out.println("抽奖已谢幕");
}
}
实现2
package com.itheima.练习.练习5.第一种实现;
import java.util.*;
public class PrizePools extends Thread {
Random random = new Random();
/*
* 抽奖池,两个抽奖盒,每次随机抽奖,抽到一个抽奖盒-1个这个奖项
* */ //共享数据是奖池[10,5,20,50,100,200,500,800,2,80,300,700]
static LinkedHashSet<Integer> pools;
static {
pools = new LinkedHashSet<Integer>();
Collections.addAll(pools, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);
}
@Override
public void run() {
while (true) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (PrizePools.class) {
int count = 0;
if (pools.isEmpty()) { //表示集合为空
break;//这里表示抽完就结束所有线程运行
} else {
//随机的索引
int index = random.nextInt(pools.size());//重置每次线程进来找索引取值
for (Integer money : pools) {
if (count == index) {
System.out.println(getName() + "又产生了一个" + money + "元大奖");
// 删除当前元素
pools.remove(money);
break; }
count++;
}
}
}
}
}
}
package com.itheima.练习.练习5.第一种实现;
public class Test {
public static void main(String[] args) throws InterruptedException {
PrizePools p1 = new PrizePools();
PrizePools p2 = new PrizePools();
p1.setName("抽奖箱1");
p2.setName("抽奖箱2");
p1.start();
p2.start();
p2.join();//在main线程之前执行
System.out.println("抽奖已谢幕");
}
}
练习3
实现1
package com.itheima.练习.练习6;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Optional;
public class Test {
public static void main(String[] args) throws InterruptedException {
PrizePools p1 = new PrizePools();
PrizePools p2 = new PrizePools();
p1.setName("抽奖箱1");
p2.setName("抽奖箱2");
p1.start();
p2.start();
p2.join();//在main线程之前执行
System.out.println("抽奖已谢幕");
ArrayList<Integer> box1 = PrizePools.box1;
ArrayList<Integer> box2 = PrizePools.box2;
//这里流的最大值只能是排序好的集合(这个说法是错误的stream的max里面已经排好序了)
Optional<Integer> box1Max = box1.stream().max(Integer::compare);// box1.stream().max((o1,o2)-> Integer.compare(o1, o2));
Optional<Integer> box1Min = box1.stream().min(Integer::compare);// box1.stream().min((o1,o2)-> Integer.compare(o1, o2));
Optional<Integer> box2Max = box2.stream().max(Integer::compare);// box1.stream().max((o1,o2)-> Integer.compare(o1, o2));
Optional<Integer> box2Min = box2.stream().min(Integer::compare);// box1.stream().min((o1,o2)-> Integer.compare(o1, o2));
System.out.println("抽奖箱1总共有"+box1.size()+"个奖项"+"分别为:"+box1+"最高奖项为"+box1Max.get()+"最低奖项为"+box1Min.get());
System.out.println("抽奖箱2总共有"+box2.size()+"个奖项"+"分别为:"+box2+"最高奖项为"+box2Max.get()+"最低奖项为"+box2Min.get());
}
}
package com.itheima.练习.练习6;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Random;
public class PrizePools extends Thread {
Random random = new Random();
/*
* 抽奖池,两个抽奖盒,每次随机抽奖,抽到一个抽奖盒-1个这个奖项
* 每次不打印,抽完一次性打印,统计各自的箱子最高/最低的奖项
* */ //共享数据是奖池[10,5,20,50,100,200,500,800,2,80,300,700]
static LinkedHashSet<Integer> pools;
static ArrayList<Integer> box1 = new ArrayList<>();
static ArrayList<Integer> box2 = new ArrayList<>();
static {
pools = new LinkedHashSet<Integer>();
Collections.addAll(pools, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);
}
@Override
public void run() {
while (true) {
/* try {
sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }*/ synchronized (PrizePools.class) {
int count = 0;
if (pools.isEmpty()) { //表示集合为空
break;//这里表示抽完就结束所有线程运行
} else {
//随机的索引
int index = random.nextInt(pools.size());//重置每次线程进来找索引取值
for (Integer money : pools) {
if (count == index) {
if (getName().equals("抽奖箱1")) {
box1.add(money);
// System.out.println(getName() + "又产生了一个" + money + "元大奖");
} else if(getName().equals("抽奖箱2")) {
box2.add(money);
// System.out.println(getName() + "又产生了一个" + money + "元大奖");
}
// 删除当前元素
pools.remove(money);
break; }
count++;
}
}
}
}
}
}
实现2
这个方式有个弊端,当添加很多线程时,需要在线程类里创建多个集合,做多个线程名匹配判断
package com.itheima.练习.练习6.第二种实现;
import java.util.ArrayList;
import java.util.Collections;
public class PrizePools extends Thread {
ArrayList<Integer> pools;
public PrizePools(ArrayList<Integer> pools) {
this.pools = pools;
}
/*
* 抽奖池,两个抽奖盒,每次随机抽奖
* */
//定义两个抽奖盒用于收集
static ArrayList<Integer> box1 = new ArrayList<>();
static ArrayList<Integer> box2 = new ArrayList<>();
@Override
public void run() {
//循环
while (true) {
//同步代码块
synchronized (PrizePools.class) {
// 共享数据到了末尾 打印每个奖箱
if (pools.size() == 0) {
//判断是否是线程1
if (getName().equals("抽奖箱1")){
System.out.println(getName()+box1);
}else{
System.out.println(getName()+box2);
}
break;
// 共享数据没到末尾
} else {
//随机集合的数据
// pools.stream().collect(Collectors.toList());
Collections.shuffle(pools);
//返回集合随机后的第一个元素,并删除他
int pool = pools.remove(0);
//判断是否是线程1
if (getName().equals("抽奖箱1")){
box1.add(pool);
}else{
box2.add(pool);
}
}
}
//线程走完睡一会
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.itheima.练习.练习6.第二种实现;
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) throws InterruptedException {
//这里要是为了打乱获取集合的第一个元素,还是换成ArrayList集合比较好
ArrayList<Integer> pools = new ArrayList<>();
Collections.addAll(pools, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);
PrizePools p1 = new PrizePools(pools);
PrizePools p2 = new PrizePools(pools);
p1.setName("抽奖箱1");
p2.setName("抽奖箱2");
p1.start();
p2.start();
p2.join();//在main线程之前执行
System.out.println("抽奖已谢幕");
}
}
实现3优化2
package com.itheima.练习.练习6.实现3;
import java.util.ArrayList;
import java.util.Collections;
public class PrizePools extends Thread {
ArrayList<Integer> pools;
public PrizePools(ArrayList<Integer> pools) {
this.pools = pools;
}
/*
* 抽奖池,两个抽奖盒,每次随机抽奖
* */
/* //定义两个抽奖盒用于收集
static ArrayList<Integer> box1 = new ArrayList<>(); static ArrayList<Integer> box2 = new ArrayList<>();*/
@Override
public void run() {
//每个线程都有自己的集合box,然后多个线程对同一个集合pools删减,直到集合为空,结束多个线程的循环
ArrayList<Integer> box = new ArrayList<>();
//循环
while (true) {
//同步代码块
synchronized (PrizePools.class) {
// 共享数据到了末尾 打印每个奖箱
if (pools.size() == 0) {
System.out.println(getName()+box);
break; // 共享数据没到末尾
} else {
//随机集合的数据
// pools.stream().collect(Collectors.toList());
Collections.shuffle(pools);
//返回集合随机后的第一个元素,并删除他
int pool = pools.remove(0);
//判断是否是线程1
box.add(pool);
}
}
//线程走完睡一会
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.itheima.练习.练习6.实现3;
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) throws InterruptedException {
//这里要是为了打乱获取集合的第一个元素,还是换成ArrayList集合比较好
ArrayList<Integer> pools = new ArrayList<>();
Collections.addAll(pools, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);
PrizePools p1 = new PrizePools(pools);
PrizePools p2 = new PrizePools(pools);
PrizePools p3 = new PrizePools(pools);
PrizePools p4 = new PrizePools(pools);
p1.setName("抽奖箱1");
p2.setName("抽奖箱2");
p3.setName("抽奖箱3");
p4.setName("抽奖箱4");
p1.start();
p2.start();
p3.start();
p4.start();
//在线程p1,p2,p3,p4之后执行main线程
Thread[] threads = {p1,p2,p3,p4};
for (Thread thread : threads) {
thread.join();
}
System.out.println("抽奖已谢幕");
}
}
线程的内存图
若有多个线程,每个线程都会执行run方法,而且每个线程都有自己的栈空间,也有各自的堆空间
练习4
实现1
package com.itheima.练习.练习7;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Random;
public class PrizePools extends Thread {
Random random = new Random();
/*
* 抽奖池,两个抽奖盒,每次随机抽奖,抽到一个抽奖盒-1个这个奖项
* 每次不打印,抽完一次性打印,统计各自的箱子最高/最低的奖项
* */ //共享数据是奖池[10,5,20,50,100,200,500,800,2,80,300,700]
static LinkedHashSet<Integer> pools;
static ArrayList<Integer> box1;//抽奖盒1
static ArrayList<Integer> box2;
static int sumbox1 = 0;
static int sumbox2 = 0;
static {
pools = new LinkedHashSet<Integer>();
Collections.addAll(pools, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);
box1 = new ArrayList<>();
box2 = new ArrayList<>();
}
@Override
public void run() {
while (true) {
/* try {
sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }*/ synchronized (PrizePools.class) {
int count = 0;
if (pools.isEmpty()) { //表示集合为空
break;//这里表示抽完就结束所有线程运行
} else {
//随机的索引
int index = random.nextInt(pools.size());//重置每次线程进来找索引取值
for (Integer money : pools) {
if (count == index) {
if (getName().equals("抽奖箱1")) {
box1.add(money);
sumbox1+=money;
// System.out.println(getName() + "又产生了一个" + money + "元大奖");
} else if (getName().equals("抽奖箱2")) {
box2.add(money);
sumbox2+=money;
// System.out.println(getName() + "又产生了一个" + money + "元大奖");
}
// 删除当前元素
pools.remove(money);
break; }
count++;
}
}
}
}
}
}
package com.itheima.练习.练习7;
import java.util.ArrayList;
import java.util.Optional;
public class Test {
public static void main(String[] args) throws InterruptedException {
PrizePools p1 = new PrizePools();
PrizePools p2 = new PrizePools();
p1.setName("抽奖箱1");
p2.setName("抽奖箱2");
p1.start();
p2.start();
p2.join();//在main线程之前执行
System.out.println("抽奖已谢幕");
ArrayList<Integer> box1 = PrizePools.box1;
ArrayList<Integer> box2 = PrizePools.box2;
//这里流的最大值只能是排序好的集合(这个说法是错误的stream的max里面已经排好序了)
Optional<Integer> box1Max = box1.stream().max(Integer::compare);// box1.stream().max((o1,o2)-> Integer.compare(o1, o2));
Optional<Integer> box1Min = box1.stream().min(Integer::compare);// box1.stream().min((o1,o2)-> Integer.compare(o1, o2));
Optional<Integer> box2Max = box2.stream().max(Integer::compare);// box1.stream().max((o1,o2)-> Integer.compare(o1, o2));
Optional<Integer> box2Min = box2.stream().min(Integer::compare);// box1.stream().min((o1,o2)-> Integer.compare(o1, o2));
System.out.println("抽奖箱1总共有"+box1.size()+"个奖项"+"分别为:"+box1+"最高奖项为"+box1Max.get()+"最低奖项为"+box1Min.get()+"总和"+PrizePools.sumbox1);
System.out.println("抽奖箱2总共有"+box2.size()+"个奖项"+"分别为:"+box2+"最高奖项为"+box2Max.get()+"最低奖项为"+box2Min.get()+"总和"+PrizePools.sumbox2);
System.out.println(box1Max.get()>box2Max.get()?
"抽奖箱1产生了最高奖项"+box1Max.get()+"元":"抽奖箱2产生了最高奖项"+box2Max.get()+"元");
}
}
实现2
获取两个线程运行完的最大值结果 ,每个线程使用
Collections.max()
方法来判断该抽奖盒的集合中的最大值
线程池
创建一个线程池,当任务提交时,线程池会创建一个线程用于执行这个任务,执行结束,线程会回到线程池。
然而 当任务大于线程数量时,其余的任务只能等着
线程池的执行原理
- 创建一个池子,池子里面是空的
- 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子下回再执行,就不需要创建新的线程了,直接使用线程池里已经有的线程。
- 一般来说,当所有线程任务执行完毕后,理想状态是将线程池关闭,实际是不会关闭服务器的线程池的
方法
无上限的线程池
- 这里先创建一个空的线程池
Execusors.newCachedThreaadPool()
- 提交任务给线程池 pool.submit(Runable实现类)
- 销毁线程池
pool.shutdown()
一般不写 ,这样就不会关闭线程池,他会处于一直运行状态,等待任务派发
package com.itheima.线程池;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class Test {
public static void main(String[] args) {
// 1. 创建一个线程池对象
ExecutorService pool = Executors.newCachedThreadPool();
// 2.提交任务 ,这里可以提交 实现了runable接口的对象
pool.submit(new MyRunnable());
// 3. 销毁线程池
// pool.shutdown();
}
}
package com.itheima.线程池;
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
这里线程池发现有任务,就创建了一个线程,每当执行一个任务,main线程沉睡1s,这时线程已经执行完任务,就回到了线程池,所以至始至终线程池只创建了一个线程
有上限的线程池
这里限制了线程总量为3 ,而且没有关闭线程池,他会处于一直运行状态,等待任务派发
package com.itheima.线程池.Pack2;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
// 1. 创建一个空的有上限的线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
// 2. 提交任务
pool.submit(new MyRunable());
pool.submit(new MyRunable());
pool.submit(new MyRunable());
pool.submit(new MyRunable());
pool.submit(new MyRunable());
}
}
package com.itheima.线程池.Pack2;
public class MyRunable implements Runnable{
@Override
public void run() {
for (int i = 1; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
自定义线程池
当核心线程用完时,排队序列占满时,才会创建临时线程
- 这里碰到这种情况,会先创建3个核心线程执行任务
- 会将3个任务排队等待
- 发现还有任务,创建3个临时线程执行任务
- 若 核心线程+临时线程+队伍线程的数量<任务数量,那么就会触发拒绝策略
拒绝策略
这里的第三种策略,会将排队时间最久的任务4抛弃掉,并将新的任务10加入排队序列中
这里为什么拒绝策略采用的是内部类这个方式,内部类只会依赖外部类而存在的,相当于外部类的一个功能,内部类单独出现没有意义,而且内部类本身就是一个独立的个体
这里提交了4个线程任务,而核心线程就3个,所以另一个会到等待序列队伍去等待。若给的任务减去核心线程数且超出等待序列的长度,那么这时会启动空闲线程去执行剩下的任务,如果还超出空闲线程的数量,那么就得看拒绝策略是怎么设置的。
package com.itheima.线程池.自定义线程池;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor poor = new ThreadPoolExecutor(
3,//核心线程数量
7,//最大线程数
1,// 空闲线程等待时间
TimeUnit.SECONDS, //空闲线程等待时间的单位
new ArrayBlockingQueue<>(5),//任务队列的个数
Executors.defaultThreadFactory(),//底层也是创建了一个Thread类,设置了些参数
//这里任务的拒绝策略是ThreadPoolExecutor的静态内部类
// new ThreadPoolExecutor.AbortPolicy() //抛弃任务,并抛出异常
// new ThreadPoolExecutor.DiscardPolicy() //丢弃任务,不抛出异常,这是不推荐的
// new ThreadPoolExecutor.DiscardOldestPolicy()// 丢弃等待时间长的等待序列中的任务,并将当前任务加入等待序列任务的末尾
new ThreadPoolExecutor.CallerRunsPolicy()//直接调用 run方法绕过线程池执行
);
//提交4个任务
poor.submit(()-> {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
}
});
poor.submit(()-> {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
}
});
poor.submit(()-> {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
}
});
poor.submit(()-> {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
}
});
}
}
最大并行数
4核8线程 : 4个大脑,通过虚拟(影分身)分出8个线程
所以 最大并行数是8
线程池的大小?
线程池的大小的决定分两种情况: cpu密集型运算,I/O密集型运算
- cpu密集型运算 : 设置线程池大小为 最大并行数+1,后面+1防止前面线程有个有异常缺页问题,可用及时补充位置
- I/O密集型运算: 对于本地文件处理/数据库数据操作比较多,一般是电脑的最大并行数x2
这里thread dump
可以计算cpu计算等待时间