android

Android多线程开发

2018-07-31  本文已影响14人  xpengb

参考链接:

多线程的三种实现方式

  1. 继承Thread类,重写run函数方法
class MyThread extends Thread {

    @Override
    public void run() {
        super.run();
    }
}
  1. 实现Runnable接口,重写run函数方法
class MyThread implements Runnable{

    @Override
    public void run() {
        
    }
}
  1. 实现Callable接口,重写call函数方法,ExecutorService、Callable、Future实现有返回结果的多线程
class MyThread <T> implements Callable<T> {

    @Override
    public T call() {
        return null;
    }
}
  1. Callable和Runnable的不同之处:

①Callable规定的方法是call(),而Runnable规定的方法是run().
②Callable的任务执行后可返回值,而Runnable的任务是不能返回值的
③call()方法可抛出异常,而run()方法是不能抛出异常的。
④运行Callable任务可拿到一个Future对象,Future表示异步计算的结果。通过Future对象可了解任务执行情况,可取消任务的执行。

Callable和Runable详解见链接:

Android(Java)之多线程结果返回——Future 、FutureTask、Callable、Runnable

如何停止一个线程

  1. 创建一个标识(flag),当线程完成你所需要的工作后,可以将标识设置为退出标识
  2. 使用Thread的interrupt()方法和nterrupted()方法,两者配合break退出循环,或者return来停止线程,有点类似标识(flag)
  3. 可以使用try-catch语句,在try-catch语句中抛出异常,强行停止线程进入catch语句,这种方法可以将错误向上抛,使线程停止事件得以传播

Thread线程状态和相关方法

Thread
  1. 可运行(runnable):线程对象创建后,线程调用start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu的使用权
  2. 运行(running):可运行状态(runnable)的线程获得了cpu使用权,执行程序代码
  3. 阻塞(block):线程因为某种原因放弃了cpu使用权,即让出了cpu使用权,暂时停止运行,直到线程进入可运行(runnable)状态,才有机会再次获得cpu使用权转到运行(running)状态。阻塞的情况分三种:
    (1)等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中
    (2)同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中
    其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态
    (3)死亡(dead):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期,且死亡的线程不可再次复生

sleep和wait的区别

  1. wait() :使一个线程处于等待状态,并且释放所有持有对象的lock锁,直到notify()/notifyAll()被唤醒后放到锁定池(lock blocked pool ),释放同步锁使线 程回到可运行状态(Runnable)。
  2. sleep():使一个线程处于睡眠状态,是一个静态方法,调用此方法要捕捉Interrupted异常,醒来后进入runnable状态,等待JVM调度。
  3. notify():使一个等待状态的线程唤醒,注意并不能确切唤醒等待状态线程,是由JVM决定且不按优先级。
  4. notifyAll():使所有等待状态的线程唤醒,注意并不是给所有线程上锁,而是让它们竞争。
  5. join():使一个线程中断,IO完成会回到Runnable状态,等待JVM的调度。
  6. Synchronized():使Running状态的线程加同步锁使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable)。

如何实现线程同步

1. Synchronized方法

当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。

2. 使用特殊域变量(volatile)实现线程同步

volatile关键字为域变量的访问提供了一种免锁机制,相当于告诉虚拟机该域可能会被其他线程更新,因此每次使用该域就要重新计算,而不是使用寄存器中的值,不能用来修饰final类型的变量

3. 使用重入锁ReentrantLock类实现线程同步

ReentrantLock类是可重入、互斥、实现了Lock接口的锁,方法:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁,通常在finally代码释放锁

private Lock lock = new ReentrantLock();
public void save(int money) {
                lock.lock();
                try{
                    account += money;
                }finally{
                    lock.unlock();
                }

            }

4. 使用ThreadLocal局部变量实现线程同步

如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。

 private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                @Override
                protected Integer initialValue(){
                    return 100;
                }
            };

ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题,前者采用以”空间换时间”的方法,后者采用以”时间换空间”的方式

5. 使用阻塞队列LinkedBlockingQueue实现线程同步

LinkedBlockingQueue是一个基于已连接节点的,范围任意的blocking queue。 队列是先进先出的顺序(FIFO
LinkedBlockingQueue 类常用方法:
LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE 的 LinkedBlockingQueue
put(E e) : 在队尾添加一个元素,如果队列满则阻塞
size() : 返回队列中的元素个数
take() : 移除并返回队头元素,如果队列空则阻塞
代码实例: 实现商家生产商品和买卖商品的同步
当队列满时:
  add()方法会抛出异常
  offer()方法返回false
  put()方法会阻塞

6. 使用原子变量AtomicXxx实现线程同步

Xxx 可以是 String ,Integer等
原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作即-这几种行为要么同时完成,要么都不完成。
AtomicInteger类常用方法:
AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicInteger
addAddGet(int dalta) : 以原子方式将给定值与当前值相加
get() : 获取当前值
原子操作主要有:
对于引用变量和大多数原始变量(long和double除外)的读写操作;  
对于所有使用volatile修饰的变量(包括long和double)的读写操作。

7. 使用线程池进行管理及优化

(1). Android HandlerThread

public class HandlerThreadActivity extends AppCompatActivity{

private HandlerThread mCheckMsgThread;
    private Handler mCheckMsgHandler;
    //与UI线程管理的handler
    private Handler mHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_thread_handler);

        //创建后台线程
        initBackThread();

    }

    private void initBackThread()
    {
        mCheckMsgThread = new HandlerThread("check-message-coming");
        mCheckMsgThread.start();
        mCheckMsgHandler = new Handler(mCheckMsgThread.getLooper())
        {
            @Override
            public void handleMessage(Message msg)
            {
                checkForUpdate();
                if (isUpdateInfo)
                {
                    mCheckMsgHandler.sendEmptyMessageDelayed(MSG_UPDATE_INFO, 1000);
                }
            }
        };
    }

    /**
     * 模拟从服务器解析数据
     */
    private void checkForUpdate()
    {
        try
        {
            //模拟耗时
            Thread.sleep(1000);
            mHandler.post(new Runnable()
            {
                @Override
                public void run()
                {
                    String result = "实时更新中,当前大盘指数:<font color='red'>%d</font>";
                    result = String.format(result, (int) (Math.random() * 3000 + 1000));
                    mTvServiceInfo.setText(Html.fromHtml(result));
                }
            });

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

    }

    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        //释放资源
        mCheckMsgThread.quit();
    }
}

HandlerThread 的源码

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

Looper.loop()的核心代码:

while (true) {  
    Message msg = queue.next(); // might block  
    if (msg != null) {  
        if (msg.target == null) {  
            // No target is a magic identifier for the quit message.  
            return;  
        }  

        long wallStart = 0;  
        long threadStart = 0;  

        // This must be in a local variable, in case a UI event sets the logger  
        Printer logging = me.mLogging;  
        if (logging != null) {  
            logging.println(">>>>> Dispatching to " + msg.target + " " +  
                    msg.callback + ": " + msg.what);  
            wallStart = SystemClock.currentTimeMicro();  
            threadStart = SystemClock.currentThreadTimeMicro();  
        }  

        msg.target.dispatchMessage(msg); 


public void quit() {  
    Message msg = Message.obtain();  
    // NOTE: By enqueueing directly into the message queue, the  
    // message is left with a null target.  This is how we know it is  
    // a quit message.  
    mQueue.enqueueMessage(msg, 0);  
}  

是一个无限循环,退出循环的条件是:msg.target == null;
也就是说,如果我们向此looper的MessageQueue发送一个target为null的message,就可以停止这个线程的远行。

停止HandlerThread的方法就是使用quit方法,具体调用形式如下:
mHandlerThread.getLooper().quit();

(2). 线程池管理

new Thread(new Runnable() {
    @Override
    public void run() {
    // TODO Auto-generated method stub
    }
}).start();
为什么要用线程池:

1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器瘫痪(每个线程需要大约
1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

Java通过Executors提供四种线程池

java线程池的使用参考链接:
https://www.cnblogs.com/dolphin0520/p/3932921.html

上一篇下一篇

猜你喜欢

热点阅读