1 多线程的实现方式
1.1 继承Thread类
- 自定义一个类继承Thread类
- 重写run方法
- 创建自定义类对象,启动线程
package com.ldz.thread_.itheima;
public class Thread1 {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}
1.2 实现Runnable接口
- 自定义类实现Runnable接口
- 重写run方法
- 创建自定义类对象,表示要执行的任务
- 创建线程对象,传入自定义对象,并开启线程
package com.ldz.thread_.itheima;
public class Thread2 {
public static void main(String[] args) {
// 创建MyRun对象,表示要执行的任务
MyRun myRun = new MyRun();
// 创建线程对象
Thread t1 = new Thread(myRun);
Thread t2 = new Thread(myRun);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
class MyRun implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "@" + i);
}
}
}
1.3 利用Callable接口和Future接口
该方法有返回值,前两种没有返回值
- 自定义类实现Callable接口
- 重写call方法,表示要执行的任务
- 创建自定义类的对象,表示多线程要执行的任务
- 创建FutureTask类的对象(作用管理多线程运行的结果)
- 创建Thread类的对象,并启动(表示线程)
package com.ldz.thread_.itheima;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Thread3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(myCallable);
Thread t = new Thread(task);
t.start();
System.out.println(task.get());
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
}
1.4 三种实现方式对比
2 常见成员方法
2.1 String getName()
默认值:Thread-X(X从0开始)
2.2 static Thread currentThread()
获取当前线程对象
当JVM虚拟器启动后,会自动启动多条线程。
其中有一条线程叫做main线程
它的作用就是调用main方法,并执行里面的代码
2.3 守护线程
将某个线程设置为守护线程后,其他没有设置的线程就是非守护线程
当非守护线程结束后,守护线程也会陆续结束
package com.ldz.thread_.itheima;
public class Thread4 {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.setName("飞机");
t2.setName("坦克");
t2.setDaemon(true);
t1.start();
t2.start();
}
}
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);
}
}
}
3 线程的生命周期
4 同步代码块
目的是解决线程不安全问题,如超卖问题
synchronized后面跟的锁,在多个线程间必须唯一,可以定义静态成员变量或者使用该类的字节码对象
package com.ldz.thread_.itheima;
public class Thread5 {
public static void main(String[] args) {
MyThread3 t1 = new MyThread3();
MyThread3 t2 = new MyThread3();
MyThread3 t3 = new MyThread3();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class MyThread3 extends Thread {
static int ticket = 1;
@Override
public void run() {
while (true) {
synchronized (MyThread3.class) {
if (ticket > 100) {
break;
}
try {
Thread.sleep(100);
System.out.println(getName() + "正在卖第" + ticket++ + "张票");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
5 同步方法
把synchronized加到方法上
package com.ldz.thread_.itheima;
public class Thread6 {
public static void main(String[] args) {
MyThread4 t1 = new MyThread4();
MyThread4 t2 = new MyThread4();
MyThread4 t3 = new MyThread4();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class MyThread4 extends Thread {
static int ticket = 1;
@Override
public void run() {
while (true) {
if (!t()) break;
}
}
private synchronized static boolean t() {
if (ticket > 100) {
return false;
}
try {
Thread.sleep(1);
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket++ + "张票");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
return true;
}
}
}
6 锁
同步代码块、同步方法是自动上锁、解锁。手动上锁就引出了这里的锁
- 多个线程间,必须用统一把锁,用static声明成员变量
- finnally中释放锁,避免break后,锁没有释放
package com.ldz.thread_.itheima;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Thread7 {
public static void main(String[] args) {
MyRunnable2 myRunnable2 = new MyRunnable2(new ReentrantLock());
Thread t1 = new Thread(myRunnable2);
Thread t2 = new Thread(myRunnable2);
Thread t3 = new Thread(myRunnable2);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class MyRunnable2 implements Runnable {
private static int ticket = 1;
private Lock lock;
public MyRunnable2(Lock lock) {
this.lock = lock;
}
@Override
public void run() {
while (true) {
lock.lock();
try {
if (ticket > 100) {
break;
}
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket++ + "张票");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
7 死锁
2个线程,互相等待
8 等待唤醒机制
8.1 问题描述
一般来说,线程的执行顺序是没有规律的,如下图所示
如果想让线程交替执行
则需要使用等待唤醒机制(生产者和消费者)
8.2 例
当你在调用notify(), notifyAll(),wait(), wait(long), wait(long, int)等线程控制操作方法时,必须要有两个前提。
第一:必须要在被synchronized关键字控制的同步代码块中,才能调用这些方法。
第二,调用者必须为你当前的锁对象。
package com.ldz.thread_.waitandnotify;
import sun.security.krb5.internal.crypto.Des;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class test {
public static void main(String[] args) {
Foodie foodie = new Foodie();
Cook cook = new Cook();
foodie.setName("吃货");
cook.setName("厨师");
foodie.start();
cook.start();
}
}
class Desk {
public static int foodFlag = 0;
public static int count = 10;
public static Lock lock = new ReentrantLock();
}
class Foodie extends Thread {
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
try {
if(Desk.count < 1) {
// 线程结束
break;
}
if (Desk.foodFlag == 0) Desk.lock.wait(); // 让当前线程跟锁对象进行绑定
else {
// 业务
System.out.println(getName() + "还能吃" + --Desk.count + "碗");
// 修改状态
Desk.foodFlag = 0;
// 唤醒生产者
Desk.lock.notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class Cook extends Thread {
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
try {
if (Desk.count < 1) {
break;
}
if (Desk.foodFlag == 1) {
// 等待
Desk.lock.wait();
} else {
System.out.println(getName() + "生产了1个");
// 生产
Desk.foodFlag = 1;
// 唤醒消费者
Desk.lock.notifyAll();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
8.3 阻塞队列实现等待唤醒机制
8.3.1 阻塞队列
-
阻塞:
put数据时,放不进去(队列满了),会等待,就叫阻塞
take数据时,取不到数据(队列空),会等待,也叫阻塞 -
队列:FIFO
8.3.2 阻塞队列的继承结构
package com.ldz.thread_.waitandnotify2;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingDeque;
public class test {
public static void main(String[] args) {
ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(1);
Cook cook = new Cook(blockingQueue);
Foodie foodie = new Foodie(blockingQueue);
cook.setName("厨师");
foodie.setName("吃货");
cook.start();
foodie.start();
}
}
class Cook extends Thread {
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
queue.put("面条");
System.out.println(getName() + "放了一碗面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Foodie extends Thread {
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true) {
try {
Thread.sleep(1000);
String food = queue.take();
System.out.println(getName() + "吃了一碗" + food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
9 线程池
9.1 引出
不使用线程池,创建线程时,被创建的线程在运行完后就会destroy
下一次还要使用线程执行任务时,就会新建线程,然后线程destroy
9.2 线程池主要核心原理
9.3 代码实现
-
创建线程池
ExecutorService pool = Executors.newCachedThreadPool(); ExecutorService pool = Executors.newFixedThreadPool(3);
-
提交任务
pool.submit(new MyRunnable())
MyRunnable类要实现Runnable接口,表示要执行的任务
-
所有的任务全部执行完毕,关闭线程池(一般不关)
pool.shutdown()
10 自定义线程池
10.1 参数
10.2 代码
ExecutorService pool = new ThreadPoolExecutor(
3, // 核心线程数量,不能小于0
6, // 最大线程数,大于等于核心线程数量
60, // 空闲线程最大存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(3), // 任务队列
Executors.defaultThreadFactory(), // 创建线程工厂
new ThreadPoolExecutor.AbortPolicy() // 任务拒绝策略
);
这里任务拒绝策略使用了内部类
为什么要使用内部类:
- 独立存在没有意义,要依赖外部类存在
- 内部类是一个独立的个体
10.3 不断地提交任务会有以下3个临界点:
-
当核心线程满时,再提交任务就会排队
-
当核心线程满,队列满时,会创建临时线程
-
当核心线程满,队列满,临时线程满时,会触发任务拒绝策略