多线程描述
一个程序在执行的过程中,可以产生多个线程,每个线程有自己的执行路径,线程是在基于进程之上划分的,没有进程就没有线程,进程相当于一个应用程序,有独立的内存空间。每个线程有自己的产生,存在,消亡的过程。线程有自己独立的栈空间和共享的堆空间。
- 继承Thread
- 实现Runnable
线程的调度
分时调度
- 所有线程轮流使用CPU的使用权,平分每个线程的CPU占用时间
抢占式调度
- 线程执行优先级相同的情况下,CPU会随机选择执行线程,优先级高的线程抢到CPU时间片的概率会大很多。JAVA使用的就是抢占式调度。
同步和异步
同步的意思就是多个线程之间排队执行,当执行中的线程执行完的时候,才会执行下一个线程,这种机制效率低,但安全。
异步就是多个线程之间同时执行,这种情况下,数据是不安全的。但是效率高。
并发与并行
并发就是在一个时间段内发生了多个事件
并行就是多个事件同时发生
多线程实现
继承Thread
通过实例化继承了Thread的子类来创建这个线程,调用start()方法来启动这个线程。
// MyThread.java
public class MyThread extends Thread {
@Override
public void run() {
super.run();
// 这里是线程要执行的代码块
}
}
// Main.java
public class Main {
public static void main(String[] args) {
// 创建一个线程
MyThread myThread = new MyThread();
// 启动线程
myThread.start();
}
}
实现Runnable接口
声明一个类实现Runnable接口,然后实现run()方法。在创建Thread时传入这个类的实例。然后调用start()启动线程。
// MyRunnable.java
public class MyRunnable implements Runnable {
@Override
public void run() {
// 这里是线程执行的代码块
}
}
// Main.java
public class Main {
public static void main(String[] args) {
// 实例化MyRunnable
MyRunnable myRunnable = new MyRunnable();
// 创建一个匿名Thread并传入myRunnable然后调用start()启动
new Thread(myRunnable).start();
}
}
获取和设置线程的名称
每一个线程都有一个名称,多个线程名称可重复。在创建线程时为传入名称会自动生成一个名称。
Thread.currentThread() 是返回当前线程对象的引用
// 获取线程名称
Thread.currentThread().getName();
// 设置线程名称
Thread.currentThread().setName("设置的名称");
线程休眠
使当前正在执行的线程进入休眠状态(暂停执行)。
Thread.sleep(1000);
// 休眠1000毫秒,参数为long类型
Thread.sleep(1000, 1000);
// 休眠1000毫秒加1000纳秒, 参数二为int类型
线程阻塞
线程阻塞就是所有比较耗时间的操作都可以理解为线程阻塞。Thread.sleep()和Thread.wait()都是线程阻塞的一种情况。
线程中断
一个线程它是否要结束由它自己决定。也可以由外部调用其interrupt()方法告诉线程你该结束了,但具体要不要中断线程,还是要看线程内部是怎么实现的。
调用interrupt()的几种情况:
- 若该线程调用了wait(), wait(long), wait(long, int)或join(long), join(long, int), sleep(long), sleep(long, int),那这个时候将会清除其中断状态并收到InterruptedException异常。
- 若在InterruptibleChannel上的IO操作中终止该线程,则通道将关闭,线程的中断状态被清除并收到ClosedByInterruptException异常。
- 如果该线程在Selector中被阻塞,则线程的中断状态将被设置,它将立即从选择操作返回,可能具有非零值,就像调用选择器的wakeup方法一样。
如果以前的条件都不成立,则将设置该线程的中断状态。中断不活动的线程不会产生任何影响。
守护线程
当没有一个用户线程,仅剩下守护线程的时候。JVM虚拟机将退出。
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
}
});
thread.setDaemon(true); // 设置为守护线程
thread.setDaemon(false); // 设置为用户线程
// 判断当前线程是否为守护线程
thread.isDaemon();
线程安全
同步代码块
// 格式 synchronized(锁对象) {} 注:锁对象可以是任何对象,如果多个线程之间不是同一个锁对象,则不能实现同步锁。
// 如:
class Ticket implements Runnable {
private int count = 10;
// 锁对象
private final Object i = new Object();
@Override
public void run() {
while (true) {
// 给这段代码加锁,当有线程执行当时候锁上,其他线程等待,执行完毕后,哪个线程抢到哪个线程执行。
synchronized(i) {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "开始出票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("剩余" + count);
} else
break;
}
}
}
}
同步方法
class Ticket implements Runnable {
private int count = 10;
@Override
public void run() {
while (true) {
if (!sale())
break;
}
}
private synchronized boolean sale() {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "开始出票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("剩余" + count);
return true;
}
return false;
}
}
显式锁
class Ticket implements Runnable {
private int count = 10;
// 显示锁对象
private final Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 获得锁
lock.lock();
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "开始出票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("剩余" + count);
} else {
break;
}
// 释放锁
lock.unlock();
}
}
}
其中,synchronized是隐式锁,Lock是显示锁,显示锁和隐式锁的区别就在于需不需要使用者手动获取锁和释放锁。隐式锁在使用完后系统会自动释放锁,非逻辑问题不会出现死锁,而显示锁如果用完了没有释放锁,会导致出现死锁的情况。
公平锁和非公平锁
在使用显示锁的时候可以传一个参数fair,true的时候就是公平锁,false就是不公平锁,公平锁就是多个线程排队执行,非公平锁就是执行完后谁抢到谁执行。
// 上面三种同步锁默认都是非公平锁
Lock lock = new ReentrantLock(true) // 公平锁
Lock lock = new ReentrantLock(false) // 非公平锁
多线程通信问题
public class Main {
public static void main(String[] args) {
Food food = new Food();
Thread cook = new Thread(new Cook(food));
Thread waiter = new Thread(new Waiter(food));
cook.start();
waiter.start();
}
static class Cook implements Runnable {
private final Food food;
public Cook(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
if (i % 2 == 0) {
food.set("红烧茄子", "美味的");
} else {
food.set("奥利给", "难吃的");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Waiter implements Runnable {
private final Food food;
public Waiter(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
food.get();
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Food {
private String name;
private String taste;
public synchronized void set(String name, String taste) throws InterruptedException {
this.name = name;
Thread.sleep(100);
this.taste = taste;
try {
notifyAll();
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void get() {
System.out.printf("菜名:%s,味道%s\n", name, taste);
try {
notifyAll();
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程的状态
线程状态。 线程可以处于以下状态之一:
-
NEW
尚未启动的线程处于此状态。 -
RUNNABLE
在Java虚拟机中执行的线程处于此状态。 -
BLOCKED
被阻塞等待监视器锁定的线程处于此状态。 -
WAITING
无限期等待另一个线程执行特定操作的线程处于此状态。 -
TIMED_WAITING
正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。 -
TERMINATED
已退出的线程处于此状态。
注:图片出处:https://blog.csdn.net/pange1991
Callable
这个接口类似于Runnable, 带有返回值结果。此接口有一个方法V call(), 计算结果,返回V类型, 如果无法执行,抛出异常。
Callable和Runnable的相同点
- 都是接口
- 都可以编写多线程程序
- 都采用Thread.start()启动线程
不同点
- call()方法能够抛出异常,run()不能抛出异常
- call()有返回值,run()没有返回值
获取Callable的返回值
- V get() 等待线程结束获取返回值
- V get(long timeout, TimeUnit unit) 最多等待给定的时间。
实例
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(myCallable);
new Thread(task).start();
// task.isDone() 用于判断线程是否执行完毕
System.out.printf("线程是否执行完毕: %s%n", task.isDone() ? "是" : "否");
int result = task.get(); // 这里阻塞了线程获取返回值
System.out.printf("线程是否执行完毕: %s%n", task.isDone() ? "是" : "否");
System.out.printf("返回的结果是: %d%n", result);
System.out.println("程序结束");
// 还可以使用task.cancel()来停止线程
}
static class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Thread.sleep(3000);
return 100;
}
}
}
线程池
如果并发的线程数量很多,并且每一个线程是执行一个时间很短的任务就结束了。这样频繁创建线程就会大大降低效率,因为频繁的创建线程和销毁线程需要时间。线程池就是一个容纳多个线程的容器,池中的线程可以重复使用,省去了频繁创建线程对象的操作,节省来大量的时间和资源。
线程池的好处
- 降低资源消耗
- 提高响应速度
- 提高线程的可管理性
JAVA中的四种线程池
- 缓存线程池
- 定长线程池
- 单线程线程池
- 周期性任务定长线程池
缓存线程池
/**
缓存线程池. (长度无限制) 执行流程:
1. 判断线程池是否存在空闲线程
2. 存在则使用
3. 不存在,则创建线程 并放入线程池, 然后使用
*/
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newCachedThreadPool();
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", 线程执行中");
});
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", 线程执行中");
});
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", 线程执行中");
});
// 前面缓存了三个线程
Thread.sleep(1000);
// 休眠一秒让前面的线程执行完毕
// 再来添加一个任务就会使用缓存好的线程,而不是新线程
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", 线程执行中");
});
}
}
执行结果图:
定长线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
/**
* 定长线程池.
* (长度是指定的数值) * 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*/
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", 线程执行中");
});
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", 线程执行中");
});
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", 线程执行中");
});
Thread.sleep(1000);
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", 线程执行中");
});
}
}
执行结果图:
定长线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
/**
* 单线程线程池. 执行流程:
* 1. 判断线程池 的那个线程 是否空闲
* 2. 空闲则使用
* 4. 不空闲,则等待 池中的单个线程空闲后 使用
*/
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", 线程执行中");
});
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", 线程执行中");
});
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", 线程执行中");
});
Thread.sleep(1000);
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", 线程执行中");
});
}
}
执行结果图:
周期任务定长线程池
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Main {
/**
* 周期任务 定长线程池.
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程 *
* 周期性任务执行时:
* 定时执行, 当某个时机触发时, 自动执行某任务 .
*/
public static void main(String[] args) throws InterruptedException {
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 1. 定是执行一次
* 参数1: 定时执行的任务
* 参数2: 时长数字
* 参数3: 时长数字的时间单位,TimeUnit的常量指定
*/
service.schedule(() -> {
System.out.println(Thread.currentThread().getName() + ", 5秒后执行的任务");
} ,5, TimeUnit.SECONDS);
/**
* 周期性执行任务
* 参数1: 任务
* 参数2: 延迟时长数字
* 参数3: 周期时长数字
* 参数4: 时长数字单位
*/
service.scheduleAtFixedRate(() -> {
System.out.println(Thread.currentThread().getName() + ", 五秒后执行,每隔一秒执行一次");
}, 5, 1, TimeUnit.SECONDS);
}
}
执行结果图:
线程池可以调用shutdown()关闭线程池。