上万字肝爆《多线程与Android线程性能优化》

本文深入讲解了Android线程的基础概念,包括线程与进程的区别、并行与并发的区别及应用场景,详细介绍了Java线程的创建与停止方法,探讨了线程间的同步问题,如锁机制与死锁现象,并给出了生产者消费者模型的实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、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 高并发编程好处

  1. 充分利用CPU的资源
  2. 加快响应用户的时间
  3. 可以使你的代码模块化,异步化,简单化

2.5.2 高并发编程注意事项

  1. 线程之间的安全性
  2. 线程之间的死循环过程
  3. 线程太多了会将服务器资源耗尽形成死机、当机

三、认识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());
        }
    }
}

通过以上代码,我们查看到了当前线程的信息:
在这里插入图片描述

线程名作用
mainmain线程,用户程序入口
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在抛出异常的时候,捕获异常之前,就已经清除。

写在后面的话

关于线程池这一块还没有介绍,会在后续的博客中呈现。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值