定义多线程的三种方式

继承Thread类

// 1.定义一个类继承自Thread
class MyThread extends Thread {
// 2.重写run()方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"run()...");
}
}
}
public class ThreadDemo01 {
public static void main(String[] args) {
// 3.创建对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程1");
t2.setName("线程2");
// 4.启动线程
t1.start();
t2.start();
}
}

实现Runnable接口的方式

// 1.定义一个类实现Runnable接口
class MyRun implements Runnable {
// 2.重写run()方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
// 获取当前线程对象
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"run()...");
}
}
}
public class ThreadDemo02 {
public static void main(String[] args) {
// 3.创建对象(表示多线程要执行的任务)
MyRun mr1 = new MyRun();
MyRun mr2 = new MyRun();
// 4.创建线程对象
Thread t1 = new Thread(mr1);
Thread t2 = new Thread(mr2);
t1.setName("线程1");
t2.setName("线程2");
// 5.启动线程
t1.start();
t2.start();
}
}

利用Callable接口和Future接口[可以获取多线程的结果]

// 1.创建一个MyCallable类实现Callable接口
class MyCallable implements Callable<Integer> { // Callable的泛型表示这个线程返回的结果
//2.重写call方法,返回值表示多线程运行结果
@Override
public Integer call() {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}

}
public class ThreadDemo03 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 3.创建MyCallable对象【表示多线程要执行的任务】
MyCallable mc = new MyCallable();
// 4.创建FutureTask对象【管理多线程运行的结果】
FutureTask<Integer> ft = new FutureTask<>(mc);
// 5.创建线程对象
Thread thread = new Thread(ft);
// 6.启动线程
thread.start();
// 获取线程的返回结果
Integer result = ft.get();
System.out.println("result = " + result);
}
}

常见的成员方法

dd9adb91be924e9883e65f30ed8b321a.png

setPriority(int newPriority)【设置线程的优先级】 和 final int getPriority()【获取线程的优先级】

  • 优先级不是绝对的,只是表示线程有很大的概率能抢到CPU

final void setDaemon(boolean on):设置为守护线程

  • 当其他非守护线程结束了,守护线程也会陆续结束
  • 应用场景:线程1(聊天)、线程2(传输文件-守护线程)
class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0 ; i < 10 ; i++) {
System.out.println(getName() + i);
}
}
}

class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0 ; i < 100 ; i++) {
System.out.println(getName() + i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();

t2.setDaemon(true); // 设置为守护线程

t1.setName("非守护线程");
t2.setName("守护线程");

t1.start();
t2.start();
}
}

线程的生命周期【5种】

82b504e93c09411da619543c59ef03e9.png

线程安全的问题

线程在执行的时候会有随机性,CPU的执行权随时有可能被其他线程抢走

买票问题:三个窗口同时卖100张票

class MyThread extends Thread {
static int ticket = 0;
@Override
public void run() {
while(true) {
if(ticket < 100) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
++ticket;
System.out.println(getName() + "卖第" + ticket + "张票");
}else {
break;
}
}
}
}
public class ThreadDemo01 {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}

同步代码块:把操作共享数据的代码锁起来

  • 锁默认打开,有一个线程进去,锁自动关闭
  • 里面的代码全部执行完毕,线程出来,锁自动打开
synchronized (锁) {
操作共享数据的代码;
}

点击并拖拽以移动

更新上边买票代码:

class MyThread extends Thread {
static int ticket = 0;
// 锁对象一定要是唯一的
static Object obj = new Object();
@Override
public void run() {
while(true) {
// 同步代码块
synchronized (obj) { // 锁对象一般可以写成【当前类的字节码文件对象MyThraed.class】
if(ticket < 100) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
++ticket;
System.out.println(getName() + "卖第" + ticket + "张票");
}else {
break;
}
}
}
}
}

同步方法

把synchronized关键字加到方法上

修饰符 synchronized 返回值类型 方法名(方法参数){
...
}

点击并拖拽以移动

  • 同步方法是锁住方法里面的所有代码
  • 锁对象不能自己指定
    • 非静态:this
    • 静态:当前类的字节码文件对象

更新上边买票代码:

class MyRunnable implements Runnable {
int ticket = 0; // 如果是继承Runnable接口的方式定义的线程,不需要加static,因为只会定义一个MyRunnable对象
@Override
public void run() {
while(true) {
if (method()) break;
}
}

private synchronized boolean method() {
if(ticket == 100) {
return true;
}else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
++ticket;
System.out.println(Thread.currentThread().getName() + "卖第" + ticket + "张票");
}
return false;
}
}
public class ThreadDemo02 {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}

点击并拖拽以移动

Lock锁

void lock():获得锁

void unlock():释放锁

Lock是接口,不能直接实例化,需要采用他的实现类ReentrantLock来实例化

class MyThread1 extends Thread {
// 因为使用继承Thread的方式,所以ticket和lock都会创建多次,要保证只会创建一次,就要加上static关键字
static int ticket = 0;
static Lock lock = new ReentrantLock();
@Override
public void run() {
while(true) {
// 加锁
lock.lock();
try {
if(ticket < 100) {
Thread.sleep(10);
++ticket;
System.out.println(getName() + "卖第" + ticket + "张票");
}else {
break;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
// 释放锁【如果不写在finally里,达到100张票后,其中一个线程会经过break跳出循环,就无法释放锁,程序无法停止】
lock.unlock();
}
}
}
}
public class ThreadDemo03 {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
MyThread1 t2 = new MyThread1();
MyThread1 t3 = new MyThread1();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}

点击并拖拽以移动

死锁

死锁是一个错误,在写锁的时候,不要让两个锁嵌套写

37439290d8b94a73be88e05f4b13c3d3.png

等待唤醒机制

生产者和消费者

常见方法

4c3d2285c11c45aaaea14e7902cdc8c7.png

场景:有一个桌子(Desk)、厨师(Cook)、吃货(Foodie);要求厨师做一碗,吃货吃一碗。

// 【厨师】:生产者
class Cook extends Thread {
@Override
public void run() {
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("厨师正在做");
Desk.foodFlag = 1;
Desk.lock.notifyAll();
}
}
}
}
}
}

// 【吃货】:消费者
class Foodie extends Thread {
@Override
public void run() {
while(true) {
synchronized (Desk.lock) {
if(Desk.count == 0) {
break;
}else {
// 判断桌子上是否有面条
if(Desk.foodFlag == 0) {
// 没有 - 等待
try {
Desk.lock.wait();// 要用锁对象调用wait方法,让当前线程和锁进行绑定
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
// 有 - 开吃、唤醒厨师、总数-1、更改桌子的状态
Desk.count--;
System.out.println("吃货正在吃,还能吃:"+ Desk.count);
Desk.lock.notifyAll();// 唤醒绑定在这把锁上的所有线程
Desk.foodFlag = 0;
}
}
}
}
}
}

// 【桌子】:控制生产者和消费者的执行
class Desk {
// 桌子上是否有食物 0-没有食物、1-有食物
public static int foodFlag = 0;
// 总个数
public static int count = 10;
// 锁对象
public static Object lock = new Object();
}

public class ThreadDemo01 {
public static void main(String[] args) {
Cook cook = new Cook();
Foodie foodie = new Foodie();
cook.setName("厨师");
foodie.setName("吃货");
cook.start();
foodie.start();
}
}

点击并拖拽以移动

利用阻塞队列方式实现

阻塞队列:连接生产者和消费者之间的管道。

  • put数据:放不进去,会等着,叫做阻塞
  • take数据:取出第一个数据,取不到会等着,也叫阻塞

写的时候可以不用加锁,put()和take()底层就已经有锁了

阻塞队列的继承结构

92fe5595b5f24ca3b41d6462ac3b58a4.png

// 【厨师】:生产者
class Cook extends Thread {
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
// 不断地把面条放入阻塞队列中
try {
queue.put("面条");
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
// 【吃货】:消费者
class Foodie extends Thread {
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
// 不断地从阻塞队列中获取面条
try {
String food = queue.take();
System.out.println(food);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

}
}
}

public class ThreadDemo02 {
public static void main(String[] args) {
// 创建阻塞队列
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
// 创建线程的对象,并把阻塞队列传过去
Thread cook = new Cook(queue);
Thread foodie = new Foodie(queue);
cook.setName("厨师");
foodie.setName("吃货");
cook.start();
foodie.start();
}
}

点击并拖拽以移动

线程的状态【7种】

2ac906960b66471d8a04b7b4c0e2bcef.png

注:在Java虚拟机种只有六种状态,没有运行状态,因为线程抢到CPU的执行权进入运行状态,虚拟机就会把当前线程交给操作系统管理

438d5dbcea4c4f58926a6a50ae6f0f02.png

线程栈

6b7fe42977f5476ea43d715e2b857282.png

线程1和线程2的run()方法里的存储空间是相互独立的。

线程池

  1. 创建一个池子,池子是空的
  2. 提交任务,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接服用已有的线程即可。
  3. 如果提交任务时,池子里没有空闲的线程,也无法创建新的线程,任务就会排队等待。

Executors:线程池的工具类,通过调用方法返回不同类型的线程池对象

4de2b6aa401f4fff96e320e3d4885980.png

创建没有上限的线程池

class MyRunnable implements Runnable {

@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class ThreadDemo01 {
public static void main(String[] args) throws InterruptedException {
// 获取线程池对象
ExecutorService pool = Executors.newCachedThreadPool();
// 提交任务
pool.submit(new MyRunnable());
Thread.sleep(1000);
pool.submit(new MyRunnable());
Thread.sleep(1000);
pool.submit(new MyRunnable());
Thread.sleep(1000);
pool.submit(new MyRunnable());
Thread.sleep(1000);
pool.submit(new MyRunnable());
// 销毁线程池
pool.shutdown();
}
}

点击并拖拽以移动

线程是可以复用的,代码中让提交任务后,让main线程睡1秒中,此时上一个线程执行完毕,就会把线程重新放入线程池中

创建有上限的线程池

public class ThreadDemo02 {
public static void main(String[] args) {
// 获取线程池对象
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());
// 销毁线程池
pool.shutdown();
}
}

点击并拖拽以移动

上边的代码相当于是3个线程在执行5个任务。因为创建了多个线程,所以当代码一步一步往下走的时候,上一步可能是还没执行完的。所以运行到第四个提交任务的时候,就会有一个任务在排队了,到第五个提交任务,就有两个任务在排队。

线程池多大合适?

62bf226445104081a2cff5e196bce135.png

自定义线程池

110193df8ff741deaf86cff67b7a3b43.png

71e1aa7b657c428d946b3cf61cf8a39c.png

a7a1272ec0724c1db98149da1afc6ce3.png

核心线程都在处理任务,队伍中也已经排满了,此时才会创建临时线程去处理任务。

任务的执行不会按照提交的顺序去执行。

9b88ce4525dc49578d6921b95e2d81e1.png

核心线程和临时线程都在工作,队伍中也排满了,此时线程池就会触发任务拒绝策略

任务拒绝策略

166fdb7e0c654fcb956a927b8be1a833.png

代码实现

1df5bad3e7984d07a11a58beb0b75dd3.png

public class ThreadDemo03 {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, // 核心线程数量
6,// 最大线程数量
60,// 空闲线程最大存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(3), // 任务队列
Executors.defaultThreadFactory(), // 创建线程工厂
new ThreadPoolExecutor.AbortPolicy() // 任务的拒绝策略
);
}
}