切换主题
十九、多线程
一、什么是线程
1、进程
2、线程
3、多线程
应用软件中互相独立,可以同时运行的功能4、并发和并行
- 并发:在同一时刻,有多个指令在单个CPU上交替执行
- 并行:在同一时刻,有多个指令在多个CPU上同时执行
二、多线程的实现方式
1、继承Thread类
- 定义一个类继承
Thread
- 重写
run
方法 - 创建子类的对象,并启动线程
MyThread
继承Thread
类
java
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName()+" helloworld");
}
}
}
main
方法调用
java
MyThread mt1=new MyThread();
MyThread mt2=new MyThread();
mt1.setName("线程1");
mt2.setName("线程2");
mt1.start();
mt2.start();
2、实现Runnable接口
- 定义一个类实现
Runnable
接口 - 重写run方法
- 创建自己类的对象
- 创建一个Thread对象,并开启线程
MyRun
java
public class MyRun implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+" helloworld");
}
}
}
main
方法
java
MyRun mr=new MyRun();
Thread t1=new Thread(mr);
Thread t2=new Thread(mr);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
3、利用Callable接口和Future接口
特点:可以获取到多线程运行的结果
创建一个类
MyCallable
实现Callable
接口重写
call
(是有返回值的,表示多线程运行的结果)创建
MyCallable
的对象(表示多线程要执行的任务)创建
FutureTask
的对象(作用管理多线程运行的结果)创建
Thread
类的对象,并启动(表示线程)创建
MyCallable
的对象javaimport java.util.concurrent.Callable; public 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; } }
main
方法javaMyCallable mc=new MyCallable(); FutureTask<Integer> ft=new FutureTask<>(mc); Thread t1=new Thread(ft); t1.start(); Integer result=ft.get(); System.out.println(result);
三、常见的成员方法
方法名称 | 说明 |
---|---|
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() | 插入线程/排队线程 |
1、getName
四、线程安全问题
1、同步代码块
把操作共享数据的代码锁起来
格式
java
synchronized (锁){ //一般来说,锁是当前类的字节码文件 MyThread.class
操作共享数据的代码
}
- 特点1:锁默认打开,有一个进程进去了,锁自动关闭
- 特点2:里面的代码全部执行完毕,线程出来,锁自动打开
2、同步方法
把synchronized
关键字加到方法上
格式
java
修饰符 synchronized 返回值类型 方法名 (方法参数) {...}
特点
- 同步方法是锁住方法里面所有的代码
- 锁对象不能自己指定
- 非静态的方法,锁是
this
- 静态的方法,锁是当前类的字节码文件对象
类名.class
- 非静态的方法,锁是
3、Lock锁
Lock
提供了获得锁和释放锁的方法
- void lock():获得锁
- void unlock():释放锁
java
static Lock lock = new ReentrantLock();
@Override
public void run() {
while(true) {
lock.lock();
try{
//操作
}catch(...){
}finally{
lock.unlock();
}
}
}
五、生产者和消费者(等待唤醒机制)
生产者消费者模式是一个十分经典的多线程协作的模式
1、常见方法
方法名称 | 说明 |
---|---|
void wait() | 当前线程等待,直到被其他线程唤醒 |
void notify() | 随机唤醒单个线程 |
void notifyAll() | 唤醒所有线程 |
2、案例一
消费者(消费数据)
- 判断桌子上是否有食物
- 如果没有就等待
- 如果有就开吃
- 吃完之后,唤醒厨师继续做
生产者(生产数据)
- 判断桌子上是否有食物
- 有:等待
- 没有:制作食物
- 把食物放在桌子上
- 叫醒等待的消费者开吃
Desk
java
package a01;
public class Desk {
// 作用:控制生产者和消费者的执行
// 控制面条的状态,1 有食物 0 无食物
public static int foodFlag = 0;
// 面条的个数
public static int count = 10;
// 锁对象
public static Object lock = new Object();
}
Cook
java
package a01;
public 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) {
e.printStackTrace();
}
} else {
Desk.foodFlag = 1;
System.out.println(Thread.currentThread().getName() + "做了一碗面条");
Desk.lock.notifyAll();
}
}
}
}
}
}
Foodie
java
package a01;
public 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();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
Desk.count--;
Desk.foodFlag = 0;
System.out.println(Thread.currentThread().getName() + "吃了一碗面条,还剩" + Desk.count + "碗面条");
Desk.lock.notifyAll();
}
}
}
}
}
}
ThreadDeom
java
package a01;
public class ThreadDemo {
public static void main(String[] args) {
Cook c = new Cook();
Foodie f = new Foodie();
c.setName("厨师");
f.setName("吃货");
System.out.println("一共" + Desk.count + "碗面条");
c.start();
f.start();
}
}
3、阻塞队列实现等待唤醒机制
Cook
java
package a02;
import java.util.concurrent.ArrayBlockingQueue;
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("面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Foodie
java
package a02;
import java.util.concurrent.ArrayBlockingQueue;
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();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
ThreadDemo
java
package a02;
import java.util.concurrent.ArrayBlockingQueue;
public class ThreadDemo {
public static void main(String[] args) {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
Cook c = new Cook(queue);
Foodie f = new Foodie(queue);
c.start();
f.start();
}
}
六、线程的状态
- 新建状态:创建线程对象
- 就绪对象:start方法
- 阻塞状态:无法获得锁对象
- 等待状态:wait方法
- 计时等待:sleep方法
- 结束状态:全部代码运行完毕
七、多线程练习题
1、卖电影票
一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒,要求:请用多线程模拟卖票过程并打印剩余电影票的数量
MyThread
java
package a03;
public class MyThread extends Thread {
static int total = 1000;
@Override
public void run() {
while (true) {
synchronized (MyThread.class) {
if (total == 0) {
break;
} else {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩" + (1000 - total) + "张票");
}
}
}
}
}
ThreadDemo
java
package a03;
public class ThreadDemo {
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.setName("窗口1");
m2.setName("窗口2");
m1.start();
m2.start();
}
}
2、送礼品
有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出,利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来
MyRunnable
java
public class MyThread implements Runnable {
int total = 100;
@Override
public void run() {
while (true) {
synchronized (MyThread.class) {
if (total < 10) break;
else {
total--;
System.out.println(Thread.currentThread().getName() + "发送了礼品,还剩" + total + "份");
}
}
}
}
}
RunnableDemo
java
public class ThreadDemo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr, "窗口1");
Thread t2 = new Thread(mr, "窗口2");
t1.start();
t2.start();
}
}
3、打印奇数数字
同时开启两个线程,共同获取1-100之间的所有数字。 要求:将输出所有的奇数。
MyCallable
java
package a05;
import java.util.ArrayList;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<ArrayList<Integer>> {
static ArrayList<Integer> result = new ArrayList<>();
@Override
public ArrayList<Integer> call() throws Exception {
for (int i = 0; i < 100; i++) {
synchronized (MyCallable.class) {
if ((i + 1) % 2 == 1) result.add(i + 1);
}
}
return result;
}
}
ThreadDemo
java
package a05;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<ArrayList<Integer>> ft = new FutureTask<>(mc);
Thread t1 = new Thread(ft);
Thread t2 = new Thread(ft);
t1.start();
t2.start();
System.out.println(ft.get());
}
}
4、抢红包
微信中的抢红包也用到了多线程。
假设:100块,分成了3个包,现在有5个人去抢。
其中,红包是共享数据。
5个人是5条线程。
打印结果如下:
XXX抢到了XXX元
XXX抢到了XXX元
XXX抢到了XXX元
XXX没抢到
XXX没抢到
MyThread
java
package a06;
import java.util.Random;
public class MyThread extends Thread {
static int count = 3;
static double money = 100;
static final double MIN = 0.01;
@Override
public void run() {
synchronized (MyThread.class) {
if (count == 0) {
System.out.println(getName() + "没有抢到红包!!!");
} else {
double price = 0;
if (count == 1) {
price = money;
} else {
Random r = new Random();
// 指定范围
double bounds = money - (count - 1) * MIN;
price = r.nextDouble(bounds);
if (price < MIN) price = MIN;
}
money -= price;
count--;
System.out.println(getName() + "抢到了" + price + "元红包");
}
}
}
}
main
java
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
MyThread mt3 = new MyThread();
MyThread mt4 = new MyThread();
MyThread mt5 = new MyThread();
mt1.setName("同学1");
mt2.setName("同学2");
mt3.setName("同学3");
mt4.setName("同学4");
mt5.setName("同学5");
mt1.start();
mt2.start();
mt3.start();
mt4.start();
mt5.start();
八、线程池
1、Executors
Executors
:线程池的工具类通过调用方法返回不同类型的线程池对象
方法名称 | 说明 |
---|---|
public static ExecutorService newCachedThreadPool | 创建一个没有上限的线程池 |
public static ExecutorService newCachedThreadPool(int nThreads) | 创建有上限的线程池 |
MyRunnable
java
public class MyRunnable implements Runnable{}
PoolDemo
java
//创建任务池
ExecutorService pool = Executors.newFixedThreadPool();
//提交任务
pool.submit(new MyRunnable());
//销毁线程池
pool.shutdown();
2、ThreadPoolExecutor
格式
java
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3,//corePoolSize:核心线程数量,能小于0
6,//maximumPoolSize:最大线程数,不能小于0,最大数量>=核心线程数量
TimeUnit.SECONDS,//空闲线程最大存活时间
new ArrayBlockingQueue<>(3),//任务队列
Executors.defaultThreadFactory(),//创建线程工厂
new ThreadPoolExecutor.AbortPolicy() //任务的拒绝策略
)
3、最大并行数
java
int i = Runtime.getRuntime().availableProcessors();
System.out.println(i);
4、线程池多少合适
- CPU密集型运算:最大并行数+1
- I/O密集型运算:最大并行数*期望CPU利用率*总时间(CPU计算时间+等待时间)/CPU计算时间