Java多线程
进程
进程是程序的基本执行实体
线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
简单理解:应用软件中互相独立,可以同时运行的功能
并发和并行
并发:在同一时刻,有多个指令在单个CPU上交替执行
并行:在同一时刻,有多个指令在多个CPU上同时执行
多线程的实现方式
- 继承 Thread 类的方式进行实现
package com.faxont.threadcase1;
public class ThreadDemo {
public static void main(String[] args) {
/**
* 多线程的第一种启动方式:
* 1. 自己定义一个类继承 Thread
* 2. 重写 run 方法
* 3. 创建子类的对象,并启动线程
*/
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
// 为线程命名
mt1.setName("线程1");
mt2.setName("线程2");
// 开启线程
mt1.start();
mt2.start();
}
}
package com.faxont.threadcase1;
public class MyThread extends Thread{
@Override
public void run() {
// 书写线程要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(getName() + " : HelloWorld");
}
}
}
- 实现 Runnable 接口的方式进行实现
package com.faxont.threadcase2;
public class ThreadDemo {
public static void main(String[] args) {
/**
* 多线程的第二种启动方式:
* 1. 自己定义一个类实现 Runnable 接口
* 2. 重写里面的 run 方法
* 3. 创建自己的类对象
* 4. 创建一个 Thread 类对象,并开启线程
*/
// 创建 MyRun 对象
// 表示多线程要执行的任务
MyRun mr = new MyRun();
// 创建 Thread 类对象
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
// 给线程设置名字
t1.setName("线程1");
t2.setName("线程2");
// 开启线程
t1.start();
t2.start();
}
}
package com.faxont.threadcase2;
public class MyRun implements Runnable{
@Override
public void run() {
// 书写线程要执行的代码
String name = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
System.out.println(name + " : HelloWorld");
}
}
}
- 利用 Callable 接口和 Future 接口方式实现
package com.faxont.threadcase3;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) {
/**
* 多线程的第三种实现方式:
* 特点:可以获取到多线程运行的结果
* 1. 创建一个类 MyCallable 实现 Callable 接口
* 2. 重写 call 方法(这个方法是有返回值的,表示多线程运行的结果)
* 3. 创建 MyCallable 类对象(表示多线程要执行的任务)
* 4. 创建 Future 的实现类 FutureTask 的对象(作用管理多线程运行的结果)
* 5. 创建 Thread 类对象,并开启线程
*/
// 创建 MyCallable 类对象
MyCallable mc = new MyCallable();
// 创建 FutureTask 的对象
FutureTask<Integer> ft = new FutureTask<>(mc);
// 创建 Thread 类对象,并开启线程
Thread t1 = new Thread(ft);
t1.start();
try {
// 获取多线程运行结果
Integer sum = ft.get();
System.out.println("结果:" + sum);
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.faxont.threadcase3;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
private int sum;
@Override
public Integer call() throws Exception{
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
}
多线程三种实现方式对比
| 实现方法 | 优点 | 缺点 |
|---|---|---|
| 继承 Thread 类 | 编程比较简单,可以直接使用 Thread 类中的方法 | 可扩展性较差,不能再继承其它的类 |
| 实现 Runnable 接口 | 扩展性强,实现该接口的同时还可以继承其它的类 | 编程相对复杂,不能直接使用 Thread 类中的方法 |
| 实现 Callable 接口 | 1. 扩展性强,可继承其它类; 2. 支持获取线程执行结果; 3. 可抛出异常 |
1. 编程更复杂,需要配合 FutureTask 使用; 2. 不能直接使用 Thread 类中的方法; 3. 获取结果时需要处理异常 |
常见的成员方法
| 方法名称 | 说明 |
|---|---|
| String getName() | 返回此线程的名称 |
| void setName(String name) | 设置线程的名字(构造方法也可以设置名字) |
| static Thread currentThread() | 获取当前线程的对象 |
| static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒 |
| setPriority(int newPriority) | 设置线程的优先级 |
| final int getPriority() | 获取线程的优先级 |
| final void setDaemon(boolean on) | 设置为守护线程 |
| public static void yield() | 出让线程 / 礼让线程 |
| public static void join() | 插入线程 / 插队线程 |
package com.faxont.threadmethod1;
public class ThreadDemo {
public static void main(String[] args) {
/*
String getName() 返回此线程的名称
void setName(String name) 设置线程的名字(构造方法也可以设置名字)
细节:
1. 如果我们没有给线程设置名字,线程也是有默认的名字的
格式:Thread-X(X为序号,从0开始递增)
2. 如果我们要给线程设置名字,可以用 setName()方法,也可以用构造方法
static Thread currentThread() 获取当前线程的对象
细节:
当 JVM 虚拟机启动之后,会自动地启动多条线程
其中有一条线程就叫做 main 线程
它的作用就是去调用 main 方法,并执行里面的代码
所以之前我们写的所有代码都是在 main 线程中执行的
static void sleep(long time) 让线程休眠指定的时间,单位为毫秒
细节:
1. 哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
2. 方法的参数:就表示睡眠时间,单位毫秒
3. 当时间到了之后,就会自动地醒来,继续执行下面的其它代码
*/
// 1. 创建线程对象
MyThread mt1 = new MyThread("军团");
MyThread mt2 = new MyThread("护士");
// 2. 开启线程
mt1.start();// Thread-0
mt2.start();// Thread-1
//
// Thread t = Thread.currentThread();
// System.out.println(t.getName());// main
// System.out.println("准备进行线程休眠...");
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println("线程休眠结束...");
}
}
package com.faxont.threadmethod1;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "@" + i);
}
}
public MyThread() {
}
public MyThread(String name) {
super(name);
}
}
package com.faxont.threadmethod2;
public class ThreadDemo {
public static void main(String[] args) {
/*
setPriority(int newPriority) 设置线程的优先级
final int getPriority() 获取线程的优先级
优先级:1~10 默认为5 优先级越高,抢占资源的概率越高
*/
// 创建线程要执行的参数对象
MyRunnable mr = new MyRunnable();
// 创建线程对象
Thread t1 = new Thread(mr, "线程1");
Thread t2 = new Thread(mr, "线程2");
// System.out.println(t1.getPriority());// 5
// System.out.println(t2.getPriority());// 5
// System.out.println(Thread.currentThread().getPriority());// 5
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
}
package com.faxont.threadmethod2;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
package com.faxont.threadmethod3;
public class ThreadDemo {
public static void main(String[] args) {
/*
final void setDaemon(boolean on) 设置为守护线程
细节:
当其它的非守护线程执行完毕之后,守护线程也会陆续结束
*/
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.setName("女神");
t2.setName("备胎");
// 把第二个线程设置为守护线程
t2.setDaemon(true);
t1.start();
t2.start();
}
}
package com.faxont.threadmethod3;
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "@" + i);
}
}
}
package com.faxont.threadmethod3;
public class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}
package com.faxont.threadmethod4;
public class ThreadDemo {
public static void main(String[] args) {
/*
public static void yield() 出让线程 / 礼让线程
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
package com.faxont.threadmethod4;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@" + i);
try {
// 表示出让当前CPU的执行权
Thread.yield();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
package com.faxont.threadmethod5;
public class ThreadDemo {
public static void main(String[] args) {
/*
public static void join() 插入线程 / 插队线程
*/
MyThread t = new MyThread();
t.setName("线程1");
t.start();
try {
// 表示把 t 线程插入到当前线程之前
// t:线程1
// 当前线程:main
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 执行在 main 线程当中的
for (int i = 0; i < 10; i++) {
System.out.println("main 线程" + i);
}
}
}
package com.faxont.threadmethod5;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}
线程安全的问题
练习:多窗口卖票
需求:
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
package com.faxont.threadsafe1;
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
*/
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
Thread t3 = new Thread(myRunnable);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
package com.faxont.threadsafe1;
public class MyRunnable implements Runnable{
private static int ticket = 100;
private static final Object obj = new Object();
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
}else {
break;
}
}
}
}
}
同步代码块
把操作共享数据的代码锁起来
// 格式
synchronized(锁){
操作共享数据的代码
}
特点1:锁默认打开,有一个线程进去了,锁自动关闭
特点2:里面的代码全部执行完毕,线程出来,锁自动打开
同步方法
就是把 synchronized 关键字加到方法上
格式:修饰符 synchronized 返回值类型 方法名(方法参数) {...}
特点1:同步方法是锁住方法里面所有的代码
特点2:锁对象不能自己指定
- 非静态:this
- 静态:当前类的字节码文件对象
package com.faxont.threadsafe2;
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
利用同步方法完成
*/
MyRunnable target = new MyRunnable();
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
Thread t3 = new Thread(target);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
package com.faxont.threadsafe2;
public class MyRunnable implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
saleTicket();
}
}
public synchronized void saleTicket() {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
}
}
}
Lock 锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,
但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰地表达如何加锁和释放锁,JDK5 以后提供了一个新的锁对象 Lock
Lock 实现提供比使用 sysnchronized 方法和语句可以获得更广泛的锁定操作
Lock 中提供了获得锁和释放锁的方法
void lock():获得锁
void unlock():释放锁
即可以手动上锁,手动释放锁
Lock 是接口不能直接实例化,这里采用它的实现类 ReentrantLock 来实例化
ReentrantLock 的构造方法
ReentrantLock():创建一个 ReentrantLock 的实例
package com.faxont.threadsafe3;
import com.faxont.threadsafe2.MyRunnable;
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
利用同步方法完成
*/
MyRunnable target = new MyRunnable();
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
Thread t3 = new Thread(target);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
package com.faxont.threadsafe3;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyRunnable implements Runnable{
private int ticket = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
ticket--;
}else{
break;
}
} finally{
lock.unlock();
}
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
生产者和消费者(等待唤醒机制)
生产者消费者模式是一个十分经典的多线程协作的模式
生产者和消费者(常见方法)
| 方法名称 | 说明 |
|---|---|
| void wait() | 当前线程等待,直到被其它线程唤醒 |
| void notify() | 随机唤醒单个线程 |
| void notifyAll() | 唤醒所有线程 |
package com.faxont.waitandnotify;
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:完成生产者和消费者(等待唤醒机制)的代码
实现线程轮流交替执行的效果
*/
Desk desk = new Desk();
Foodie foodie = new Foodie(desk);
Cook cook = new Cook(desk);
Thread t1 = new Thread(foodie);
Thread t2 = new Thread(cook);
t1.setName("消费者");
t2.setName("生产者");
t1.start();
t2.start();
}
}
package com.faxont.waitandnotify;
public class Foodie implements Runnable{
private Desk desk;
public Foodie(Desk desk) {
this.desk = desk;
}
@Override
public void run() {
while (true) {
synchronized (desk.obj){
if(desk.count == 0 && desk.hasFood == 0){
break;
}
if(desk.hasFood == 0){
try {
desk.obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "正在吃,还能再吃" + desk.count + "碗");
desk.hasFood = 0;
System.out.println(Thread.currentThread().getName() + "吃完了");
desk.obj.notify();
}
}
}
}
package com.faxont.waitandnotify;
public class Cook implements Runnable{
private Desk desk;
public Cook(Desk desk) {
this.desk = desk;
}
@Override
public void run() {
while (true) {
synchronized (desk.obj){
if(desk.count == 0){
break;
}
if(desk.hasFood == 1){
try {
desk.obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "开始做菜");
desk.hasFood = 1 ;
System.out.println(Thread.currentThread().getName() + "做菜完毕,放到了桌上");
desk.count--;
desk.obj.notify();
}
}
}
}
package com.faxont.waitandnotify;
public class Desk {
/**
* 作用:控制生产者和消费者的执行
*/
// 0-表示没有食物,1-表示有食物
public int hasFood = 0;
// 总个数
public int count = 10;
// 锁对象
public Object obj = new Object();
}
等待唤醒机制(阻塞队列方式实现)
put 数据时:放不进去,会等着,也叫做阻塞。
take 数据时:取出第一个数据,取不到会等着,也叫做阻塞。
package com.faxont.waitandnotify2;
import java.util.concurrent.ArrayBlockingQueue;
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
细节:
生产者和消费者必须使用同一个阻塞队列
*/
// 1. 创建阻塞队列的对象
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
// 2. 创建生产者和消费者
Cook cook = new Cook(queue);
Foodie foodie = new Foodie(queue);
Thread t1 = new Thread(cook);
Thread t2 = new Thread(foodie);
t1.setName("厨师");
t2.setName("吃货");
t1.start();
t2.start();
}
}
package com.faxont.waitandnotify2;
import java.util.concurrent.ArrayBlockingQueue;
public class Cook implements Runnable{
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
queue.put("面条");
System.out.println(Thread.currentThread().getName() + "放了一碗面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.faxont.waitandnotify2;
import java.util.concurrent.ArrayBlockingQueue;
public class Foodie implements Runnable{
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
String food = queue.take();
System.out.println(Thread.currentThread().getName() + "吃掉了一个" + food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
综合练习
练习:多线程练习1(卖电影票)
一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒
要求:请用多线程模拟卖票过程并打印剩余电影票的数量
package com.faxont.com.practice1;
public class Test {
public static void main(String[] args) {
/*
一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒
要求:请用多线程模拟卖票过程并打印剩余电影票的数量
*/
MovieTickerSellRunnable mt = new MovieTickerSellRunnable();
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
t1.setName("窗口1");
t2.setName("窗口2");
t1.start();
t2.start();
}
}
package com.faxont.com.practice1;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MovieTickerSellRunnable implements Runnable{
private int ticketCount = 1000;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try{
if(ticketCount > 0){
System.out.println(Thread.currentThread().getName() + "卖出一票,剩余" + ticketCount + "张电影票");
ticketCount--;
}else{
break;
}
}finally{
lock.unlock();
}
try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
练习:多线程练习2(送礼品)
有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出
利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来
package com.faxont.com.practice2;
public class Test {
public static void main(String[] args) {
/*
有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出
利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来
*/
GiftsSendRunnable gsr = new GiftsSendRunnable();
Thread t1 = new Thread(gsr);
Thread t2 = new Thread(gsr);
t1.setName("Faxont");
t2.setName("Scroll");
t1.start();
t2.start();
}
}
package com.faxont.com.practice2;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class GiftsSendRunnable implements Runnable{
private int giftCount = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while(true){
lock.lock();
try{
if(giftCount >=10){
System.out.println(Thread.currentThread().getName() + "送出礼物,剩余" + giftCount + "个礼物");
giftCount--;
}else{
break;
}
}finally {
lock.unlock();
}
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
练习:多线程练习3(打印奇数数字)
同时开启两个线程,共同获取1-100之间的所有数字
要求:将输出所有的奇数
package com.faxont.com.practice3;
public class Test {
public static void main(String[] args) {
/*
同时开启两个线程,共同获取1-100之间的所有数字
要求:将输出所有的奇数
*/
PrintOddRunnable por = new PrintOddRunnable();
Thread t1 = new Thread(por);
Thread t2 = new Thread(por);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
package com.faxont.com.practice3;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class PrintOddRunnable implements Runnable{
private int num = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while(true){
lock.lock();
try{
if(num > 0){
if(num % 2 == 1){
System.out.println(Thread.currentThread().getName() + "打印了" + num);
}
num--;
}else{
break;
}
}finally {
lock.unlock();
}
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
练习:多线程练习4(抢红包)
抢红包也用到了多线程
假设:100块,分成了3个包,现在有5个人去抢
其中,红包是共享数据
5个人是5条线程
打印结果如下:
XXX抢到了XXX元
XXX抢到了XXX元
XXX抢到了XXX元
XXX没抢到
XXX没抢到
package com.faxont.com.practice4;
public class Test {
public static void main(String[] args) {
/*
抢红包也用到了多线程
假设:100块,分成了3个包,现在有5个人去抢
其中,红包是共享数据
5个人是5条线程
打印结果如下:
XXX抢到了XXX元
XXX抢到了XXX元
XXX抢到了XXX元
XXX没抢到
XXX没抢到
*/
HongBaoRunnable hbr = new HongBaoRunnable();
Thread t1 = new Thread(hbr);
Thread t2 = new Thread(hbr);
Thread t3 = new Thread(hbr);
Thread t4 = new Thread(hbr);
Thread t5 = new Thread(hbr);
t1.setName("路人甲");
t2.setName("路人乙");
t3.setName("路人丙");
t4.setName("路人丁");
t5.setName("路人戊");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
package com.faxont.com.practice4;
import java.text.DecimalFormat;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HongBaoRunnable implements Runnable{
private double money = 100.00;
private int count = 3;
private Lock lock = new ReentrantLock();
private int index = 0;
private double[] moneyArr;
private static final double MIN_MONEY = 0.01;
// 格式化工具:保留两位小数
private static final DecimalFormat df = new DecimalFormat("#.00");
public HongBaoRunnable() {
moneyArr = getMoney(money, count);
}
@Override
public void run() {
lock.lock();
try {
if(index < count){
System.out.println(Thread.currentThread().getName() + "抢到了" + moneyArr[index] + "元");
index++;
}else{
System.out.println(Thread.currentThread().getName() + "没抢到");
}
}finally{
lock.unlock();
}
}
private double[] getMoney(double totalMoney, int count){
double[] result = new double[count];
Random r = new Random();
double leftMoney = totalMoney;
for (int i = 0; i < count - 1; i++) {
// df.format(...)会返回字符串,所以需要 Double.parseDouble 将其转换为 double 类型的数据
double num = Double.parseDouble(df.format(r.nextDouble(leftMoney - MIN_MONEY * (count - i - 1))));
if(num < MIN_MONEY){
num = MIN_MONEY;
}
result[i] = num;
leftMoney -= num;
}
result[count - 1] = Double.parseDouble(df.format(leftMoney));
return result;
}
}
练习:多线程练习5(抽奖箱抽奖)
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80,300,700}
创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
随机从抽奖池中获取奖项元素并打印再控制台上,格式如下:
每次抽出一个奖项就打印一个(随机)
抽奖箱1 又产生了一个 10 元大奖
抽奖箱1 又产生了一个 100 元大奖
抽奖箱1 又产生了一个 200 元大奖
抽奖箱1 又产生了一个 800 元大奖
抽奖箱2 又产生了一个 700 元大奖
...
package com.faxont.com.practice5;
public class Test {
public static void main(String[] args) {
/*
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80,300,700}
创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
随机从抽奖池中获取奖项元素并打印再控制台上,格式如下:
每次抽出一个奖项就打印一个(随机)
抽奖箱1 又产生了一个 10 元大奖
抽奖箱1 又产生了一个 100 元大奖
抽奖箱1 又产生了一个 200 元大奖
抽奖箱1 又产生了一个 800 元大奖
抽奖箱2 又产生了一个 700 元大奖
...
*/
LotteryRunnable lr = new LotteryRunnable();
Thread t1 = new Thread(lr);
Thread t2 = new Thread(lr);
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
t1.start();
t2.start();
}
}
package com.faxont.com.practice5;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LotteryRunnable implements Runnable{
private static final List<Integer> lottery = new ArrayList<>();
private int index = 0;
private Lock lock = new ReentrantLock();
private static final Random r = new Random();
static {
int[] arr = {10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700};
for (int num : arr) {
lottery.add(num);
}
}
@Override
public void run() {
while(true){
lock.lock();
try{
if(lottery.isEmpty()){
break;
}
int randomIndex = r.nextInt(lottery.size());
int prize = lottery.remove(randomIndex);
System.out.println(Thread.currentThread().getName() + "又产生了一个 " + prize + " 元大奖");
index++;
}finally{
lock.unlock();
}
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
练习:多线程练习6(多线程统计并求最大值)
在上一题基础上继续完成如下需求:
每次抽的过程中,不打印,抽完一次性打印(随机)
在此次抽奖过程中,抽奖箱1总共产生了6个奖项
分别为:10, 20, 100, 500, 2, 300最高奖项为500元,总计饿为932元
在此次抽奖过程中,抽奖箱2总共产生了6个奖项
分别为:5, 50, 200, 800, 80, 700最高奖项为800元,总计额为1835元
package com.faxont.com.practice6;
public class Test {
public static void main(String[] args) {
/*
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80,300,700}
创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
随机从抽奖池中获取奖项元素并打印再控制台上,格式如下:
每次抽出一个奖项就打印一个(随机)
抽奖箱1 又产生了一个 10 元大奖
抽奖箱1 又产生了一个 100 元大奖
抽奖箱1 又产生了一个 200 元大奖
抽奖箱1 又产生了一个 800 元大奖
抽奖箱2 又产生了一个 700 元大奖
...
在上一题基础上继续完成如下需求:
每次抽的过程中,不打印,抽完一次性打印(随机)
在此次抽奖过程中,抽奖箱1总共产生了6个奖项
分别为:10, 20, 100, 500, 2, 300最高奖项为500元,总计饿为932元
在此次抽奖过程中,抽奖箱2总共产生了6个奖项
分别为:5, 50, 200, 800, 80, 700最高奖项为800元,总计额为1835元
*/
LotteryRunnable lr1 = new LotteryRunnable();
LotteryRunnable lr2 = new LotteryRunnable();
Thread t1 = new Thread(lr1);
Thread t2 = new Thread(lr2);
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
t1.start();
t2.start();
}
}
package com.faxont.com.practice6;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LotteryRunnable implements Runnable{
private static final List<Integer> lottery = new ArrayList<>();
private static volatile int index = 0;
private static final Lock lock = new ReentrantLock();
private final List<Integer> prizes = new ArrayList<>();
private static final Random r = new Random();
static {
int[] arr = {10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700};
for (int num : arr) {
lottery.add(num);
}
}
@Override
public void run() {
while(true){
lock.lock();
try{
if(lottery.isEmpty()){
break;
}
int randomIndex = r.nextInt(lottery.size());
int prize = lottery.remove(randomIndex);
prizes.add(prize);
}finally {
lock.unlock();
}
try { Thread.sleep(10); } catch (InterruptedException e) {}
}
printLottery();
}
private void printLottery(){
String name = Thread.currentThread().getName();
int count = prizes.size();
int max = Collections.max(prizes);
int sum = prizes.stream().mapToInt(Integer::intValue).sum();
System.out.println("在此次抽奖过程中," + name + "总共产生了" + count + "个奖项\n分别为" + prizes + "\s最高奖项为" + max + "元,总计额为" + sum + "元");
}
}
练习:多线程练习7(多线程之间的比较)
在上一题基础上继续完成如下需求:
在此次抽奖过程中,抽奖箱1总共产生了6个奖项,分别为:10, 20, 100, 500, 2, 300
最高奖项为300元,总计额为932元
在此次抽奖过程中,抽奖箱2总共产生了6个奖项,分别为:5, 50, 200, 800, 80, 700
最高奖项为800元,总计额为1835元
在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元
以上打印效果只是数据模拟,实际代码运行的效果会有差异
package com.faxont.com.practice7;
public class Test {
public static void main(String[] args) {
/*
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80,300,700}
创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
随机从抽奖池中获取奖项元素并打印再控制台上,格式如下:
每次抽出一个奖项就打印一个(随机)
抽奖箱1 又产生了一个 10 元大奖
抽奖箱1 又产生了一个 100 元大奖
抽奖箱1 又产生了一个 200 元大奖
抽奖箱1 又产生了一个 800 元大奖
抽奖箱2 又产生了一个 700 元大奖
...
在上一题基础上继续完成如下需求:
在此次抽奖过程中,抽奖箱1总共产生了6个奖项,分别为:10, 20, 100, 500, 2, 300
最高奖项为300元,总计额为932元
在此次抽奖过程中,抽奖箱2总共产生了6个奖项,分别为:5, 50, 200, 800, 80, 700
最高奖项为800元,总计额为1835元
在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元
以上打印效果只是数据模拟,实际代码运行的效果会有差异
*/
LotteryRunnable lr1 = new LotteryRunnable();
LotteryRunnable lr2 = new LotteryRunnable();
Thread t1 = new Thread(lr1);
Thread t2 = new Thread(lr2);
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
int max1 = lr1.getMaxPrize();
int max2 = lr2.getMaxPrize();
if(max1 > max2){
System.out.println("在此次抽奖过程中," + t1.getName() + "中产生了最大奖项,该奖项金额为" + max1 + "元");
}else{
System.out.println("在此次抽奖过程中," + t2.getName() + "中产生了最大奖项,该奖项金额为" + max2 + "元");
}
}
}
package com.faxont.com.practice7;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LotteryRunnable implements Runnable{
private static final List<Integer> lottery = new ArrayList<>();
private static volatile int index = 0;
private static final Lock lock = new ReentrantLock();
private final List<Integer> prizes = new ArrayList<>();
private static final Random r = new Random();
static {
int[] arr = {10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700};
for (int num : arr) {
lottery.add(num);
}
}
@Override
public void run() {
while(true){
lock.lock();
try{
if(lottery.isEmpty()){
break;
}
int randomIndex = r.nextInt(lottery.size());
int prize = lottery.remove(randomIndex);
prizes.add(prize);
}finally {
lock.unlock();
}
try { Thread.sleep(10); } catch (InterruptedException e) {}
}
printLottery();
}
private void printLottery(){
String name = Thread.currentThread().getName();
int count = prizes.size();
int max = Collections.max(prizes);
int sum = prizes.stream().mapToInt(Integer::intValue).sum();
System.out.println("在此次抽奖过程中," + name + "总共产生了" + count + "个奖项\n分别为" + prizes + "\s最高奖项为" + max + "元,总计额为" + sum + "元");
}
public int getMaxPrize(){
return Collections.max(prizes);
}
}
线程池
以前写多线程的弊端
弊端1:用到线程的时候就创建
弊端2:用完之后线程消失
线程池的核心原理
- 创建一个池子,池子中是空的
- 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再提交任务时,不需要创建新的线程,直接复用已有的线程即可
- 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
线程池代码实现
-
创建线程池
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。
方法名称 说明 public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池 public static ExecutorService newFixedThreadPool(int nThreads) 创建有上限的线程池 -
提交任务
-
所有的任务全部执行完毕,关闭线程池
package com.faxont.threadpool1;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
public class ThreadPoolDemo {
public static void main(String[] args) {
/*
public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池
public static ExecutorService newFixedThreadPool(int nThreads) 创建有上限的线程池
*/
// 1. 获取线程池对象
ExecutorService pool1 = Executors.newCachedThreadPool();
ExecutorService pool2 = Executors.newFixedThreadPool(3);
// 2. 提交任务
// pool1.submit(new MyRunnable());// 线程名为 pool-1-thread-1
// pool1.submit(new MyRunnable());
// pool1.submit(new MyRunnable());
// pool1.submit(new MyRunnable());
// pool1.submit(new MyRunnable());
// 能提交多个线程任务,但是线程池里只有三个线程
pool2.submit(new MyRunnable());// 线程名为 pool-2-thread-1
pool2.submit(new MyRunnable());
pool2.submit(new MyRunnable());
pool2.submit(new MyRunnable());
pool2.submit(new MyRunnable());
// 3. 销毁线程池
// pool1.shutdown();
}
}
package com.faxont.threadpool1;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
自定义线程池(任务拒绝策略)
| 任务拒绝策略 | 说明 |
|---|---|
| ThreadPoolExecutor.AbortPolicy | 默认策略:丢弃任务并抛出 RejectedExecutionException 异常 |
| ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常,这是不推荐的做法 |
| ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务,然后把当前任务加入队列中 |
| ThreadPoolExecutor.CallerRunsPolicy | 调用任务的 run() 方法绕过线程池直接执行 |
package com.faxont.threadpool2;
import java.util.concurrent.*;
public class ThreadPoolDemo1 {
public static void main(String[] args) {
/*
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
(核心线程数量,最大线程数,空间线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);
参数一:核心线程数量 不能小于0
参数二:最大线程数量 不能小于等于0,最大数量 >= 核心线程数量
参数三:空间线程最大存活时间 不能小于0
参数四:时间单位 用 TimeUnit 指定
参数五:任务队列 不能为 null
参数六:创建线程工厂 不能为 null
参数七:任务的拒绝策略 不能为 null
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
3,// 核心线程数量, 不能小于0
6,// 最大线程数量, 不能小于等于0, 最大数量 >= 核心线程数量
60,// 空间线程最大存活时间, 不能小于0
TimeUnit.SECONDS,// 时间单位
new ArrayBlockingQueue<>(3),// 任务队列
Executors.defaultThreadFactory(),// 创建线程工厂
new ThreadPoolExecutor.AbortPolicy()// 任务的拒绝策略
);
}
}
自定义线程池小结
- 创建一个空的池子
- 有任务提交时,线程池会创建线程去执行任务,执行完毕归还线程
不断地提交任务,会有以下三个临界点:
- 当核心线程满时,再提交任务就会排队
- 当核心线程满,队伍满时,会创建临时线程
- 当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略
线程池多大合适呢?

package com.faxont.threadpool2;
public class ThreadPoolDemo2 {
public static void main(String[] args) {
// 向 Java 虚拟机返回可用处理器的数目
int count = Runtime.getRuntime().availableProcessors();
System.out.println(count);
}
}

浙公网安备 33010602011771号