Those things about Android multithreading

I am participating in the "Nuggets·Starting Plan"

Concurrency is to do several things at the same time, such as network data acquisition, file download, database read and write and other time-consuming business logic, we need to use sub-threads to process, each thread has a clear division of labor, and the main thread can better handle other tasks. thing, avoid ANR.

In fact, our application seems to have many threads, because the CPU calculation speed is too fast, and it can quickly create and switch between threads. Looking at the time, it seems that there are multiple threads executing at the same time. However, if In terms of shortening the time, it still executes another thread after executing the thread.

The difference between process and thread

  • A process is the basic unit of operating system resource allocation. Each process has a different virtual address space and is independent of each other. When an Android application is running, it will create a process to host the application. This process is allocated and managed by the system, including resources such as memory occupied by the process. In Android, each application program will run in an independent process, which can ensure the isolation between applications and improve the stability of the system.
  • A thread is a single path of execution within a process, and a process can run multiple threads at the same time. Threads use shared memory to communicate, so thread safety needs to be considered when accessing shared data between threads. In Android, we need to create child threads to perform time-consuming operations, so that the main thread can keep responding to user operations smoothly.

Threads execute in a specific order

use join

join can handle some scenarios that need to wait for the task to complete before continuing to execute.

Thread t1 = new Thread(() -> System.out.println("Executing thread 1"));
Thread t2 = new Thread(() -> System.out.println("Executing thread 2"));
Thread t3 = new Thread(() -> System.out.println("Executing thread 3"));
try {
    t1.start();
    t1.join();
    t2.start();
    t2.join();
    t3.start();
} catch (InterruptedException e) {
    e.printStackTrace();
}
复制代码

Using a single-threaded thread pool

Thread t1 = new Thread(() -> System.out.println("Executing thread 1"));
Thread t2 = new Thread(() -> System.out.println("Executing thread 2"));
Thread t3 = new Thread(() -> System.out.println("Executing thread 3"));

ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(t1);
executorService.submit(t2);
executorService.submit(t3);
复制代码

Use wait/notify to wait for the notification mechanism

wait is a method of Object, its function is to make the current thread enter the waiting state, let the current thread release the lock it holds, until other threads call the notify or notifyAll method of this object, and the current thread is awakened.

boolean run1, run2;
final Object lock1 = new Object();
final Object lock2 = new Object();
复制代码
Thread t1 = new Thread(() -> {
    synchronized (lock1) {
        System.out.println("Executing thread 1");
        run1 = true;
        lock1.notify();
    }
});

Thread t2 = new Thread(() -> {
    synchronized (lock1) {
        try {
            if (!run1) {
                lock1.wait();
            }
            synchronized (lock2) {
                System.out.println("Executing thread 2");
                lock2.notify();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});

Thread t3 = new Thread(() -> {
    synchronized (lock2) {
        try {
            if (!run2) {
                lock2.wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    System.out.println("Executing thread 3");
});

t1.start();
t2.start();
t3.start();
复制代码

Thread.sleep(0) sleep or not

After a thread releases CPU resources, the operating system recalculates the priorities of all threads to reallocate CPU resources, so the real meaning of sleep is not to pause, but to not participate in CPU competition in the next period of time, and wait until the CPU is redistributed. After finishing, if the priority has not changed, then continue to execute, so the real meaning of sleep(0) is to trigger the reallocation of CPU resources.

synchronized 到底锁住了啥

实例方法

public synchronized void method() {
    // 锁住的是该类的实例对象
}
复制代码

静态方法

public static synchronized void method() {
    // 锁住的是类对象
}
复制代码

实例对象代码块

synchronized (this) {
    // 锁住的是该类的实例对象
}
复制代码

类对象代码块

synchronized (Test.class) {
    // 锁住的是类对象
}
复制代码

任意实例对象代码块

Object obj = new Object();

synchronized (obj) {
    // 锁住的是配置的实例对象
}
复制代码

多线程的三个特性

  • 可见性:如果一个线程对于某个共享变量进行更新之后,后续访问该变量的线程可以读取到该更改的结果,那么我们就说这个线程对于共享变量的更新是可见的。
  • 原子性:访问某个共享变量的操作从其执行线程之外的线程来看,该操作要么已经执行完毕,要么尚未发生,其他线程不会看到执行操作的中间结果。非原子操作存在线程安全问题,需要我们使用同步,比如使用 sychronized 让它变成一个原子操作。一个操作是原子操作,那么称它具有原子性。
  • 有序性:程序在执行的时候,程序的代码执行顺序和语句顺序是一致的。

volatile,synchronized 和 Lock

  • volatile:可以保证线程对变量的修改对其他线程可见,其作用是强制将修改后的值立即写入主存,并通知其他线程刷新缓存。volatile 可以保证变量在线程之间的可见性,但并不能保证线程安全,因为不具备原子性,当多个线程同时进行读写操作时,仍然可能出现数据不一致的情况。
  • synchronized:可以保证在同一时间只有一个线程访问一个共享资源,其他线程需要等待该线程访问结束才能继续执行,既具有原子性又具有可见性,可以保证多个线程访问共享资源时数据的一致性和正确性,但使用 synchronized 可能会使其他线程阻塞,导致性能下降。
  • Lock:是一个接口,提供了一种比 synchronized 更加灵活的方式来实现同步,允许多个线程同时访问同一个共享资源,在使用 Lock 机制时,必须手动加锁和释放锁。
public class LockTest {

    public static ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args) {
        new Thread(() -> {
            testSync();
        }, "t1").start();

        new Thread(() -> {
            testSync();
        }, "t2").start();

    }

    public static void testSync() {
        reentrantLock.lock();
        try {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }
}
复制代码

volatile 可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或某个变量的当前值与修改后的值之间没有约束。

Atomic 包

Atomic 包(java.util.concurrent.atomic)提供了一系列用于处理原子操作的类和方法,以确保多线程环境下的线程安全和数据一致性,如 AtomicInteger,AtomicBoolean,AtomicLong 等。

public class AtomicTest {

    private static AtomicInteger count = new AtomicInteger(1);

    public static void main(String[] args) {

        new Thread(() -> {
            // 以原子方式将输入的数值与实例中的值相加,并返回结果。
            count.addAndGet(2);
        }).start();

        new Thread(() -> {
            System.out.println(count.get());
        }).start();
    }
}
复制代码

线程池

  • 核心线程:有新任务提交时,先检查核心线程数,如果核心线程都在工作,而且数量也已经达到最大核心线程数,则不会继续新建核心线程,而会将任务放入等待队列。
  • 等待队列 :等待队列用于存储当核心线程都在忙时,继续新增的任务,核心线程在执行完当前任务后,会去等待队列拉取任务继续执行,这个队列一般是一个线程安全的阻塞队列,它的容量也可由开发者根据业务来定制。
  • 非核心线程:当等待队列满了,如果当前线程数没有超过最大线程数,则会新建线程执行任务,其实,核心线程和非核心线程本质上没有什么区别。
  • 饱和策略:当等待队列已满,线程数也达到最大线程数时,线程池会根据饱和策略来执行后续操作,默认的策略是抛弃要加入的任务。
  • 线程活动保持时间:线程空闲下来之后,保持存活的持续时间,超过这个时间还没有任务执行,该工作线程结束。

20201130142052367.png

线程池的好处:

  1. 降低资源消耗,通过重复利用已创建的线程来降低线程创建和销毁造成的消耗。
  2. 提高响应速度,当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性,使用线程池可以进行统一分配和监控。

线程池的构造

public ThreadPoolExecutor(int corePoolSize, //核心线程数量
                          int maximumPoolSize, //允许创建的最大线程数
                          long keepAliveTime, //工作线程空闲后保持存活的时间
                          TimeUnit unit, //保持存活的时间单位
                          BlockingQueue<Runnable> workQueue, //任务队列
                          //拒绝策略
                          RejectedExecutionHandler handler) { ... }
复制代码

任务队列有几个可供选择:

  • ArrayBlockingQueue:一个基于数组结构的阻塞队列,按照先进先出的原则对元素进行排序。
  • LinkedBlockingQueue:一个基于链表结构的阻塞队列,按照先进先出的原则对元素进行排序,newFixedThreadPool 就是使用这个队列。
  • SynchronousQueue:一个不存储元素的阻塞队列,每插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,newCachedThreadPool 就是使用这个队列。
  • PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
public class Task implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            System.out.println("Perform Tasks");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
复制代码
ArrayBlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(1);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 1, TimeUnit.MINUTES, blockingQueue);
for (int i = 0; i < 10; i++) {
    threadPoolExecutor.submit(new Task());
}
复制代码

提交任务有两种方式:submit 和 execute。submit 会返回一个 Future 类型的对象,通过这个对象可以判断任务是否执行成功,并且可以通过 get 方法来获取返回值,而 execute 用于提交不需要返回值的任务。

关闭线程池有两种方式:shutdown 和 shutdownNow。shutdown 只是将线程池的状态设置为 SHUTWDOWN 状态,正在执行的任务会继续执行下去,没有被执行的则中断。而 shutdownNow 则是将线程池的状态设置为 STOP,正在执行的任务则被停止,没被执行任务的则返回。

线程池的种类:

newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。

ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
    try {
        Thread.sleep(1000);
        System.out.println(" CurrentThread: " + Thread.currentThread().getName());
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
executorService.shutdown();
复制代码

newFixedThreadPool:创建一个定长的线程池,可控制最大并发数,超出的线程进行队列等待。

ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
    executorService.execute(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Current Thread: " + Thread.currentThread().getName());
    });
}
executorService.shutdown();
复制代码

newCacheTreadPool:创建一个可以缓存的线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程,没回收的话就新建线程。该线程池的最大核心线程为无限大,当执行第二个任务时第一个任务已经完成,则会复用执行第一个任务的线程,否则会新建一个线程。

ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(() -> {
    for (int i = 0; i < 5; i++) {
        try {
            Thread.sleep(1000);
            System.out.println("Current Thread: " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});
复制代码

newScheduledThreadPool:创建一个定长的线程池,支持定时或周期任务执行。

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
scheduledThreadPool.scheduleAtFixedRate(() -> {
    System.out.println("Current Thread: " + Thread.currentThread().getName());
}, 2, 1, TimeUnit.SECONDS); // 周期任务:延迟2秒钟后每隔1秒执行一次任务
复制代码

Guess you like

Origin juejin.im/post/7229976104009564216
Recommended