我正在参加「启航方案」

并发便是一起做几件事情,比如网络数据获取,文件下载,数据库读写等这些耗时的事务逻辑,都需求咱们运用子线程去处理,每个线程分工清晰,主线程才干更好地处理其他事情,防止 ANR。

其实,咱们的运用看似很多个线程,由于 CPU 核算速度太快,能迅速地进行线程间的创立与切换,把时刻拉宽来看,就好像有多个线程在一起履行,可是,假如把时刻拉短来看,它还是履行完该线程后才去履行另一个线程。

进程和线程的区别

  • 进程是操作体系资源分配的基本单位,每个进程有不同的虚拟地址空间,相互之间互相独立。Android 运用程序在运行时,就会创立一个进程来承载这个运用程序,这个进程是由体系分配管理的,包含该进程所占用的内存等资源。在 Android 中,每个运用程序都会运行在独立的进程内,这样能够确保运用程序之间的互相隔离,进步体系的稳定性。
  • 线程是进程内单个履行路径,一个进程能够一起运行多个线程。线程之间运用同享内存的方法进行通讯,因此线程之间拜访同享数据时需求考虑线程安全。在 Android 中,咱们需求创立子线程来履行耗时的操作,这样主线程才干保持流畅地响运用户操作。

线程依照特定的次序履行

运用 join

join 能够处理一些需求等候使命完结后才干持续往下履行的场景。

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();
}

运用单线程化的线程池

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);

运用 wait/notify 等候通知机制

wait 是 Object 的办法,作用是让当时线程进入等候状况,让当时线程开释它所持有的锁,直到其他线程调用此目标的 notify 或 notifyAll 办法,当时线程被唤醒。

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) 究竟睡没睡

操作体系是在一个线程开释 CPU 资源今后,从头核算一切线程的优先级来从头分配 CPU 资源,所以 sleep 真实的意义不是暂停,而是在接下去的时刻内不参加 CPU 的竞争,比及 CPU 从头分配完今后,假如优先级没变,那么持续履行,所以 sleep(0) 的真实含义是触发 CPU 资源从头分配。

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();
    }
}

线程池

  • 中心线程:有新使命提交时,先查看中心线程数,假如中心线程都在作业,而且数量也现已抵达最大中心线程数,则不会持续新建中心线程,而会将使命放入等候行列。
  • 等候行列 :等候行列用于存储当中心线程都在忙时,持续新增的使命,中心线程在履行完当时使命后,会去等候行列拉取使命持续履行,这个行列一般是一个线程安全的堵塞行列,它的容量也可由开发者依据事务来定制。
  • 非中心线程:当等候行列满了,假如当时线程数没有超过最大线程数,则会新建线程履行使命,其实,中心线程和非中心线程本质上没有什么区别。
  • 饱满策略:当等候行列已满,线程数也抵达最大线程数时,线程池会依据饱满策略来履行后续操作,默认的策略是抛弃要加入的使命。
  • 线程活动保持时刻:线程闲暇下来之后,保持存活的持续时刻,超过这个时刻还没有使命履行,该作业线程完毕。

Android 多线程的那些事

线程池的好处:

  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秒履行一次使命