Java多线程相关的一些知识
1 创建多线程的方法
创建多线程的方法有3种:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
1.1 继承Thread类实现多线程
继承Thread类,实现主线程main和子线程同时运行:
public class ANewThread extends Thread{
public ANewThread(String name){
super(name);
}
@Override
public void run() {
while(true){
System.out.println(this.getName() + " 运行中");
}
}
public static void main(String[] args){
ANewThread aNewThread = new ANewThread("子线程");
aNewThread.start();
while (true) {
System.out.println("main线程 运行中");
}
}
}
1.2 实现Runnable接口实现多线程
实现Runnable接口,实现主线程main和子线程同时运行:
public class ANewThread implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("子线程运行中");
}
}
public static void main(String[] args) {
new Thread(new ANewThread()).start();
while (true) {
System.out.println("主线程运行中");
}
}
}
1.3 实现Callable接口实现多线程
实现Callable接口,实现主线程main和子线程同时运行:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ANewThread implements Callable {
@Override
public Object call() throws Exception {
Thread.sleep(2000);
return "hello";
}
public static void main(String[] args) {
FutureTask<String> futureTask = new FutureTask<String>(new ANewThread());
new Thread(futureTask).start();
try {
String result = futureTask.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
实现Callable接口和其它两种方法的区别是:
- Callable可以返回类型
- Callable能够抛出checked exception
- 未列出
2 线程终止的方法
线程终止的方法:
- 正常终止:main/run方法结束
- 非正常终止:抛出异常
- 受控终止:
- 使用thread.
stop(弃用)方法 - 使用thread.interrupt()方法
- 使用boolean标志位终止线程(加上volatile)
- 使用thread.
使用boolean停止标志位终止线程(加上volatile)的方法终止线程,为什么要加上volatile?
- 假设具有终止标志的线程为线程A,当其它线程希望线程A终止时,修改停止标志位为true(初始为false),该修改希望立即被线程A观测到。
- 当加上volatile后,线程A每次查看标志位时,都不能从线程本地内存中读取,必须到主内存中读取,于是可以立即响应终止请求。
3 volatile关键字
volatile关键字的功能:
- 保证变量可见性
- 禁止指令重排序
volatile如何实现保证变量可见性?
在Java内存模型JMM中,将内存分为两大块:主内存(线程公有),工作内存(线程私有)。
对被volatile修饰的变量:
- 读操作之前加入内存屏障,可理解为清除该变量在线程工作内存中的值,从而使得线程必须去主内存中取该值
- 写操作之后加入内存屏障,可理解为将要清除该变量在线程工作内存中的值,从而使得线程必须将值存入到主内存中
4 synchronized关键字
4.1 synchronized关键字和锁
synchronized关键字的功能:
- 保证被修饰的代码块同一时间内只有一个线程可以执行
synchronized关键字的功能是如何实现的?
使用“锁”机制实现。每个Java对象都有一个锁(堆内存中对象头部的一部分),所有线程访问一个对象,都必须获得这个对象的锁。
synchronized将一个对象的锁赋予给一个代码块,让线程访问该代码块的时候必须先获得该对象的锁,从而保证这段代码块在同一时间内只有一个线程可以执行。
synchronized获取不同的锁的类型:
- 对象锁 - (使用对象锁监视一个代码块)
- 类锁 - (本质上也是对象锁,使用本类class对象的锁监视本类)
- 方法锁 - (调用该方法的实例变量对象的锁)
4.2 Java中的锁
Java中的锁有两种:
- synchronized锁
- Lock锁(ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock)
synchronized锁和Lock锁都是是可重入锁,互斥锁。
ReentrantLock默认为非公平锁,但可设置为公平锁。
可重入锁:
- 获得锁的线程内部可以分享锁
- 例子:
- 类结构
- 类的同步方法A
- 调用同步方法B
- 类的同步方法B
- 执行一些操作
- 类的同步方法A
- 当调用同步方法A时,如果锁的类型不是可重入锁,则会导致同步方法B不能获得锁,造成死锁。
- 类结构
非公平锁:
不按照锁的申请顺序来分配锁,如果锁刚空闲时恰好有一个线程来申请锁,就把锁给那个线程。