多线程

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 { 
 ... 
}

// 类中参数为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 {

    //执行线程前的操作
    @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();
    //3.创建泛型对象 & 初始化值
    //指定泛型的好处 不需要每次对使用get()方法返回的值作强制类型转换
    private ThreadLocal local = new ThreadLocal() {
        @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 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 与同步机制的区别

你可能感兴趣的:(多线程)