在Android中实现多线程,常见的方法有以下几种:
1. 继承Thread类
2. 实现Runnable接口
3. ThreadPoolExecutor
4. AsyncTask
5. Handler
6. ThreadLocal
7. HandlerThread
8. IntentService
9. Thread
具体使用如下:
1. 创建线程类(继承自Thread类):
```java
// 步骤1:创建线程类 (继承自Thread类)
class MyThread extends Thread{ // 步骤2:复写run(),内容 = 定义线程行为
@Override
public void run(){ // ... // 定义的线程行为 }
}
// 步骤3:创建线程对象,即实例化线程类
MyThread mt=new MyThread("线程名称"); // 步骤4:通过线程对象控制线程的状态,如运行、睡眠、挂起/停止
// 此处采用start()开启线程
mt.start();
```
2. 匿名类使用:
```java
// 步骤1:采用匿名类,直接创建线程类的实例
new Thread("线程名称") { // 步骤2:复写run(),内容 = 定义线程行为
@Override
public void run() { // 步骤3:通过线程对象控制线程的状态,如运行、睡眠、挂起/停止
// ... // 定义的线程行为
}.start();
}
```
3. 实现Runnable接口:
```java
// 步骤1:实现Runnable接口并重写run()方法
class MyRunnable implements Runnable{ // 步骤2:重写run()方法,内容 = 定义线程行为
@Override
public void run(){ // ... // 定义的线程行为 }
}
// 步骤3:创建线程对象,即实例化Runnable接口的实现类对象
Thread thread=new Thread(new MyRunnable()); // 步骤4:通过线程对象控制线程的状态,如运行、睡眠、挂起/停止
// 此处采用start()开启线程
thread.start();
```
创建线程辅助类并实现Runnable接口的步骤如下:
1. 创建一个名为MyThread的类,实现Runnable接口。
2. 在MyThread类中重写run()方法,定义线程行为。
3. 实例化MyThread类,即创建线程辅助对象。
4. 创建线程对象,即实例化Thread类;线程类 = Thread类。
5. 通过线程对象控制线程的状态,如运行、睡眠、挂起/停止。
6. 当调用start()方法时,线程对象会自动回调线程辅助类对象的run(),从而实现线程操作。
匿名类使用步骤如下:
1. 通过匿名类直接创建线程辅助对象,即实例化线程辅助类Runnable。
2. 复写run()方法,定义线程行为。
3. 实例化Thread类;线程类 = Thread类。
4. 通过线程对象控制线程的状态,如运行、睡眠、挂起/停止。
5. 调用start()方法启动线程。
synchronized相关问题:
1. 使用注意问题:锁对象不能为空,作用域不宜过大,避免死锁。
2. Lock和synchronized的选择:根据具体需求选择合适的同步机制。如果需要更细粒度的控制,可以使用Lock;如果只是简单的同步,可以使用synchronized。
以下是内容重构后的代码:
```java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (threadFactory == null || handler == null) {
throw new IllegalArgumentException("线程工厂和饱和策略不能为空");
}
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.keepAliveTime = unit.toNanos(keepAliveTime); // 将时间单位转换为纳秒
this.unit = unit;
this.workQueue = Objects.requireNonNull(workQueue);
this.threadFactory = threadFactory;
this.handler = handler;
}
private int corePoolSize;
private int maximumPoolSize;
private long keepAliveTime;
private TimeUnit unit; // 时间单位
private BlockingQueue
private ThreadFactory threadFactory; // 线程工厂
private RejectedExecutionHandler handler; // 饱和策略
```
AsyncTask和ThreadPoolExecutor都是用于控制线程任务执行的工具,但它们的作用不同。AsyncTask主要用于在主线程中执行子线程任务,而ThreadPoolExecutor则是一个自定义的线程池,可以自由地控制线程池的大小和工作队列等参数,以及对线程池中的线程进行管理。
AsyncTask使用时需要注意,虽然它可以让你在主线程中执行子线程任务,但是它并不是所有类型都被使用。如果没有被使用,可以使用java.lang.Void类型代替。如果有不同的业务需求,需要额外再写一个AsyncTask的子类。
AsyncTask原理是2个线程池 + Handler机制。Handler机制中,Handler的处理过程运行在创建Handler的线程里。一个Looper对应一个MessageQueue,一个线程对应一个Looper。一个Looper可以对应多个Handler。线程是默认没有Looper的,线程需要通过Looper.prepare()、绑定Handler到Looper对象、Looper.loop()来建立消息循环。
在使用Handler时需要注意以下几点:
- 不确定当前线程时,更新UI时尽量调用post方法。
- 在使用Handler发送消息时,需要保证消息的发送方式正确。
- 在处理完消息后需要及时回收资源。
在Android开发中,主线程的Looper对象会自动生成,无需手动创建。而子线程的Looper对象则需要通过`Looper.prepare()`方法手动创建。如果在子线程中不手动创建Looper对象,将无法生成Handler对象。
当Handler在处理消息时,它会根据消息中的`msg.callback`属性来决定如何回调。如果`msg.callback`属性不为空,说明使用了`post(Runnable r)`发送消息,此时直接回调`Runnable`对象中的`run()`方法。如果`msg.callback`属性为空,说明使用了`sendMessage(Message msg)`发送消息,此时回调`handleMessage(msg)`方法。
`post`和`sendMessage`的区别在于:
- `post`方法不需要外部创建消息对象,而是内部根据传入的`Runnable`对象封装消息对象。
- 回调的消息处理方法是在`Runnable`对象中复写的`run()`方法。
在Android系统中,还涉及到一些与Looper相关的API,如`nativePollOnce()`和`nativeWake()`。此外,还有关于内部类和匿名内部类的讨论。
在Java中,非静态内部类和匿名内部类都默认持有外部类的引用。这意味着当主线程的Looper对象处于某个周期时,该应用程序的生命周期也处于该周期。当Handler消息队列中有未处理或正在处理的消息时,由于引用关系的存在,GC无法回收这些外部类的引用。
为了解决这个问题,可以采取以下措施:
1. 将非静态内部类改为静态内部类,这样就不再持有外部类的引用。
2. 尽量避免使用匿名内部类,可以考虑使用Lambda表达式或其他方式来实现回调逻辑。
private static class FHandler extends Handler { // 定义弱引用的Activity实例 private WeakReference
@Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); // 在外部类Activity生命周期结束时,同时清空消息队列和结束Handler生命周期 }
ThreadLocal
// 将ThreadLocalMap对象存储在ThreadLocal中,以便在多线程环境中访问。
threadLocalMap.set(new ThreadLocalMap());
以下是重构后的内容:
Looper.java
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
ThreadLocal.java
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
内部类持有外部类的引用:MessageQueue -> Message -> Handler -> Activity。在主线程中,Looper.prepareMainLooper()方法用于初始化主线程的Looper。当quit()方法被调用时,Looper会退出循环并释放资源。由于synchronized同步锁的存在,delaymsg时间可能不准确。此外,obtain()方法用于获取Looper对象。首先,主线程的Looper是不能quit的,因为它会一直存在并运行所有消息。卡死和阻塞主线程会导致ANR(应用程序无响应)。Looper的block方法只是让当前线程进入睡眠状态,而不是真正地阻塞其他线程。
ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。synchronizedThreadLocal是ThreadLocal的一个实现,它可以保证在同一时刻只有一个线程访问该变量,从而实现线程安全。因此,ThreadLocal的设计初衷就是为了提供线程内部的局部变量,方便在本线程内随时随地的读取,并且与其他线程隔离。ThreadLocal的应用场景包括:当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候。
ThreadLocal是线程安全的,因为它为每个线程都创建了一个独立的副本,保证了线程间的数据隔离。另外,ThreadLocal内部使用了ThreadLocalMap来存储每个线程的变量副本,这个ThreadLocalMap是线程安全的,它使用了synchronized来保证多线程访问时的安全 。
ThreadLocal类是Java中一个非常实用的工具类,它可以为每个线程提供独立的变量副本。在多线程环境下,通过使用ThreadLocal类,可以避免因为多个线程共享同一个变量而导致的数据不一致问题。
每个线程都有一个与之关联的ThreadLocals变量,这个变量指向一个ThreadLocalMap对象。当线程访问ThreadLocals变量时,实际上访问的是各自线程自己的ThreadLocalMap对象(键值对)。
ThreadLocalMap是一个特殊的哈希表,它的键(key)是唯一的,并且与当前的ThreadLocal实例相关联。每个键对应一个值(value),这个值就是当前线程所拥有的ThreadLocal变量的副本。
HandlerThread是Android系统中的一个类,它继承自Thread类。通过继承Thread类,我们可以快速地创建一个带有Looper对象的新工作线程。而通过封装Handler类,我们可以快速地创建Handler对象,并与其他线程进行通信。
总之,ThreadLocal类和HandlerThread机制为我们提供了一种简单有效的方法来实现多线程编程中的数据隔离和通信。
以下是重构后的代码:
```java
// 步骤1:创建HandlerThread实例对象
// 传入参数 = 线程名字,作用 = 标记该线程
HandlerThread mHandlerThread = new HandlerThread("handlerThread");
mHandlerThread.start();
// 步骤2:创建工作线程Handler并复写handleMessage()方法
// 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
// 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
Handler workHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
...//消息处理
}
};
// 步骤3:使用工作线程Handler向工作线程的消息队列发送消息
// 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 2; //消息的标识
msg.obj = "B"; // 消息的存放
// b. 通过Handler发送消息到其绑定的消息队列
workHandler.sendMessage(msg);
// 步骤5:结束线程,即停止线程的消息循环
mHandlerThread.quit();
```
以下是重构后的代码:
```java
@Override
public void onCreate() {
super.onCreate();
// 1. 通过实例化andlerThread新建线程并启动;故使用IntentService时,不需额外新建线程
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
// 2. 获得工作线程的 Looper 并维护自己的工作队列
mServiceLooper = thread.getLooper();
// 3. 新建mServiceHandler并绑定上述获得Looper
mServiceHandler = new ServiceHandler(mServiceLooper);
}
/**
* 分析1:ServiceHandler源码分析
*/
private final class ServiceHandler extends Handler {
// 构造函数
public ServiceHandler(Looper looper) {
super(looper);
}
// IntentService的handleMessage()把接收的消息交给onHandleIntent()处理
@Override
public void handleMessage(Message msg) {
// onHandleIntent方法在工作线程中执行
onHandleIntent((Intent) msg.obj);
}
}
/**
* 分析2: onHandleIntent()源码分析
*/
@WorkerThread
protected abstract void onHandleIntent(Intent intent);
```
IntentService是一种特殊类型的Service,它可以在后台线程中处理任务,而不会阻塞主线程。当启动一个IntentService时,需要传递一个Intent参数,该参数包含了要执行的任务信息。通过onStartCommand()方法将Intent传递给服务,并依次插入到工作队列中。
以下是IntentService的源码分析:
```java
public int onStartCommand(Intent intent, int flags, int startId) {
// 调用onStart()
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
public void onStart(Intent intent, int startId) {
// 1. 获得ServiceHandler消息的引用
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
// 2. 把 Intent参数包装到 message 的 obj 发送消息中
// 这里 Intent = 启动服务时startService(Intent)里传入的 Intent
msg.obj = intent;
// 3. 发送消息,即添加到消息队列里
mServiceHandler.sendMessage(msg);
}
```
从上面的源码可以看出,IntentService本质上是由Handler和HandlerThread组成的。通过HandlerThread单独开启一个工作线程,创建一个内部Handler(ServiceHandler),并将ServiceHandler绑定到IntentService。当调用onStartCommand()方法时,Intent会被传递给ServiceHandler,然后依次插入到工作队列中。最后,逐个发送给onHandleIntent()方法进行处理。
建议不要使用bindService()启动IntentService的原因如下:
1. IntentService的生命周期包括onCreate() -> onBind() -> onUnbind() -> onDestroy(),其中并没有执行onStart或onStartCommand方法。因此,在这些生命周期方法中,消息无法发送到消息队列,onHandleIntent()也不会被回调,从而无法实现多线程操作。
2. 多线程并发问题:当只有一个线程写数据,其他线程都是读数据时,可以使用volatile修饰变量来保证数据的可见性。当有多个线程同时写数据时,如果并发不严重,可以继续使用synchronized关键字,但它有一些局限性,例如不能设置锁超时,也不能通过代码释放锁。这时,可以使用ReentrantLock,它可以通过代码释放锁,并且可以设置锁超时。
在高并发情况下,Synchronized和ReentrantLock效率较低,因为同一时刻只有一个线程能够进入同步代码块。如果有很多线程同时访问,那么其他线程都在等待锁。为了解决这个问题,可以使用Java并发包提供的数据结构,如ConcurrentHashMap、LinkBlockingQueue以及原子性的数据结构(如AtomicInteger)。