1、线程的第一种实现方式 继承Thread类
1.1、先创建一个类继承Thread
public class MyThread extends Thread {
@Override
public void run() {
// 书写要执行的代码
for (int i = 0; i < 20; i++) {
System.out.println(this.getName() + "hello");
}
}
}
1.2、在main方法进行测试
public class ThreadDemo {
public static void main(String[] args) {
/**
* 多线程的第一种启动方式
* 1、定义一个类继承 Thread
* 2、重写 run 方法
* 3、创建类的对象,并启动线程
*/
MyThread myThread = new MyThread();
MyThread myThread1 = new MyThread();
myThread.setName("线程一");
myThread1.setName("线程二");
myThread.start();
myThread1.start();
}
}
2、线程的第二种实现方式 实现Runnable接口
2.1、定义一个类实现Runnable接口 并 重写里面的run方法
public class MyRun implements Runnable{
@Override
public void run() {
// 书写线程执行的方法
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() +"hello");
}
}
}
2.2、创建自己类的对象 并 创建一个Thread类的对象,并启动线程
public class ThreadDemo {
public static void main(String[] args) {
/**
* 多线程的第二种启动方式
* 1、定义一个类实现Runnable接口
* 2、重写里面的run方法
* 3、创建自己类的对象
* 4、创建一个Thread类的对象,并启动线程
*/
// 创建 MyRun 的对象
// 表示多线程要执行的任务
MyRun myRun = new MyRun();
// 创建线程对象
Thread t1 = new Thread(myRun);
Thread t2 = new Thread(myRun);
t1.setName("run---1");
t2.setName("run---2");
// 开启线程
t1.start();
t2.start();
}
}
3、线程的第三种实现方式 实现 Callable接口
3.1、创建一个类 MyCallable 实现 Callable 接口,重写call方法
// 1、创建一个类 MyCallable 实现 Callable 接口
public class MyCallable implements Callable<Integer> {
// 2、重写 call (是有返回值的,表示多线程运行结果)
@Override
public Integer call() throws Exception {
// 求 1-100之间的和
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
}
3.2、创建 MyCallable的对象,创建 FutureTask的对象,创建 Thread 类的对象
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/**
* 多线程的第三种实现方式 【可以获取多线程运行结果】
* 1、创建一个类 MyCallable 实现 Callable 接口
* 2、重写 call (是有返回值的,表示多线程运行结果)
* 3、创建 MyCallable的对象(表示多线程要执行的任务)
* 4、创建 FutureTask的对象(作用管理多线程运行的结果)
* 5、创建 Thread 类的对象(并启动表示线程)
*/
// 3、创建 MyCallable的对象(表示多线程要执行的任务)
MyCallable myCallable = new MyCallable();
// 4、创建 FutureTask的对象(作用管理多线程运行的结果)
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable);
// 5、创建 Thread 类的对象(并启动表示线程)
Thread t1 = new Thread(ft);
// 启动
t1.start();
// 获取返回结果并打印
Integer result = ft.get();
System.err.println("result=" + result);
}
}
使用Thread类
优点:编写相对简单,访问当前线程直接使用this就可以获得当前线程。
缺点:由于线程类已经继承了Thread类,所以不能再继承其他的父类。
使用Runnable接口
优点:没有继承Thread类,所以可以继承其他的类,适合多线程访问同一资源的情况,将 CPU 和数据分开,形成清晰的模型,较好的体现了面向对象的思想
缺点:编程相对复杂,要想获得当前线程对象,需要使用 Thread.currentThread() 方法。
使用callable接口
优点:也可以继承其他的类,多线程可以共享同一个target对象,适合多线程访问同一资源的情况,将cpu和数据分开,形成清晰的模型,较好的体现了面向对象的思想,还有返回值
缺点:编程稍显复杂,如果要访问当前线程,则必须使用 Thread.currentThread() 方法。
4、多线程常用成员方法
方法名 | 功能说明 |
---|---|
getName() | 获取线程名 |
setName(String name) | 修改线程名 |
static currentThread() | 获取当前正在执行的线程 |
static sleep(long millis, int nanos) | 使得当前线程阻塞millis毫秒加上纳秒 |
setPriority(int newPriority) | 设置线程的优先级 |
final int getPriority() | 获取线程的优先级 |
final void setDaemon(boolean on) | 设置为守护线程【当其他非守护线程结束,守护线程会陆续结束】 |
public static void yield() | 出让线程 / 礼让线程 |
public final void join() | 插入线程 / 插队线程 |
5、线程的生命周期
新建、就绪、运行、等待、销毁~
6、线程的安全问题
6.1、方式一 同步代码块
public class MyThread extends Thread {
// 共享 ticket 数据
static int ticket = 0;
// 锁对象,一定要唯一的
// static Object obj = new Object();
@Override
public void run() {
while (true) {
// 同步代码块
synchronized (MyThread.class) {
if (ticket < 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
} else {
break;
}
}
}
}
}
6.2、同步方法
将 同步代码块 抽取出来形成同步方法
public class MyRunnable implements Runnable {
int ticket = 0;
@Override
public void run() {
// 1、循环
while (true) {
// 2、同步代码块(同步方法)
if (method()) {
break;
}
}
}
// this
private synchronized boolean method() {
// 3、判断共享数据是否到了末尾,如果到了末尾
if (ticket == 100) {
return true;
} else { // 4、判断共享数据是否到了末尾,如果没到末尾
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(Thread.currentThread().getName()
+ "在卖第" + ticket + "张票!!!");
}
return false;
}
}
6.3、Lock 锁
public class MyThread extends Thread {
// 共享 ticket 数据
static int ticket = 0;
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 同步代码块
// synchronized (MyThread.class) {
lock.lock();
try {
if (ticket == 100) {
break;
} else {
Thread.sleep(100);
ticket++;
System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
// }
}
}
}
6.4、死锁
public class MyThread extends Thread {
static Object objA = new Object();
static Object objB = new Object();
@Override
public void run() {
// 循环
while (true) {
if ("线程A".equals(getName())) {
synchronized (objA) {
System.out.println("线程A拿到了A锁,准备拿B锁");
synchronized (objB) {
System.out.println("线程A拿到了B锁,顺利执行完一轮");
}
}
} else if ("线程B".equals(getName())) {
synchronized (objB) {
System.out.println("线程B拿到了A锁,准备拿A锁");
synchronized (objA) {
System.out.println("线程B拿到了A锁,顺利执行完一轮");
}
}
}
}
}
}
结论:不要两个锁嵌套使用
7、生产者和消费者 (等待唤醒机制)
方法名称 | 说明 |
---|---|
void wait() | 当前线程等待,直到被其他线程唤醒 |
void notify() | 随机唤醒单个线程 |
void notifyAll() | 唤醒所有线程 |
7.1、消费者等待
消费者:判断桌子上是否有食物,如果没有就等待;
生产者:制作食物,把食物放在桌子上,叫醒等待的消费者开吃
7.2、生产者等待
生产者:判断桌子上是否有食物,如果有就等待,没有就制作食物,把食物放在桌子上,叫醒等待的消费者开吃;
消费者:判断桌子上是否有食物,如果没有就等待,有就开吃,吃完之后,唤醒厨师继续做
步骤1 创建厨师线程
public class Cook extends Thread {
@Override
public void run() {
/**
* 1、循环
* 2、同步代码块
* 3、判断共享数据是否到了末尾
* 4、判断共享数据没有到末尾[执行核心]
*/
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
} else {
// 判断桌子上是否有食物
if (Desk.foodFlag == 1) {
// 如果有,就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
// 如果没有,就制作食物
System.out.println(getName() + "做了一碗面条");
// 修改桌子的状态
Desk.foodFlag = 1;
// 修改完成后,唤醒消费者开吃
Desk.lock.notifyAll();
}
}
}
}
}
}
步骤2 创建吃货线程
public class Foodie extends Thread {
@Override
public void run() {
/**
* 1、循环
* 2、同步代码块
* 3、判断共享数据是否到了末尾
* 4、判断共享数据没有到末尾[执行核心]
*/
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
} else {
// 判断桌子上有没有面条
if (Desk.foodFlag == 0) {
// 如果没有,就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
// 把吃的数量减一
Desk.count--;
// 如果有,就开吃
System.out.println(getName() + "在吃面条,还能再吃" + Desk.count + "碗!!!");
// 吃完之后,唤醒厨师继续做
Desk.lock.notifyAll();
// 修改桌子的状态
Desk.foodFlag = 0;
}
}
}
}
}
}
步骤3 创建一个共享类
public class Desk {
/**
* 作用:控制生产者和消费者的执行
*/
// 桌上是否有食物 0 无 1 有
public static int foodFlag = 0;
// 总个数
public static int count = 10;
// 锁对象
public static Object lock = new Object();
}
步骤4 创建主进程 【执行】
public class ThreadDemo {
public static void main(String[] args) {
/**
* 需求:完成生产者和消费者(等待唤醒机制)
* 实现线程轮流交替执行的效果
*/
// 创建线程对象
Cook cook = new Cook();
Foodie foodie = new Foodie();
cook.setName("厨师");
foodie.setName("吃货");
cook.start();
foodie.start();
}
}
7.3、阻塞队列实现 【模糊人事~】
厨师线程
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方法自带锁
queue.put("面条");
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
吃货线程
public class Foodie extends Thread {
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("吃货吃了一碗" + food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
主程序
public class ThreadDemo {
public static void main(String[] args) {
/**
* 需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)
* 细节:
* 生产者和消费者必须使用同一个阻塞队列
*/
// 1、创建阻塞队列的对象
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(10);
// 2、创建线程的对象,并把阻塞队列传递过去
Cook c = new Cook(queue);
Foodie f = new Foodie(queue);
// 3、开启线程
c.start();
f.start();
}
}
8、线程的状态
新增、就绪、运行、阻塞、等待、计时等待、死亡
9、测试
9.1、抢红包
public class MyThread extends Thread {
// 共享数据【100块,3个红包】
static int money = 100;
static int count = 3;
// 最小的中奖金额【0.01】,因为jdk17才可以,所以我定义最小金额为1
static final int MIN = 1;
@Override
public void run() {
// 循环【因为每人只抢一次,所以没有必要循环】
// 同步代码块
synchronized (MyThread.class) {
if (count == 0) {
// 判断共享数据是否已经到末尾【已经到末尾】
System.out.println(getName() + "没有抢到红包!");
} else {
// 判断共享数据是否已经到末尾【没到末尾】
// 不能直接随机【要扣除每一次的】
// 定义一个变量,表示中奖的金额
int prize = 0;
if (count == 1) {
// 此时是最后一个红包【无需随机,直接拿走】
prize = money;
} else {
// 表示还没到最后一次【随机】
Random r = new Random();
int bounds = money - (count-1)*MIN;
prize = r.nextInt(bounds);
// 判断是否会小于最小值
if (prize < MIN) {
prize = MIN;
}
}
// 去除这次获奖的金额
money = money - prize;
// 红包个数减一
count--;
// 本次红包的信息进行打印
System.out.println(getName() + "抢到了" + prize + "元");
}
}
}
}
主程序
public class ThreadDemo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
MyThread t4 = new MyThread();
MyThread t5 = new MyThread();
t3.setName("王昭君");
t4.setName("兰陵王");
t5.setName("钟馗");
t2.setName("阿轲");
t1.setName("安琪拉");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
9.2、抽奖池【初始态】
直接输出抽奖箱抽中的奖金
public class MyThread extends Thread {
// {10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700};
// 选择集合
ArrayList<Integer> list;
public MyThread(ArrayList<Integer> list) {
this.list = list;
}
@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);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
9.3、抽奖池【改良态】
现将抽中的奖金存入集合中,待抽奖完毕后一次性打印,并获取最高奖金
public class MyThread extends Thread {
// {10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700};
// 选择集合
ArrayList<Integer> list;
public MyThread(ArrayList<Integer> list) {
this.list = list;
}
// 线程一 所使用的的集合
static ArrayList<Integer> list1 = new ArrayList<Integer>();
// 线程二 所使用的的集合
static ArrayList<Integer> list2 = new ArrayList<Integer>();
@Override
public void run() {
while (true) {
synchronized (MyThread.class) {
if (list.size() == 0) {
// 判断是哪一个线程打印
if ("抽奖箱A".equals(getName())) {
System.out.println("抽奖箱A 中奖金额分别是" + list1 + ",最高金额是" + Collections.max(list1));
} else {
System.out.println("抽奖箱B 中奖金额分别是" + list2 + ",最高金额是" + Collections.max(list2));
}
break;
} else {
// 继续抽奖
Collections.shuffle(list);
int prize = list.remove(0);
// 判断是哪一个线程抽奖
if ("抽奖箱A".equals(getName())) {
list1.add(prize);
} else {
list2.add(prize);
}
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
9.4、抽奖池【最终态】
抽奖箱获取抽奖池中的最大值
public class MyCallable implements Callable<Integer> {
// {10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700};
// 选择集合
ArrayList<Integer> list;
public MyCallable(ArrayList<Integer> list) {
this.list = list;
}
@Override
public Integer call() throws Exception {
ArrayList<Integer> boxList = new ArrayList<Integer>();
while (true) {
synchronized (MyCallable.class) {
if (list.size() == 0) {
System.out.println(Thread.currentThread().getName() + boxList);
break;
} else {
// 继续抽奖
Collections.shuffle(list);
int prize = list.remove(0);
boxList.add(prize);
}
}
Thread.sleep(10);
}
// 把集合中的最大值返回
if (boxList.size() == 0) {
return null;
} else {
return Collections.max(boxList);
}
}
}
主线程
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建奖池
ArrayList<Integer> list = new ArrayList<Integer>();
Collections.addAll(list, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);
// 创建多线程要运行的参数对象
MyCallable mc = new MyCallable(list);
// 创建多线程运行结果的管理者对象
// ft1 线程一 管理者对象
FutureTask<Integer> ft1 = new FutureTask<Integer>(mc);
// ft2 线程二 管理者对象
FutureTask<Integer> ft2 = new FutureTask<Integer>(mc);
// 创建线程对象
Thread t1 = new Thread(ft1);
Thread t2 = new Thread(ft2);
// 设置名字
t1.setName("抽奖箱A");
t2.setName("抽奖箱B");
// 启动线程
t1.start();
t2.start();
Integer max1 = ft1.get();
Integer max2 = ft2.get();
System.out.println("抽奖箱A中最大奖金为" + max1);
System.out.println("抽奖箱B中最大奖金为" + max2);
if (max1 > max2) {
System.out.println("抽奖箱A获得抽奖池中最大奖金为" + max1);
} else {
System.out.println("抽奖箱B获得抽奖池中最大奖金为" + max2);
}
}
}
10、线程池
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象
方法名称 | 说明 |
---|---|
public static ExecutorService newCachedThreadPool() | 创建一个没有上限的线程池【Int的最大值】 |
public static ExecutorsService newFixedThreadPool(int nThreads) | 创建有上限的线程池 |
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
/*
// 1、获取无指定数量线程池对象
ExecutorService pool1 = Executors.newCachedThreadPool();
// 2、提交任务
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
// 3、销毁线程池
// pool1.shutdown();
*/
// 1、获取指定数量线程池对象
ExecutorService pool = Executors.newFixedThreadPool(3);
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
}
}
核心原理
-
创建一个池子,池子中是空的。
-
提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可。
-
但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待。
10.1、线程池详解
自定义线程池(任务拒绝策略)
任务拒绝策略 | 说明 |
---|---|
ThreadPoolExecutor.AbortPolicy | 默认策略:丢弃任务并抛出RejectedEexcutionException异常 |
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常,不推荐做法 |
ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务,然后把当前任务加入队列中 |
ThreadPoolExecutor.CallerRunsPolicy | 调用任务的run()方法绕过线程池直接执行 |
public class MyThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, // 核心线程数量,不能小于0
6, // 最大线程数,不能小于0 最大数量 >= 核心线程数量
60, // 空闲线程最大存活时间
TimeUnit.SECONDS, // 时间单位 用 TimeUnit 指定
new ArrayBlockingQueue<Runnable>(3), // 任务队列. 不能为 null
Executors.defaultThreadFactory(), // 创建线程工程,不能为 null
new ThreadPoolExecutor.AbortPolicy() // 任务的拒绝策略
);
// pool.submit();
}
}
小结 【不断的提交任务,会有以下三个临界点】
-
当核心线程满时,再提交任务就会排队
-
当核心线程满,队列满时,会创建临时线程
-
当核心线程满,队列满,临时线程满时,会触发任务拒绝策略
10.2、获取最大并行数
public class MyThreadPoolDemo {
public static void main(String[] args) {
// 向 Java 虚拟机返回可用处理器的数目
int count = Runtime.getRuntime().availableProcessors();
System.out.println(count);
}
}
10.3、线程池多大合适
可分为 CPU密集型运算 和 I/O密集型运算
-
CPU密集型运算
最大并行数 + 1 -
I/O密集型运算
最大并行数 * 期望CPU利用率 * (总时间【CPU计算时间 + 等待时间】)/ CPU计算时间