认识线程
线程的启动方式以及应用场景
线程的状态和常用方法
线程的优先级 提升任务的响应速度
线程间通讯 (子线程与主线程 发消息)多线程开发
线程安全 (关键字synchornized 锁 , 原子类, 并发容器)
线程流程控制线程池原理
任务调度
复用原理多线程优化
线程池
并发安全
kotlin(协程)
线程与进程
分类:ui线程 工作线程
5种线程创建方式
-
new Thread (2种创建方法)
缺点: 却反统一管理,可能无限制新建线程,相互之间竞争,可能占用过多系统资源导致OOM 或者死机.
public class ThreadDemo {
public static void main(String[] args) {
Thread t1 = new MyThread();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程2 =" + i);
}
}
});
t1.start();
t2.start();
}
static class MyThread extends Thread {
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
System.out.println("线程1 = " + i);
}
}
}
}
-
AsyncTask (3种写法)
于是Android 提供了 AsyncTask, 轻量级任务工具类,提供任务执行的进度回调到UI线程,
场景:如果需要知道任务执行的进度,多个任务串行
缺点:生命周期和宿主的生命周期不同不,有可能会导致内存泄漏,默认情况下所有任务都是串行执行。
解决方法 可以加上static不持有宿主的对象,如果程序不能并发执行,就指定一个线程池.
public class AsyncTaskDemo extends AsyncTask<String,Integer,String> {
@Override
protected String doInBackground(String... params) {
//TODO 。。。。。
publishProgress(**);
return params[0];
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(s);
// 拿到结果 result
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
// 拿到进度 , values[0];
}
}
main(){
AsyncTaskDemo asyncTask = new AsyncTaskDemo();
// #1, 默认串行执行,可以感知任务进度
asyncTask.execute( params );
// 可以修改为并发执行
asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
// #2,如果不想知道任务执行进度,可以直接创建
AsyncTask.execute(new Runnable() {
@Override
public void run() {
// ......
}
});
//# 3,直接创建 并发执行
AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
}
});
}
-
HandlerThread
使用于主线程需要和工作线程通信,适用于持续性任务,比如轮询,所有任务串行执行。
缺点: 不会像普通线程一样主动销毁资源,会一直运行,所以可能会造成内存泄漏。
private void handlerThread() {
// 创建一个子线程
HandlerThread thread = new HandlerThread("current-thread");
thread.start();
/**
* 在主线程中新建一个Handler,持有 Thread 的 looper
Looper.
* 现在是主线程中的Handler 持有了一个 子线程的 Looper
*/
MHandler handler = new MHandler(thread.getLooper());
// handler 发送数据 ,就是主线程 向子线程发送消息
handler.sendEmptyMessage(1);
}
static class MHandler extends Handler {
public MHandler(@NonNull Looper looper) {
super(looper);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
// 因为 Handler 持有了子线程的 Looper,所以此时实在子线程当中。
Log.e("MHandler", "message = " + msg.what);
}
}
如果
MHandler handler = new MHandler(); // 没有传递Looper
handler.sendEmptyMessage(1);
// 因为主线程中创建 Handler 时会有个默认的 Looper
//这时捕获消息的就是再主线程 中.
-
IntentService
继承与Service ,在创建IntentService时,onCreate()内部会创建一个子线程来完成耗时操作
应用场景: 适用于任务需要夸页面读取任务执行的进度、结果。比如后台上传图片,批量操作数据库等。任务执行完成后,就会自我结束,不需要手动 stopService()(与Service的区别)
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
String value = intent.getStringExtra("key");
Log.e("MyIntentService", "传递 key = " + value);
}
}
}
main 中调用
Intent intent = new Intent(this, MyIntentService.class);
intent.putExtra("key", "value");
startService(intent);
-
ThreadPoolExecutor
使用与快速处理大量耗时较短的任务场景。
Executors.newCachedThreadPool(); // 线程可复用的线程池
Executors.newFixedThreadPool(); // 固定线程数量的线程池
Executors.newScheduledThreadPool(); // 可指定定时任务的线程池
Executors.newSingleThreadExecutor(); // 线程数量为1 的线程池.
线程的优先级
一般而言,线程的优先级越高,获得Cpu片的概率越大。
/**
* Jdk 提供的 , [1,10] 数字越大,优先级越高, UI线程的 优先级 为 5 ;
*
*/
Thread thread = new Thread();
thread.setPriority(1);
/**
* Android API , 优先级更精细的划分为 [-20,19],数字越小,优先级越高, UI线程的 优先级为 -10.
* 效果较为明显
* 一般把耗时较长的线程的优先级设置较低级别,
* 耗时较短较频繁的线程的优先级设置较高级别. 但是不要高于UI 线程.
*/
android.os.Process.setThreadPriority();
线程的状态及常用方法
wait()/notify()
当前线程进入等待状态,并且释放资源对象锁,可使用notify(),notifyAll() 或者等待超时来唤醒.
使用与多线程同步,一个线程要等待另一个线程的结果或者部分结果。 注意 wait - notify 的执行顺序
final Object object = new Object();
class Runnable1 implements Runnable {
@Override
public void run() {
Log.e("Runnable", "thread1 - start");
synchronized (object) {
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.e("Runnable", "thread1 - end");
}
}
class Runnable2 implements Runnable {
@Override
public void run() {
Log.e("Runnable", "thread2 - start");
synchronized (object) {
object.notify();
}
/**
* 线程2 notify()唤醒了线程1 ,
* 如果线程2有耗时操作,就先执行线程1 了.
*/
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
Log.e("Runnable", "thread2 - end");
}
}
new Thread(new Runnable1()).start();
new Thread(new Runnable2()).start();
以上, object就是对象锁, 这里如果换成 this , 跟object有啥不同吗。 wait()的使用是要在 synchornized() 同步代码块里面执行的.
理想状态下,线程1 执行了 wait()以后使当前线程进入等待状态,于是线程2拿到Cpu片开始执行,然后通过notify()唤醒线程1。
但是实际情况,线程1 和线程2 谁先执行可不知道,如果线程2先执行了,轮到线程1执行时,到了wait() 就没有唤醒了出现假死现象。
为了规避这种情况,可以定义一个 volatile boolean hasNotify = false, 线程2 notify() 以后 hsNotify = true ; 在 线程1 wait()前判断 if(!hasNotify){ wait() } , 或者 wait(1000), 自动唤醒,可以避免线程 一直 wait();
join()
在当前线程中插入一条任务,当任务完成以后线程才可以继续执行.
class JoinThread extends Thread {
@Override
public void run() {
super.run();
Log.e("JoinThread", "run: 1 " + System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("JoinThread", "run:2 " + System.currentTimeMillis());
}
}
JoinThread thread = new JoinThread();
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("JoinThread", "join: 3 " + System.currentTimeMillis());
}
执行顺讯 就是 1,2,3
UI线程中插入了一个 JoinThread,UI线程要等JoinThread执行完毕以后才接着执行。
yield()
不咋的用
暂停当前正在执行的线程对象,不会释放资源锁。
sleep()
使调用线程进入休眠状态,但在一个synchornized块中执行sleep,线程虽然休眠,但不会释放资源对象锁.
final Object object = new Object();
class Runnable1 implements Runnable {
@Override
public void run() {
Log.e("Runnable", "thread1 - start");
synchronized (object) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.e("Runnable", "thread1 - end");
}
}
class Runnable2 implements Runnable {
@Override
public void run() {
synchronized (object) {
Log.e("Runnable", "thread2 - start");
Log.e("Runnable", "thread2 - end");
}
}
}
new Thread(new Runnable1()).start();
new Thread(new Runnable2()).start();
线程1 和线程2 的执行顺序不保证,如果线程1先执行,线程2会等线程1全部执行完才执行.
线程间通信
子线程发主线程发送消息
post() ,runOnUiThread()...
主线程向子线程发送消息
/**
* 要让主线程给子线程发送消息, 必然要使子线程有处理消息的能力 ,Looper
*/
class LooperThread extends Thread {
Looper looper;
public LooperThread(String name) {
super(name);
}
// 但是由于 Thread 可能不会一 start 就执行 run(),
// 所以 looper可能为空
// 于是 wait();
public Looper getLooper() {
if (looper == null && isAlive()) {
synchronized (this) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return looper;
}
@Override
public void run() {
Looper.prepare();
looper = Looper.myLooper();
synchronized (this) {
notify();
}
// 开启Looper 的无线循环;
Looper.loop();
}
}
LooperThread looperThread = new LooperThread("Looper - Thread");
looperThread.start();
// 创建的 Handler 持有了子线程的Looper, handler 就会捕获有子线程中Looper分发的消息.
Handler handler = new Handler(looperThread.getLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Log.e("ThreadMessage", " message = " + msg.what);
Log.e("ThreadMessage", "thread - name: " + Thread.currentThread().getName());
}
};
handler.sendEmptyMessage(2);
线程安全
(买票)
线程安全的本质:
能够让并发线程,有序的运行(有序可能是先来后到的排队,也可能是有人插队,但同一时刻只能有一个线程有权访问同步数据), 线程执行的结果,能够对其他线程可见。
AutomicInteger 原子类
实现无锁数据更新,自旋( do-while 循环 ,线程不会阻塞) 的设计能够有效避免线程阻塞-唤醒的系统资源开销。 客户端并发量不会很高.
适用于多线程技术,原子操作,并发量小的场景。
volatile 可见性修饰
volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存重新读取该成员的值,而且,当成员变量值发生变化时,强迫将变化的值重新写入共享内存
不能解决非原子操作的线程安全性,性能不及原子类高。
volatile int count;
private void increment() {
// 其他线程可见.
count = 5;
// 以下两步操作都不是一步操作,是非原子操作,所以不可被其他线程可见.
count = count + 1;
count++;
}
对比两种方式
public class AtomicDemo {
public static void main(String[] args) {
final AtomicTask task = new AtomicTask();
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
task.atoAdd();
task.volatileCount();
}
}
};
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("原子类操作的结果 = " + task.atomicInteger.get()); // 结果 20000
System.out.println("volatile 操作的结果 = " + task.count); // 结果 18720
}
static class AtomicTask {
AtomicInteger atomicInteger = new AtomicInteger();
volatile int count = 0;
public void atoAdd() {
// 增量 ,先 加1,然后返回操作之前的值.
atomicInteger.getAndIncrement();
// 减量 ,先获取未更改前的值,然后 减1
//atomicInteger.getAndDecrement();
}
public void volatileCount() {
count++;
}
}
}
以上,在输出结果中表现 使用volatile 修饰的变量 不保证线程安全。
synchornized
锁java对象, 锁Class类对象,锁代码块.
- 锁方法 , 加在方法上,为获取到对象锁的其他线程不可以访问该方法
public class ThreadDemo {
static ArrayList<String> tickets = new ArrayList<>();
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
tickets.add("票 -" + (i + 1));
}
saleTicket();
}
private static void saleTicket() {
final SynchronizedDemo demo = new SynchronizedDemo();
for (int i = 0; i < tickets.size(); i++) {
new Thread(new Runnable() {
@Override
public void run() {
demo.buyTicket();
}
}).start();
}
}
static class SynchronizedDemo {
synchronized void buyTicket() {
String name = Thread.currentThread().getName();
System.out.println("买票人:" + name + " 已经准备好");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("买票人 :" + name + " 买到的票是 " + tickets.remove(0));
}
}
}
========== 输出结果 ============
买票人:Thread-0 已经准备好
买票人 :Thread-0 买到的票是 票 -1
买票人:Thread-4 已经准备好
买票人 :Thread-4 买到的票是 票 -2
买票人:Thread-3 已经准备好
买票人 :Thread-3 买到的票是 票 -3
买票人:Thread-2 已经准备好
买票人 :Thread-2 买到的票是 票 -4
买票人:Thread-1 已经准备好
买票人 :Thread-1 买到的票是 票 -5
以上, 针对同一个 买票人对象,在butTicket()方法上加 synchronized就可以保证线程安全. 如果在多线程中访问不同对象的同步方法,就不能保证线程安全了.
因为线程同步是对同一个对象而言.
比如这样, 每个线程中都创建一个买票人对象,
for (int i = 0; i < tickets.size(); i++) {
new Thread(new Runnable() {
@Override
public void run() {
final SynchronizedDemo demo = new SynchronizedDemo();
demo.buyTicket();
}
}).start();
}
========== 输出结果 ============
买票人 :Thread-3 买到的票是 票 -2
买票人 :Thread-0 买到的票是 票 -1
买票人 :Thread-2 买到的票是 票 -4
买票人 :Thread-1 买到的票是 票 -3
买票人 :Thread-4 买到的票是 票 -1
========= == 购买的票出现了重复 =====
-
锁Class 对象, 加载 static 方法上相当于给Class对象加锁,哪怕是不同的Java对象实例,也需要排队等候.
如果 synchronized 加在 static方法上,线程安全
static class SynchronizedDemo {
synchronized static void buyTicket() {
String name = Thread.currentThread().getName();
System.out.println("买票人:" + name + " 已经准备好");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("买票人 :" + name + " 买到的票是 " + tickets.remove(0));
}
}
=========== 输出结果 ==========
买票人:Thread-0 已经准备好
买票人 :Thread-0 买到的票是 票 -1
买票人:Thread-4 已经准备好
买票人 :Thread-4 买到的票是 票 -2
买票人:Thread-3 已经准备好
买票人 :Thread-3 买到的票是 票 -3
买票人:Thread-2 已经准备好
买票人 :Thread-2 买到的票是 票 -4
买票人:Thread-1 已经准备好
买票人 :Thread-1 买到的票是 票 -5
以上,static加锁,相当于给Class类加锁, 在内存中只有一个,即使不同的买票人对象来访问,也是可以保证线程安全的.
- 锁代码块,获取到对象锁的线程可以执行同步代码块以外的代码.
void buyTicket() {
String name = Thread.currentThread().getName();
synchronized (this) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("买票人 :" + name + "正在买票");
}
System.out.println("买票人 :" + name + " 买到的票是 " + tickets.remove(0));
}
================ 输出结果 ===================
买票人 :Thread-1正在买票
买票人 :Thread-1 买到的票是 票 -1
买票人 :Thread-3正在买票
买票人 :Thread-3 买到的票是 票 -2
买票人 :Thread-4正在买票
买票人 :Thread-4 买到的票是 票 -3
买票人 :Thread-2正在买票
买票人 :Thread-2 买到的票是 票 -4
买票人 :Thread-0正在买票
买票人 :Thread-0 买到的票是 票 -5
以上 如果 synchronized (SynchronizedDemo.class){ .........买票...... }
能保证线程同步.
synchronized 优劣势
优势:可以规避死锁,即使同步代码快中出现了异常,jvm也能自动释放锁。
劣势: - 必须要获取锁对象的线程执行完或者异常才可以释放锁,不能中断
- 多个线程获取锁 是否成功也不知道,不够灵活.
- 不能设置超时。
ReentrantLock锁
void lock() ; 获取不到会阻塞
boolean tryLock() ; 尝试获取锁,获取到锁返回 true,
基本用法 :
public class ReentrantLockDemo {
public static void main(String[] args) {
final ReentrantTask task = new ReentrantTask();
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
task.buyTicket();
}
}).start();
}
}
static class ReentrantTask {
ReentrantLock lock = new ReentrantLock();
void buyTicket() {
String name = Thread.currentThread().getName();
try {
lock.lock();
System.out.println(name + " :准备买票");
Thread.sleep(100);
System.out.println(name + " : 票买好了");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
=============== 输出结果 ============
Thread-0 :准备买票
Thread-0 : 票买好了
Thread-1 :准备买票
Thread-1 : 票买好了
Thread-2 :准备买票
Thread-2 : 票买好了
Thread-3 :准备买票
Thread-3 : 票买好了
Thread-4 :准备买票
Thread-4 : 票买好了
Thread-5 :准备买票
Thread-5 : 票买好了
可重入,避免死锁
在不释放锁的情况下,重复获取锁的对象.
try {
lock.lock();
System.out.println(name + " :准备买票");
Thread.sleep(100);
System.out.println(name + " : 票买好了");
lock.lock();
System.out.println(name + " :准备买票");
Thread.sleep(100);
System.out.println(name + " : 票买好了");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
lock.unlock();
}
============== 输出结果 ==============
Thread-0 :准备买票
Thread-0 : 票买好了
Thread-0 : 又准备买票
Thread-0 : 票又买好了
Thread-1 :准备买票
Thread-1 : 票买好了
Thread-1 : 又准备买票
Thread-1 : 票又买好了
Thread-2 :准备买票
Thread-2 : 票买好了
Thread-2 : 又准备买票
Thread-2 : 票又买好了
Thread-3 :准备买票
Thread-3 : 票买好了
Thread-3 : 又准备买票
Thread-3 : 票又买好了
Thread-4 :准备买票
Thread-4 : 票买好了
Thread-4 : 又准备买票
Thread-4 : 票又买好了
公平锁与非公平锁
公平锁 : 所有进入阻塞队列的线程依次有机会执行.
(默认)非公平锁:允许线程插队,避免每一个线程都阻塞再被唤醒,性能较高。但是因为可以插队的原因,会导致有的线程一直得不到执行.
ReentrantLock lock = new ReentrantLock( true / false );
简单模拟多线程打印功能
public class ReentrantDemo2 {
public static void main(String[] args) {
final PrintTask task = new PrintTask();
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
task.print();
}
}).start();
}
}
static class PrintTask {
private ReentrantLock lock = new ReentrantLock(true);
void print() {
String name = Thread.currentThread().getName();
try {
lock.lock();
System.out.println(name + ": 第一次打印");
Thread.sleep(1000);
lock.unlock();
lock.lock();
System.out.println(name + ": 第二次打印");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
如果 公平锁
输出结果
Thread-0: 第一次打印
Thread-1: 第一次打印
Thread-2: 第一次打印
Thread-3: 第一次打印
Thread-4: 第一次打印
Thread-0: 第二次打印
Thread-1: 第二次打印
Thread-2: 第二次打印
Thread-3: 第二次打印
Thread-4: 第二次打印
如果非公平锁
Thread-0: 第一次打印
Thread-0: 第二次打印
Thread-1: 第一次打印
Thread-1: 第二次打印
Thread-2: 第一次打印
Thread-2: 第二次打印
Thread-3: 第一次打印
Thread-3: 第二次打印
Thread-4: 第一次打印
Thread-4: 第二次打印
以上,表明公平锁释放锁以后,每个线程获取到锁的机会是均等的, 如果是非公平锁,当第一次打印完释放锁以后,因为其他线程的状态都是阻塞的,重新唤醒需要有一定的资源消耗,而当前线程释放掉以后还没有进入阻塞状态,由当前线程来获取锁对象是开销最小的。
应用场景:
公平锁:交易
非公平锁 :synchornized, 场景就很多了.
ReentrantLock- Condintion对象
使用 await - singnal 可以指定唤醒一个(或一组)线程。
共享锁 & 排他锁 (ReentrantReadWriteLock)
共享锁,所有线程都可同时获得,并发量高,比如在线查看文档
排他锁,同一时刻只能有一个线程有权修改资源,比如在线文档编辑.
如何正确的使用锁和原子类
一般情况下 synchronized 已经足够使用,
如果需要知道锁的细节,就要使用Lock
调优
- 减少持锁的时间,比如synchronized 同步代码快,非持有锁的线程可以执行同步代码块之外的程序,可以减少线程等待时间.
- 锁粗化 , 如果多次同步代码块之间有一部分耗时很短不用同步的代码,可以将这些程序合并为一次同步.
- 在并发量不大的情况下,可以考虑使用原子类。
线程池
为什么引入线程池
- 降低资源消耗 : 通过重复利用已创建的线程降低线程创建和销毁的造成的消耗。
- 提高响应速度: 当任务到达时,任务可以不用等待线程的创建就可以立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会造成系统资源的浪费,还会影响系统的稳定性,使用线程池统一分配,调优和监控.