线程简介
1.进程和线程
- 程序:存放在磁盘中的可执行文件
- 进程:当运行一个程序时,操作系统就会创建一个进程。
- 线程:线程就是进程内部的程序流,也就是说操作系统内部支持多进程的,而每个进程的内部又是支持多线程的,线程是轻量的,新建线程会共享所在进程的系统资源
- 多线程:采用时间片轮转法来保证多个线程的并发执行,所谓并发就是指宏观并行微观串行的机制
线程的使用
- java.lang.Thread类代表线程,任何线程对象都是Thread类(子类)的实例
1.创建线程
- 继承
Thread
类并重写run
方法,然后创建该类的对象调用start
方法。
public class ExtendsThread extends Thread {
@Override
public void run() {
System.out.println("用Thread类实现线程");
}
}
- 实现
Runnable
接口并重写run
方法,构造Thread
对象作为参数传入,然后调用start
方法。
public class RunnableThread implements Runnable {
@Override
public void run() {
System.out.println("用实现Runnable接口实现线程");
}
}
继承Thread类的方式代码简单,但是若该类继承Thread类后则无法继承其它类,而实现 Runnable接口的方式代码复杂,但不影响该类继承其它类以及实现其它接口,因此以后的开发中 推荐使用第二种方式
2.启动线程
- main方法是程序的入口,当start方法调用成 功后线程的个数由1个变成了2个。主线程继续向下执行,两 个线程各自独立运行互不影响
public static void main(String[] args) {
// 方式一
Thread t1 = new ExtendsThread("ExtendsThread");
t1.start();
// 方式二:也可以通过匿名内部类来传入Runnable实现类
Thread t2 = new Thread(new RunnableThread(), "RunnableThread");
t2.start();
}
3.关闭线程
- 通过interrupt停止线程
public class StopThread implements Runnable {
@Override
public void run() {
int count = 0;
while (!Thread.currentThread().isInterrupted() && count < 1000) {
System.out.println("count=" + count++);
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopThread());
thread.start();
Thread.sleep(500);
thread.interrupt();
}
- 线程阻塞停止线程
通过抛出异常终止线程
public class StopThread implements Runnable {
@Override
public void run() {
try {
while (true) {
subTask();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
void subTask() throws InterruptedException {
int count = 0;
while (!Thread.currentThread().isInterrupted() && count < 1000) {
System.out.println("count=" + count++);
Thread.sleep(1000);
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopThread());
thread.start();
Thread.sleep(500);
thread.interrupt();
}
当发出了中断请求,清除了中断信号,可以通过重新设置中断信号进行中断线程
public class StopThread implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
subTask();
}
}
void subTask() {
try {
int count = 0;
while (!Thread.currentThread().isInterrupted() && count < 1000) {
System.out.println("count=" + count++);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopThread());
thread.start();
Thread.sleep(500);
thread.interrupt();
}
- volatile标记位停止线程(不建议使用):当volatile标记位发生改变时,线程处于休眠状态时是无法跳出while循环的,这样就会出现线程无法退出的情况
线程的生命周期
- 状态说明
| 状态名称 | 说明 |
| —- | —- |
| NEW | 初始状态,线程被构建,但是还没有调用
start()
方法 | | RUNNABLE | 运行状态,Java线程将就绪和运行两种状态统称“运行中” | | BLOCKED | 阻塞状态,表示线程获取锁时阻塞 | | WAITING | 等待状态,线程进入等待状态,需要等待其他线程做出动作 | | TIME_WAITING | 超时等待状态,带有超时时间的等待状态 | | TERMINATED | 终止状态,表示线程执行完毕 |
- 状态图
Java线程状态
线程常用方法
1.获取线程信息
Thread thread = new Thread(new RunnableThread(), "RunnableThread");
// 获取线程ID
long id = thread.getId();
// 获取线程名称
String name = thread.getName();
// 修改线程名称
thread.setName("RenameThread");
// 获取当前线程实例
Thread thread1 = Thread.currentThread();
2.让出处理器资源
- 让线程让出处理器资源进入排队
Thread.yield();
3.线程休眠
- 使当前线程休眠1秒
Thread.sleep(1000);
4.线程优先级
- 设置线程优先级为10
Thread thread = new Thread(new RunnableThread(), "RunnableThread");
int priority = thread.getPriority();
thread.setPriority(10);
- 获取线程优先级
thread.getPriority();
5.线程等待
- 当子线程thread结束时才会继续执行下面的代码
Thread thread = new Thread(new RunnableThread(), "RunnableThread");
thread.start();
// 等待子线程执行完毕
thread.join();
// 等待子线程1秒后执行
// thread.join(1000);
6.Deamon线程
- 当子线程是守护线程,主线程结束时子线程也会结束
Thread thread = new Thread(new RunnableThread(), "RunnableThread");
thread.setDaemon(true);
- 获取线程是否是守护线程
thread.getDaemon();
synchronized关键字
1.基本介绍
- synchronized关键字可以修饰方法或者以同步块的形式来进行使用
- synchronized关键字主要确保多个线程在同一时间,只能有一个线程处于方法或者同步块中
- synchronized关键字保证了线程对变量访问的可见性和排他性
2.使用
- 同步当前变量的引用
public synchronized void syncMethod() {
}
// 等价于
public void syncMethod() {
synchronized (this) {
}
}
- 同步当前类
public synchronized static void syncMethod() {
}
// 等价于
public void syncMethod() {
synchronized (MySync.class) {
}
}
线程安全
1.线程安全场景
- 访问共享变量或资源:多个线程同时读取写入同一个共享变量,会导致读写不一致问题
- 依赖时序的操作:两个线程同时删除一个数据,一个线程率先删除了,另一个线程则会报错
- 不同数据之间存在绑定关系:两条需要绑定的数据只改变了一条,导致数据匹配不上
- 对方没有声明自己是线程安全(synchronized)的:比如ArrayList不是线程安全的类
- Collections.synchronizedList() 和 Collections.synchronizedMap()等方法实现安全
2.三种线程安全问题
- 运行结果错误:由并发读写造成的
- 发布和初始化导致线程安全问题:没有在正确的时间发布和初始化
活跃性问题
- 死锁:线程由于争夺导致系统无法继续运行
- 活锁:消息队列出现了错误数据无限重试机制
- 饥饿:Java线程优先级比较低,导致可能始终分配不到CPU资源
3.死锁
- 死锁Demo
public class DeadLockDemo {
private static String A = "A";
private static String B = "B";
public static void main(String[] args) {
new DeadLockDemo().deadLock();
}
private void deadLock() {
Thread t1 = new Thread(() -> {
synchronized (A) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B){
}
}
});
Thread t2 = new Thread(() -> {
synchronized (B) {
synchronized (A){
}
}
});
t1.start();
t2.start();
}
}
Lock锁
1.常用方法
方法声明 | 功能介绍 |
---|---|
void lock() | 获取锁,调用该方法当前线程将会获取锁 |
void lockInterruptibly() | 可中断的获取锁,可以响应中断信号 |
boolean tryLock() | 非阻塞的获取锁,调用后返回是否获取到了锁 |
boolean tryLock(long time, TimeUnit unit) | 有超时时间的非阻塞获取锁 |
void unlock() | 释放锁 |
Condition newCondition | 获取等待组件,组件与当前锁绑定,线程只有获取到了锁才能调用该组件的wait方法,调用后将释放锁 |
2.使用方式
- Lock接口是控制多个线程对共享资源进行访问的工具,主要实现类是ReentrantLock类
- finally中释放锁,目的是保证能在获取锁之后,最终能够被释放
Lock lock = new ReentrantLock();
lock.lock();
try {
}finally {
lock.unlock();
}
3.与synchronized方式的比较
- Lock是显式锁,需要手动实现开启和关闭操作,而synchronized是隐式锁,执行锁定代码后自动 释放
- Lock只有同步代码块方式的锁,而synchronized有同步代码块方式和同步方法两种锁
- 使用Lock锁方式时,Java虚拟机将花费较少的时间来调度线程,因此性能更好
等待/通知机制
wait方法必须在 synchronized 保护的同步代码中使用
wait方法会释放monitor锁,所以必须首先进入到synchronized内持有这把锁
等待/通知相关方法被定义在超类Object中
方法声明 | 功能介绍 |
---|---|
void wait() | 用于使得线程进入等待状态,直到其它线程调用notify()或notifyAll()方法 |
void wait(long timeout) | 用于进入等待状态,直到其它线程调用方法或参数指定的毫秒数已经过去为止 |
void notify() | 用于唤醒等待的单个线程 |
void notifyAll() | 用于唤醒等待的所有线程 |
生产者消费者模式
- 队列的创建
public class MyBlockingQueue {
private int maxSize;
private LinkedList<Object> storage;
public MyBlockingQueue(int size) {
this.maxSize = size;
storage = new LinkedList<>();
}
public synchronized void put(Object o) throws InterruptedException {
while (storage.size() == maxSize) {
wait();
}
storage.add(o);
notifyAll();
}
public synchronized Object take() throws InterruptedException {
while (storage.size() == 0){
wait();
}
Object o = storage.remove();
notifyAll();
return o;
}
}
这里使用while而不使用if的原因是,当线程进入等待状态被唤醒的时候,if会在被唤醒的地方接着执行下面的代码,而while会再次进行条件判断,当不满足条件时会继续进入等待状态。
- 生产者消费者创建
public static void main(String[] args) {
MyBlockingQueue<Object> queue = new MyBlockingQueue<>(10);
// 生产者
Runnable producer = () -> {
while (true) {
try {
queue.put(new Object());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
new Thread(producer).start();
// 消费者
Runnable consumer = () -> {
while (true) {
try {
queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
new Thread(consumer).start();
}
Callable接口
- 实现Callable接口
public class CallableTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return new Random().nextInt();
}
}
- 启动线程
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<Integer>(new CallableTask());
Thread thread = new Thread(futureTask);
thread.start();
Integer integer = futureTask.get();
}
FutureTask类用于描述可取消的异步计算,该类提供了Future接口的基本实 现,包括启动和取消计算、查询计算是否完成以及检索计算结果的方法,也可以用于获取方法调用 后的返回结果
更新时间:{docsify-updated}