线程总览
- Java多线程和线程同步
- Android中线程间通信的本质和原理:Handler和Ascytask,
- RxJava使用及原理
- 线程与kotlin中的协程
CPU缓存和指令重排
会造成多线程的结果不可控,即线程结果不同步
Java内存模型
Java Memroy Model即JMM 本质是一套规范happens-before,即先行发生的结果对后续是可见的
volatile和synchronized
volatile和synchronized等关键字都是实行happens-before化
线程基础
什么是线程
操作系统在运行一个程序的时候,会为其创造一个进程。例如,启动一个Java程序,操作系统就会为其创建一个Java进程。操作系统调度的最小单元就叫线程。在一个进程中可以创建多个线程,这些线程拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。
- 一个Java程序的运行,就包括了main线程和其他线程的同时运行
- 线程安全指的是一个类可以被多线程正确访问
线程的状态
一个线程被创建,只会start一次
- New 表示新建线程
- Runnable 运行中的线程,run中的方法
- Blocked 运行中的线程,被阻塞挂起
- Waiting 运行中的线程,处于等待状态中
- Timed Waiting 运行中的线程,调用了sleep正在计时等待
- Terminated 线程终止,可能时正常退出,也可能时异常终止
为什么要使用多线程
单个线程一个时刻只能运行在一个处理器核心上,而面对现在强大的多核处理器,为了充分“榨取”处理器资源,提高程序响应速度,所以要使用多线程,同时也可以将一些不太重要的业务逻辑放到其他线程。
什么是守护线程(Daemon thread)
JVM启动线程,线程都终止则JVM退出,当线程终止不了呢?那就需要守护线程来终止
通过thread.setDaemon(true)来开启守护线程
线程基本操作
如何开启一个线程
- 1)通过Runable接口实现
private static void thread() {
Thread thread = new Thread() {
@Override
public void run() {
super.run();
System.out.println("thread start");
}
};
thread.start();
}
或者
private static void run() {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("thread by runnable");
}
};
Thread thread = new Thread(runnable);
thread.start();
}
两种方式没有本质区别
- 2)通过实现callable接口,这一方式相对其他方式,有返回值
private static void callable() {
Callable<String> callable = new Callable<String>() {
@Override
public String call() {
int a = 1+2;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return a + "";
}
};
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> result = executorService.submit(callable);
try {
String resultStr = result.get();
System.out.println(resultStr);
executorService.shutdown();
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
也可以通过Executors直接开启线程,后面回讲到
join
在线程B中调用线程A.join(),指的是线程B等待线程A终止后,再执行后续任务,join()可以指定时间,比如10ms后不继续等待,直接执行线程B的任务
public static void main(String[] args) throws InterruptedException {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread1 running");
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
thread1.join(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread2 running");
}
});
System.out.println("start");
thread1.start();
thread2.start();
System.out.println("end");
}
执行结果为
start
end
thread2 running
thread1 running
yield
把自己的时间片暂时让出一段时间,不是等结束,而是下一个时间继续
死锁
两个线程持有不同的锁,都想获取彼此手中的锁,就会造成死锁。死锁的解决办法是一定要获取锁的顺序
wait/notify
- 在synchronized内部,wait用来使线程进入等待状态,此时会释放锁
- 在synchronized内部,notify/notifyall 用来唤醒线程
ReentrantLock
synchronized锁比较重,ReentrantLock可以自己控制锁的粒度,与Condition搭配可以实现Synchronized+wait+notify的操作
- await 释放锁并进入等待状态,唤醒线程从await返回后,需要重新获取锁
- singal 唤醒某个线程
- singalAll会唤醒全部线程
private static class TaskQueue {
private Queue<Integer> queue = new LinkedList<>();
private ReentrantLock lock = new ReentrantLock();
private Condition mCondition;
public TaskQueue() {
mCondition = lock.newCondition();
}
public void addTask(int value) {
lock.lock();
try {
queue.add(value);
mCondition.signalAll();
}finally {
lock.unlock();
}
}
public int getTask() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
mCondition.await();
}
return queue.remove();
}finally {
lock.unlock();
}
}
读写锁
通常多线程写数据的话容易造成数据不一致问题,而读操作不会,所以通过读写锁可以区分写少读多的情况
- ReentrantReadWriteLock只允许一个线程写入
- ReentrantReadWriteLock允许多个线程在没有写入情况下读取
- ReentrantReadWriteLock适合读多写少的情况
private int[] ints = new int[10];
private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock mReadLock;
private final ReentrantReadWriteLock.WriteLock mWriteLock;
public ReadWriteLockThread() {
mReadLock = mLock.readLock();
mWriteLock = mLock.writeLock();
}
public void write(int index) {
mWriteLock.lock();
try {
ints[index] += 1;
} finally {
mWriteLock.unlock();
}
}
public int[] read() {
mReadLock.lock();
try {
return Arrays.copyOf(ints, ints.length);
} finally {
mReadLock.unlock();
}
}
Atomic
Atomic用于基本类型数据的原子性读写,
- 原子操作通过CAS(compare and set 重点关注unsafe类)实现了无锁的线程安全
- 适用于累加器、计数器、id唯一生成等
javap 命令
javap 命令主要用来反编译java的class文件,查看java编译器生成的字节码
通过javap命令包含如下:
C:\projects\AndroidLearn\hotfix>javap
用法: javap <options> <classes>
其中, 可能的选项包括:
-help --help -? 输出此用法消息
-version 版本信息
-v -verbose 输出附加信息
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类
和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的
系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示最终常量
-classpath <path> 指定查找用户类文件的位置
-cp <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置
通过javap -c xxx.class,
线程池
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors 返回的线程池对象的弊端如下:
- 1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
- 2)CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
利用三个线程打印依次打印1A,2B,3C,1D,2E...
private static class Run implements Runnable{
@Override
public void run() {
synchronized (PrintThread.class) {
int threadNo = Integer.valueOf(Thread.currentThread().getName());
while (index < 26) {
if (index % 3 == threadNo -1) {
System.out.println(threadNo + " " + (char)(aChar++));
index++;
PrintThread.class.notifyAll();
}else {
try {
PrintThread.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}