多线程

1 继承Thread类

image.png

2 实现Runnable接口

image.png

a.常规使用

// 步骤1:创建线程辅助类,实现Runnable接口
 class MyThread implements Runnable{
    ....
    @Override
// 步骤2:复写run(),定义线程行为
    public void run(){

    }
}

// 步骤3:创建线程辅助对象,即 实例化 线程辅助类
  MyThread mt=new MyThread();

// 步骤4:创建线程对象,即 实例化线程类;线程类 = Thread类;
// 创建时通过Thread类的构造函数传入线程辅助类对象
// 原因:Runnable接口并没有任何对线程的支持,我们必须创建线程类(Thread类)的实例,从Thread类的一个实例内部运行
  Thread td=new Thread(mt);

// 步骤5:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起  / 停止
// 当调用start()方法时,线程对象会自动回调线程辅助类对象的run(),从而实现线程操作
  td.start();

b.匿名类

    // 步骤1:通过匿名类 直接 创建线程辅助对象,即 实例化 线程辅助类
    Runnable mt = new Runnable() {
                    // 步骤2:复写run(),定义线程行为
                    @Override
                    public void run() {
                    }
                };

    // 步骤3:创建线程对象,即 实例化线程类;线程类 = Thread类;
    Thread mt1 = new Thread(mt, "窗口1");
    // 步骤4:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起  / 停止
    mt1.start();

2.1 Thread和Runnable的区别

image.png

3 Handler

image.png

image.png

3.1 Handler工作原理

工作流程

1.异步通信准备
2.消息发送
3.消息循环
4.消息处理

image.png

3.2 工作流程

image.png

image.png

3.3 使用Handler.sendMessage()

class mHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    }

    void useHandler() {
        Handler mHandler = new mHandler();
        
        Message message = Message.obtain();
        message.what = 1;
        message.obj = "AA";
        
        mHandler.sendMessage(message);
   }

3.4 使用Handler.post

void useHandler() {
        Handler mHandler = new mHandler();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                
            }
        });
    }

3.5 具体类

image.png

4 AsyncTask

属于抽象类 使用时要实现子类

4.1 作用

1 实现多线程
在工作线程中执行任务
2 异步通信 消息传递
实现工作线程和主线程直接的通信

4.2 优点

  • 方便实现异步通信
    不需要使用 "任务线程 + Handler" 的复杂组合
  • 节省资源
    采用线程池的缓存线程 + 复用线程 避免了频繁创建和销毁线程所带来的系统资源开销

4.3 类和方法

4.3.1 类定义
public abstract class AsyncTask<Params, Progress, Result> { 
 ... 
}

// 类中参数为3种泛型类型
// 整体作用:控制AsyncTask子类执行线程任务时各个阶段的返回类型
// 具体说明:
    // a. Params:开始异步任务执行时传入的参数类型,对应excute()中传递的参数
    // b. Progress:异步任务执行过程中,返回下载进度值的类型
    // c. Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致
// 注:
    // a. 使用时并不是所有类型都被使用
    // b. 若无被使用,可用java.lang.Void类型代替
    // c. 若有不同业务,需额外再写1个AsyncTask的子类
}
4.3.2 核心方法
image.png
  • 方法执行顺序


    image.png

4.4 使用步骤

  1. 创建AsyncTask 子类 和 根据需求实现核心方法
    2 创建AsyncTask 子类的实例对象
    3 手动调用execute() 从而执行异步线程任务
public class MyTask extends AsyncTask<String, Integer, String> {

    //执行线程前的操作
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    //接受输入参数  执行任务中的耗时操作 返回线程任务执行的结果
    @Override
    protected String doInBackground(String... strings) {
        return null;
    }

    //在主线程 显示线程任务执行的进度
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
    }

    //接受线程任务执行结果 将执行结果显示到UI组件
    @Override
    protected void onPostExecute(String s) {
        super.onPostExecute(s);
    }

    //将异步任务设置为取消状态
    @Override
    protected void onCancelled() {
        super.onCancelled();
    }
}

4.5 生命周期

AsyncTask 不与任何组件绑定声明周期
在Activity 或 Fragment中使用 AsyncTask时,最好在Activity 或 Fragment的onDestory()调用 cancel(boolean)

4.6 内存泄露

若AsyncTask被声明为Activity的非静态内部类,当Activity需销毁时,会因AsyncTask保留对Activity的引用 而导致Activity无法被回收,最终引起内存泄露

把AsyncTask声明为Activity的静态内部类

4.7 线程任务执行结果 丢失

当Activity重新创建时(屏幕旋转 / Activity被意外销毁时后恢复),之前运行的AsyncTask(非静态的内部类)持有的之前Activity引用已无效,故复写的onPostExecute()将不生效,即无法更新UI操作

在Activity恢复时的对应方法 重启 任务线程

4.8 工作原理

AsyncTask的实现原理 = 线程池 + Handler


image.png

5. ThreadPool

image.png

image.png

5.1 内部原理逻辑

image.png

5.2 常见的4类功能线程池

  • 定长线程池(FixedThreadPool)
  • 定时线程池(ScheduledThreadPool )
  • 可缓存线程池(CachedThreadPool)
  • 单线程化线程池(SingleThreadExecutor)
5.2.1 定长线程池

只有核心线程 不会被回收 线程数量固定 任务队列无大小限制(超出的线程任务会在队列中等待)
应用场景:控制线程最大并发数

       //创建定长线程池对象 设置线程池数量固定为3
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        //创建好Runnable
        Runnable task = new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务");
            }
        };
        // 向线程池提交任务
        fixedThreadPool.execute(task);
        // 关闭线程池
        fixedThreadPool.shutdown();
5.2.2 定时线程池

核心线程数量固定、非核心线程数量无限制(闲置时马上回收)
执行定时 / 周期性 任务

      // 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        //创建好Runnable
        Runnable task = new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务");
            }
        };
        // 向线程池提交任务
        scheduledExecutorService.schedule(task,1, TimeUnit.SECONDS);// 延迟1s后执行任务
        //延迟10毫秒 每隔1000毫秒执行任务
        scheduledExecutorService.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);
        //关闭线程
        scheduledExecutorService.shutdown();
5.2.3 可缓存线程池

只有非核心线程,线程数量不固定(可无限大),灵活回收空闲线程(具备超时机制,全部回收时几乎不占系统资源),新建线程(无限城可用时)
任何线程任务到来都会立刻执行,不需要等待
执行大量、耗时少的线程任务

// 1. 创建可缓存线程池对象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
  public void run(){
        System.out.println("执行任务啦");
            }
    };

// 3. 向线程池提交任务:execute()
cachedThreadPool.execute(task);

// 4. 关闭线程池
cachedThreadPool.shutdown();

//当执行第二个任务时第一个任务已经完成
//那么会复用执行第一个任务的线程,而不用每次新建线程。
5.2.4 单线程化线程池

只有一个核心线程(保证所有任务按照指定顺序在一个线程中执行,不需要处理线程同步的问题)
不适合并发但可能引起IO阻塞及影响UI线程的操作

// 1. 创建单线程化线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
  public void run(){
        System.out.println("执行任务啦");
            }
    };

// 3. 向线程池提交任务:execute()
singleThreadExecutor.execute(task);

// 4. 关闭线程池
singleThreadExecutor.shutdown();

5.3 线程池对比

image.png

5.4 多线程对比

image.png

6. Synchronized关键字

image.png

6.1 原理

  1. 依赖JVM实现同步
    2.底层通过一个监视器对象(monitor)完成,wait() notify() 等方法也依赖于monitor对象

监视器锁(monitor)的本质 依赖于 底层操作系统的互斥锁(Mutex Lock)实现

6.2 锁的类型和等级

image.png
6.2.1 区别
image.png

6.3 使用规则

image.png
//对象锁
class Test {
    //方法锁
    public synchronized void Method1() {
        System.out.println("我是对象锁也是方法锁");
        try {
            Thread.sleep(500);
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //代码块形式
    public void Method2() {
        synchronized (this) {
            System.out.println("我是对象锁");
            try {
                Thread.sleep(500);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
}
//方法锁
    public synchronized void Method1() {
        System.out.println("我是对象锁也是方法锁");
        try {
            Thread.sleep(500);
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
//类锁
class Test {
    //锁静态方法
    public static synchronized void Method1() {
        System.out.println("我是对象锁也是方法锁");
        try {
            Thread.sleep(500);
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //锁静态代码块
    public void Method2() {
        synchronized (Test.class){
            System.out.println("我是类锁二号");
            try{
                Thread.sleep(500);
            } catch (InterruptedException e){
                e.printStackTrace();
            }

        }
    }

}

6.4 特别注意

若使用synchronized 关键字修饰线程类的run() 由于run()在线程的整个生命周期内一直在运行,因此将会导致它对本类恩和synchronized 方法的调用都永远不会成功

  • 解决方案
    使用 Synchronized关键字声明代码块
 synchronized(syncObject) { 
    // 访问或修改被锁保护的共享状态 
    // 上述方法 必须 获得对象 syncObject(类实例或类)的锁
}

6.5 特点

image.png

7. ThreadLocal

image.png

7.1 使用流程

7.1.1 创建ThreadLocal变量
//1.直接创建对象
    private ThreadLocal myThreadLocal = new ThreadLocal();
    //2.创建泛型对象
    private ThreadLocal threadLocal = new ThreadLocal<String>();
    //3.创建泛型对象 & 初始化值
    //指定泛型的好处 不需要每次对使用get()方法返回的值作强制类型转换
    private ThreadLocal local = new ThreadLocal<String>() {
        @Nullable
        @Override
        protected String initialValue() {
            return "This is the initial value";
        }
    };

1.ThreadLocal 实例 = 类中的private static字段
2.只需实例化对象一次 & 不需要知道它是被哪个线程实例化
3.每个线程都保持 对其他线程局部变量副本的隐式引用
4.线程消失后 其线程举报变量实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)
5.虽然所有的线程都能访问到这个ThreadLocal实例 但是每个线程只能访问到自己通过调研ThreadLocal的set()设置的值(哪怕2个不同的线程在同一个ThreadLocal对象上设置了不同的值,他们仍然无法访问到对方的值)

7.1.2 访问ThreadLocal变量
// 1. 设置值:set()
// 需要传入一个Object类型的参数
myThreadLocal.set("初始值”);

// 2. 读取ThreadLocal变量中的值:get()
// 返回一个Object对象
String threadLocalValue = (String) myThreadLocal.get();

7.2 具体使用

class ThreadLocalTest {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        new Thread(runnable,"线程1").start();
        new Thread(runnable,"线程2").start();
    }

    public static class MyRunnable implements Runnable {

        private ThreadLocal<String> threadLocal = new ThreadLocal<>() {
            @Nullable
            @Override
            protected Object initialValue() {
                return "初始化值";
            }
        };

        @Override
        public void run() {
            //运行线程时 分别设置 & 获取ThreadLocal的值
            String name = Thread.currentThread().getName();
            threadLocal.set(name + "的threadLocal");
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + ":" + threadLocal.get());
        }
    }
}

7.3 实现原理

ThreadLocal类中有1个Map(称:ThreadLocalMap):用于存储每个线程 & 该线程设置的存储在ThreadLocal变量的值

1.ThreadLocalMap的键key = 当前ThreadLocal实例、值value = 该线程设置的存储在ThreadLocal变量的值
2.该key是 ThreadLocal对象的弱引用;当要抛弃掉ThreadLocal对象时,垃圾收集器会忽略该 key的引用而清理掉ThreadLocal对象

7.4 与同步机制的区别

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 第5章 多线程编程 5.1 线程基础 5.1.1 如何创建线程 在java要创建线程,一般有==两种方式==:1)...
    AndroidMaster阅读 1,789评论 0 11
  • Android中实现多线程,常见的方法有: 继承Thread类 实现Runnable接口 ThreadPoolEx...
    FelixLiuu阅读 348评论 0 0
  • 扩展文章非主线程中能不能直接new Handler()Android 异步消息处理机制 让你深入理解 Looper...
    wayDevelop阅读 601评论 0 0
  • Java 多线程 线程和进程的区别 线程和进程的本质:由CPU进行调度的并发式执行任务,多个任务被快速轮换执行,使...
    安安zoe阅读 2,193评论 1 18
  • CPU时间片轮转机制 CPU时间片轮转机制(RR调度-Round-robin scheduling)原理解释如下:...
    任振铭阅读 980评论 0 10