多线程与Android线程性能优化
一、App线程的概念
二、一些基础概念
2.1 CPU核心数和线程数的关系
核心数、线程数:目前主流CPU有双核、三核和四核,六核也在2010年发布。增加核心数目就是为了增加线程数,因为操作系统是通过线程来执行任务的,一般情况下它们是1:1对应关系,也就是说四核CPU一般拥有四个线程。但 Intel引入超线程技术后,使核心数与线程数形成1:2的关系(超核心技术)
以博主的破电脑为例:打开此电脑 -> 管理 -> 设备管理器 -> 处理器
数了数,博主的电脑有8个线程,说明是4核的处理器
安卓处理器: ARM32、ARM64、x86、x64
2.2 CPU时间片轮转机制
时间片轮转调度是一种最古老、最简单、最公平且使用最广的算法,又称RR调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
时间片设得太短会导致过多的进程切换,降低了CPU效率:而设得太长又可能引起对短的交互请求的响应变差。将时间片设为100ms通常是一个比较合理的折衷。
CPU不停在几个进程中切换执行,随机执行,速度非常快,所以我们看起来就好像是三个应用在同时进行一样。
CPU的时间片轮转机制采用了RR调度算法。
2.3 什么是进程和线程
进程:操作系统进行资源分配的最小单位
线程:CPU调度的最小单位,必须依赖于进程而存在
注意:
① 进程 > 线程
② 一个进程至少一个线程或多个线程
③ 如果一个进程还有一个线程没有杀掉,进程还存活 (线程依附于进程)
进程A { 线程1,线程2,线程3 } -> 存活
进程B { 线程1 } -> 存活
进程C { } -> 挂了
2.4 澄清并行和并发
2.4.1 并行
指应用能够同时执行不同的任务。比如类似于多个跑道,多个运动员一起跑
2.4.2 并发
指应用能够交替执行不同的任务。比如10秒钟,服务器的吞吐量;比如10秒钟,多少车流量
两者区别:一个是交替执行,一个是同时执行。
2.5 高并发编程的意义、好处和注意事项
2.5.1 高并发编程好处
- 充分利用CPU的资源
- 加快响应用户的时间
- 可以使你的代码模块化,异步化,简单化
2.5.2 高并发编程注意事项
- 线程之间的安全性
- 线程之间的死循环过程
- 线程太多了会将服务器资源耗尽形成死机、当机
三、认识Java里的线程
Java里的线程天生就是多线程的,类Thread、接口Runnable、接口Callable。
有开始就有结束,stop()、interrupt()、isInterrupted()、Thread.interrupted()怎样才能让Java里的线程安全停止工作呢?
package com.swpuiot.lib;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
/**
* Time: 2021/9/17
* Author: lenovo
* Description: Java的多线程无处不在
* Finalizer Object finalize() 需要资源回收就复写该方法 把代码写这里边去
*/
public class ThreadStudy {
public static void main(String[] args) {
// 虚拟机线程管理的接口
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 取得线程信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false,false);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() + "]" + " " + threadInfo.getThreadName());
}
}
}
通过以上代码,我们查看到了当前线程的信息:
线程名 | 作用 |
---|---|
main | main线程,用户程序入口 |
Reference Handler | 清除Reference的线程 |
Finalizer | 调用对象finalize方法的线程 |
Signal Dispatcher | 分发处理发送给JVM信号的线程 |
Attach Listener | 内存dump,线程dump,类信息统计,获取系统属性等 |
Common-Cleaner | 该线程是 JDK9 之后新增的守护线程,用来更高效的处理垃圾回收 |
3.1 线程的三种启动方式
线程的启动方式有类Thread、接口Runnable、接口Callable三种。
package com.swpuiot.lib;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* Time: 2021/9/17
* Author: lenovo
* Description: 关于线程的三种启动方式
*/
public class NewThread {
// 第一种方式 线程
private static class StudentThread extends Thread{
@Override
public void run() {
super.run();
System.out.println("Do Thread!");
}
}
// 第二种方式 任务
private static class PersonThread implements Runnable{
@Override
public void run() {
System.out.println("Do Runnable!");
}
}
// 第三种方式 任务 可返回 return XXX
private static class WorkerThread implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("Do Callable!");
Thread.sleep(10*1000);
return "success";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
StudentThread thread = new StudentThread();
thread.start(); //.start() 才能证明是线程
// thread.run(); 这个和线程没有关系,就是函数调用
// 任务不能运行,需要寄托于 Thread
PersonThread personThread = new PersonThread();
new Thread(personThread).start();
// 任务不能运行,需要寄托于 Thread,有返回值
WorkerThread workerThread = new WorkerThread();
FutureTask<String> futureTask = new FutureTask<>(workerThread);
new Thread(futureTask).start();
System.out.println(futureTask.get()); // 阻塞
}
}
3.2 停止线程
stop停止线程(暴力行为),线程机制里边的碎片来不及释放,不推荐使用。
让run函数执行完毕才是最好最和谐的结束方式。
以下代码使用interrupt无法使线程停止下来
package com.swpuiot.lib;
/**
* Time: 2021/9/17
* Author: lenovo
* Description: interrupt无法使线程停下来
*/
public class EndThread {
private static class UserThread extends Thread{
@Override
public void run() {
super.run();
String name = Thread.currentThread().getName();
while(true){
System.out.println(name + " is Running!");
}
}
}
public static void main(String[] args) throws InterruptedException {
UserThread thread = new UserThread();
thread.start();
// 休眠
Thread.sleep(5*1000);
// 发起中断信号 但是是停不下来的
thread.interrupt();
}
}
采用isInterrupted() + interrupt()使线程停止下来
package com.swpuiot.lib;
/**
* Time: 2021/9/17
* Author: lenovo
* Description: 采用isInterrupted() + interrupt()使线程停止下来
*/
public class EndThread2 {
private static class UserThread extends Thread{
@Override
public void run() {
super.run();
String name = Thread.currentThread().getName();
while (!isInterrupted()){ //isInterrupted()默认是false
System.out.println(name + " is Running! " + isInterrupted());
}
System.out.println("flag:" + isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException {
UserThread thread = new UserThread();
thread.start();
// 休眠
Thread.sleep(5*1000);
thread.interrupt();
}
}
实现Runnab接口的方式:
package com.swpuiot.lib;
/**
* Time: 2021/9/17
* Author: lenovo
* Description: 采用isInterrupted() + interrupt()使线程停止下来
*/
public class EndThread3 {
private static class UserThread implements Runnable{
@Override
public void run() {
String name = Thread.currentThread().getName();
while (!Thread.currentThread().isInterrupted()){ //isInterrupted()默认是false
System.out.println(name + " is Running! " + Thread.currentThread().isInterrupted());
}
System.out.println("flag:" + Thread.currentThread().isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException {
UserThread userThread = new UserThread();
Thread thread = new Thread(userThread);
thread.start();
// 休眠
Thread.sleep(5*1000);
thread.interrupt();
}
}
3.3 对Java里的线程再多一点点认识
守护线程:
package com.swpuiot.lib;
/**
* Time: 2021/9/17
* Author: lenovo
* Description: 守护线程
*/
public class DaemonThread {
public static void main(String[] args) throws InterruptedException {
final Thread thread = new Thread(){
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "---" + i);
}
}
};
thread.setDaemon(true); // 设置了守护线程
thread.start();
Thread.sleep(5000);
// 走到这里,代表主线程结束,主线程结束不管thread线程有没有结束,thread线程都必须结束,因为thread线程是守护线程,守护了main
}
}
四、锁
4.1 内置锁(synchronized)
4.1.1 对象锁
package com.swpuiot.lib;
/**
* Time: 2021/9/17
* Author: lenovo
* Description: synchronized 内置锁
* synchronized的作用
* 对象锁
*/
public class SynTest {
private long count = 0;
private Object obj = new Object(); // 作为一个锁 对象锁
private String str = new String(); // 也可作为一个锁 对象锁
public long getCount() {
return count;
}
public void setCount(long count) {
this.count = count;
}
// count进行累加
public void incCount(){
synchronized (obj){ // 使用一把锁
count++;
}
}
// count进行累加
public synchronized void incCount2(){
count++;
}
// count进行累加
public void incCount3(){
synchronized (this){
count++;
}
}
// 线程
private static class Count extends Thread{
private SynTest simplOper;
public Count(SynTest simplOper){
this.simplOper = simplOper;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
simplOper.incCount();
}
}
}
public static void main(String[] args) throws InterruptedException {
SynTest simplOper = new SynTest();
// 启动两个线程
Count count1 = new Count(simplOper);
Count count2 = new Count(simplOper);
count1.start();
count2.start();
Thread.sleep(50);
System.out.println(simplOper.count);
}
}
4.1.2 类锁
package com.swpuiot.lib;
/**
* Time: 2021/9/17
* Author: lenovo
* Description: 类锁
*/
public class SynClzAndInst {
private static class SynClass extends Thread{
@Override
public void run() {
System.out.println("TestClass is running!");
synClass();
}
}
// 使用类锁的线程
// synchronized == 类锁 == SynClzAndInst.class的对象锁
private static synchronized void synClass(){
SleepTools.second(1);
System.out.println("synClass going!");
SleepTools.second(1);
System.out.println("synClass end!");
}
// 特殊的类锁 obj对象类锁 只有一份
private static Object obj = new Object();
private void synStaticObject(){
synchronized (obj){
SleepTools.second(1);
System.out.println("synClass going!");
SleepTools.second(1);
System.out.println("synClass end!");
}
}
}
4.2 显示锁
4.2.1 可重入锁
package com.swpuiot.lib;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Time: 2021/9/17
* Author: lenovo
* Description: 使用显示锁
*/
public class LockDemo {
private int count = 0;
// 声明一个显示锁之可重入锁
private Lock lock = new ReentrantLock(true);
public void incr(){
// 使用显示锁的规范
lock.lock();
try{
count++;
}finally {
lock.unlock();
}
}
// 可重入锁 意思就是递归调用自己,锁可以释放出来
// 如果是非重入锁,就会自己把自己锁死
// synchronized是天生的可重入锁
public synchronized void incr2(){
count++;
incr2();
}
public static void main(String[] args) {
LockDemo lockDemo = new LockDemo();
}
}
4.3 死锁
锁中锁易导致死锁。
package com.swpuiot.lib;
/**
* Time: 2021/9/17
* Author: lenovo
* Description: 死锁
*/
class DieLockThread extends Thread{
private boolean flag;
public DieLockThread(boolean flag){
this.flag = flag;
}
@Override
public void run() {
int i = 0;
int j = 0;
if(flag){
while (true){
synchronized (Lock.LOCK1){
// 使用第一把锁
synchronized (Lock.LOCK2){
// 使用第二把锁
System.out.println("----------------" + i++);
}
}
}
}else{
while (true){
synchronized (Lock.LOCK2){
// 使用第二把锁
synchronized (Lock.LOCK1){
// 使用第一把锁
System.out.println("----------------" + j++);
}
}
}
}
}
}
// 定义两把锁
class Lock{
public final static Object LOCK1 = new Object();
public final static Object LOCK2 = new Object();
}
public class DieLockDemo{
public static void main(String[] args) {
new DieLockThread(true).start();
new DieLockThread(false).start();
}
}
4.4 多线程之生产者消费者案例
package 多线程之生产者消费者案例一;
/**
* Time: 2021/9/17
* Author: lenovo
* Description: 多线程之生产者消费者案例
*/
/**
* 描述资源
*/
class Res {
// 共享数据
private String name;
private int id;
private boolean flag; // 定义标记
// 对操作共享数据的地方加入同步锁的方式来解决安全问题
public synchronized void put(String name){
if(!flag){
id += 1;
this.name = name + "id:" + id;
System.out.println(Thread.currentThread().getName() + "produce:" + this.name);
flag = true;
// 唤醒 wait()冻结的线程,如果没有就是空唤醒,Java是支持的
notifyAll(); // 注意 wait()、notify()这些必须要有同步锁包裹着
// 当前自己进程冻结,释放CPU执行资格,释放CPU执行权,CPU就会去执行其他线程了
try{
wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
// 对操作共享数据的地方加入同步锁的方式来解决安全问题
public synchronized void out(){
if(flag){
System.out.println(Thread.currentThread().getName() + ">>>>>>>>>>>>>>>>>>consume:" + this.name);
flag = false;
// 唤醒 wait()冻结的线程,如果没有就是空唤醒,Java是支持的
// 唤醒生产者
notifyAll(); // 注意 wait()、notify()这些必须要有同步锁包裹着
// 当前自己进程冻结,释放CPU执行资格,释放CPU执行权,CPU就会去执行其他线程了
try{
wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
/**
* 描述生产者任务
*/
class ProduceRunnable implements Runnable{
private Res res;
ProduceRunnable(Res res){
this.res = res;
}
/**
* 执行线程任务
*/
@Override
public void run() {
for (int i = 0; i < 20; i++) {
res.put("bread");
}
}
}
/**
* 描述消费者任务
*/
class ConsumeRunnable implements Runnable{
private Res res;
ConsumeRunnable(Res res){
this.res = res;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
res.out();
}
}
}
/**
* 多线程通讯案例
*/
public class ThreadCommunicationDemo{
public static void main(String[] args) {
// 创建资源对象
Res res = new Res();
// 创建生产者任务
ProduceRunnable produceRunnable = new ProduceRunnable(res);
// 创建消费者对象
ConsumeRunnable consumeRunnable = new ConsumeRunnable(res);
// 启动生产者任务
new Thread(produceRunnable).start();
// 启动消费者任务
new Thread(consumeRunnable).start();
}
}
4.5 ThreadLocal的使用
package com.swpuiot.lib;
/**
* Time: 2021/9/18
* Author: lenovo
* Description: ThreadLocal的使用
*/
public class ThreadLocalTest {
static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 1;
}
};
// 运行3个线程
public void StartThreadArray(){
Thread[] runs = new Thread[3];
for (int i = 0; i < runs.length; i++) {
runs[i] = new Thread(new TestThread(i));
}
for (int i = 0; i < runs.length; i++) {
runs[i].start();
}
}
// 测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,看看线程之间是否会互相影响
public static class TestThread implements Runnable{
int id;
public TestThread(int id){
this.id = id;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":start!");
Integer s = threadLocal.get();
s = s + id;
threadLocal.set(s);
System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
// threadLocal.remove();
}
}
public static void main(String[] args) {
ThreadLocalTest test = new ThreadLocalTest();
test.StartThreadArray();
}
}
五、面试题集锦
【面试题】多线程中的并行和并发是什么?
答:例如四个车道,四辆车并行的走,就是并行。四个车道中,五秒钟有多少的车流量,多少的吞吐量,就是并发。
【面试题】在Java中能不能指定CPU去执行某个线程?
答:不能,Java是做不到的,唯一能够去干预的就是C语言调用内核的API去执行才行。
【面试题】run和start的区别?
答:run是函数调用,和线程没有任何关系,start会走底层,走系统层,最终调度到run函数,这才是线程。
【面试题】如何控制线程的执行顺序?
答:join来控制,让t2获取执行权力,能够做到顺序执行。
package com.swpuiot.lib;
/**
* Time: 2021/9/17
* Author: lenovo
* Description: 顺序执行
*/
class ThreadJoinTest extends Thread{
public ThreadJoinTest(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(this.getName() + ":" + i);
}
}
}
/**
* join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是:
* 程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行知道线程t1执行完毕
* 所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会
*/
public class JoinTest{
public static void main(String[] args) throws InterruptedException {
ThreadJoinTest t1 = new ThreadJoinTest("A");
ThreadJoinTest t2 = new ThreadJoinTest("B");
t1.start();
t1.join(); // 让t2获得执行权
t2.start();
}
}
【面试题】在项目开发过程中,你会去考虑Java线程的优先级吗?
答:不会考虑线程优先级。因为线程的优先级很依赖于系统的平台,所以这个优先级无法对号入座,无法做到你想象中的优先级,属于不稳定,有风险。因为某些开源框架,也不可能依靠线程优先级来,设置自己想要的优先级顺序,这个是不可靠的。例如:Java线程优先级有十级,而此时操作系统优先级只有2~3级,那么就对应不上。
【面试题】sleep和wait有什么区别?
答:sleep是休眠,等休眠时间过了,才有执行权的资格,注意:又是有资格,并不代表马上就会被执行,什么时候又执行取决于操作系统调度。wait是等待,需要人家来唤醒,唤醒后才有执行权的资格。sleep无条件可以休眠,wait是某些原因与条件需要等待一下(资源不满足)。
【面试题】在Java中能不能强制中断线程的执行?
答:虽然提供了stop等方法,但是此方法不推荐使用,因为这种暴力的方式,很危险。例如:下载图片5kb,只下载了4kb就被中断了。我们可以使用interrupt来处理线程的停止,但是注意interrupt只是协作式的方法,并不能绝对保证中断,并不是抢占式的。
【面试题】如何让出当前线程的执行权?
答:yield方法,只在JDK某些实现才能看到,是让出执行权。
【面试题】sleep、wait,到底哪个方法才会清除中断标记?
答:sleep在抛出异常的时候,捕获异常之前,就已经清除。
写在后面的话
关于线程池这一块还没有介绍,会在后续的博客中呈现。