(轉)多線程優化

1.線程通信基礎

  • 生成者消費者

2.AsyncTask

  • FutureTask
  • 線程池
  • 問題和缺點

3.HandlerThread

  • 優點
  • 例子
  1. IntentService
  • 原理和使用
  • 優點

5.Loader

  • 優點
  • 例子

1.線程通信基礎

1.1.普通的生產者消費者模式

 

public class ThreadTest1 {
    
    //產品
    static class ProductObject{
        //線程操作變量可見
        public volatile static String value;
    }
    
    //生產者線程
    static class Producer extends Thread{
        
        @Override
        public void run() {
            //不斷生產產品
            while(true){
                if(ProductObject.value == null){
                    ProductObject.value = "NO:"+System.currentTimeMillis();
                    System.out.println("生產產品:"+ProductObject.value);
                }       
            }
        }
    }
    
    //消費者線程
    static class Consumer extends Thread{
        
        @Override
        public void run() {
            while(true){
                if(ProductObject.value != null){
                       System.out.println("消費產品:"+ProductObject.value);
                       ProductObject.value = null;
                }
            }
        }
    }

    
    public static void main(String[] args) {
        new Producer().start();
        new Consumer().start();
    }
    
}

當兩個線程對同一個值value操作的時候,在每個線程中都會有一個私有空間保存這個值,即每個線程分別有一個value,假如A線程修改了value,B是不知道A修改了。

1.boolean value=true
生成者線程中vaule修改爲false,消費者中的value任然爲true。

如何修改:給修改值加上volatile,就能保證同步。

 

volatile boolean  value=true;

image.png

1.2.優化

但是volatile的這種操作也會帶來一個問題,就是消費者和生產者線程需要不斷的去判斷值是否消費,這樣也會帶來性能消耗,這裏引入了鎖的概念。

 

image.png

 

public class ThreadTest1 {
    
    //產品
    static class ProductObject{
        //線程操作變量可見
        public volatile static String value;
    }
    
    //生產者線程
    static class Producer extends Thread{
        Object lock;
        
        public Producer(Object lock) {
            this.lock = lock;
        }
        
        @Override
        public void run() {
            //不斷生產產品
            while(true){
                synchronized (lock) { //互斥鎖
                    //產品還沒有被消費,等待
                    if(ProductObject.value != null){
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //產品已經消費完成,生產新的產品
                    ProductObject.value = "NO:"+System.currentTimeMillis();
                    System.out.println("生產產品:"+ProductObject.value);
                    lock.notify(); //生產完成,通知消費者消費
                }
            }
    
        }
    }
    
    //消費者線程
    static class Consumer extends Thread{
        Object lock;
        public Consumer(Object lock) {
            this.lock = lock;
        }
        
        @Override
        public void run() {
            while(true){
                synchronized (lock) {
                    //沒有產品可以消費
                    if(ProductObject.value == null){
                        //等待,阻塞
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("消費產品:"+ProductObject.value);
                    ProductObject.value = null;
                    lock.notify(); //消費完成,通知生產者,繼續生產
                }
            }
        }
    }

    
    public static void main(String[] args) {
        Object lock = new Object();
        new Producer(lock).start();
        new Consumer(lock).start();
    }
    
}

2.AsyncTask

Android的刷新頻率是60fps,如果低於25fps,就會感覺有卡頓的現象。
優化點:減少主線程的負擔,創建子線程進行處理。那麼就涉及到子線程和主線程的通信。

子線程和主線程的通信方式:

  • AsyncTask
  • Handler

2.1.FutureTask

Callable:可以返回結果,Runable是無法獲取結果的
Future
在普通的線程中(比如上面的例子),異步任務執行的結果,主線程是無法輕易獲取。
FutureTask是可以獲取到異步線程中的結果。

Java FutureTask 異步任務操作提供了便利性:

  • 1.獲取異步任務的返回值
  • 2.監聽異步任務的執行完畢
  • 3.取消異步任務

 

   public AsyncTask() {
       // 實現了Callable
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);   //子線程
                Binder.flushPendingCommands();
                return postResult(result);
            }
        };
        //實現了RunnableFuture
        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());   //主線程
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

FutureTask模式實現:

 

public class FutureTest1 {

    
    
    public static void main(String[] args) {
        Task work = new Task();
        FutureTask<Integer> future = new FutureTask<Integer>(work){
            //異步任務執行完成,回調
            @Override
            protected void done() {
                try {
                    System.out.println("done:"+get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        };
        //線程池(使用了預定義的配置)
        ExecutorService executor = Executors.newCachedThreadPool();
        executor.execute(future);
        
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        //取消異步任務
        future.cancel(true);
        
        try {
            //阻塞,等待異步任務執行完畢
            System.out.println(future.get()); //獲取異步任務的返回值
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
    
    //異步任務
    static class Task implements Callable<Integer>{

        //返回異步任務的執行結果
        @Override
        public Integer call() throws Exception {
            int i = 0;
            for (; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + "_"+i);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            return i;
        }
    }
}

doBackground()在call方法中執行
call的返回值在Future的done方法中獲取

 

->onPostExecute

new MyTask().execute();

2.2.執行流程:

 

->onPostExecute

new MyTask().execute();

實例化:
new AsyncTask() -> new FutureTask()

執行:
Executor.execute(mFuture) -> SerialExecutor.myTasks(隊列)
-> (線程池)THREAD_POOL_EXECUTOR.execute

線程池中的所有線程,爲了執行異步任務

2.3.線程池:

線程池中的所有線程,爲了執行異步任務

 

public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, 
KEEP_ALIVE,TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
參數 意義
CORE_POOL_SIZE 核心線程數
MAXIMUM_POOL_SIZE 最大線程數量
KEEP_ALIVE 1s閒置回收
TimeUnit.SECONDS 時間單位
sPoolWorkQueue 異步任務隊列
sThreadFactory 線程工廠
  • 如果當前線程池中的數量小於corePoolSize,創建並添加的任務。
  • 如果當前線程池中的數量等於corePoolSize,緩衝隊列 workQueue未滿,那麼任務被放入緩衝隊列、等待任務調度執行。
  • 如果當前線程池中的數量大於corePoolSize,緩衝隊列workQueue已滿,並且線程池中的數量小於maximumPoolSize,新提交任務會創建新線程執行任務。
  • 如果當前線程池中的數量大於corePoolSize,緩衝隊列workQueue已滿,並且線程池中的數量等於maximumPoolSize,新提交任務由Handler處理。
  • 當線程池中的線程大於corePoolSize時,多餘線程空閒時間超過keepAliveTime時,會關閉這部分線程。

image.png

最終AsyncTask執行的任務是在線程池中執行的,如果創建大量的線程,會出現線程堵塞的現象(FC的風險)。

2.4.問題和缺點:參考

  • 1.生命週期;
  • 2.內存泄漏;
  • 3.結果丟失;
  • 4.並行還是串行;
  • 5.線程池不夠導致拋出異常:線程池中已經有128個線程,緩衝隊列已滿,如果此時向線程提交任務,將會拋出RejectedExecutionException。過多的線程會引起大量消耗系統資源和導致應用FC的風險;
  • 6.異步任務中只能幹一件事情,一個線程只能幹一件事情。

添加任務到線程池的過程是串行,在線程池中執行時是並行。

 

public class AsyncTaskTest {

    public static void main(String[] args) {
        int CPU_COUNT = Runtime.getRuntime().availableProcessors();  //可用的CPU個數
        int CORE_POOL_SIZE = CPU_COUNT + 1; //5
        int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; //9
        int KEEP_ALIVE = 1;
        
        //任務隊列(128)
        final BlockingQueue<Runnable> sPoolWorkQueue =
                new LinkedBlockingQueue<Runnable>(128);
        
        //線程工廠
        ThreadFactory sThreadFactory = new ThreadFactory() {
            private final AtomicInteger mCount = new AtomicInteger(1);

            public Thread newThread(Runnable r) {
                String name = "Thread #" + mCount.getAndIncrement();
                System.out.println(name);
                return new Thread(r, name);
            }
        };
        
        //線程池
        Executor THREAD_POOL_EXECUTOR
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
        
        //執行異步任務
        //如果當前線程池中的數量大於corePoolSize,緩衝隊列workQueue已滿,
        //並且線程池中的數量等於maximumPoolSize,新提交任務由Handler處理。
        //RejectedExecutionException
        for (int i = 0; i < 200; i++) {
            //相當於new AsyncTask().execute();
            THREAD_POOL_EXECUTOR.execute(new MyTask());
        }
    }
    
    static class MyTask implements Runnable{

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
            /*while(true){
                try {
                    System.out.println(Thread.currentThread().getName());
                    //Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }*/
        }
        
    }

}

AsyncTask可以自定義線程池,風險是:太多線程可能導致內存消耗太多。

 

//1.使用的默認線程池
task = new MyTask();
task.execute();
//2.線程池擴容,自定義線程池
Executor exec = Executors.newScheduledThreadPool(25);
for (int i = 0; i < 200; i++) {
    new MyTask().executeOnExecutor(exec);
}

在Activity的onDestroy中task.cancel(true),並不能真正取消線程執行。

AsyncTask的handler用到的的Looper是主線程的,如果任務太多,在主線程中進行輪詢,會導致UI線程有卡頓的現象。

 

   private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

3.HandlerThread

那如果對異步任務的輪詢放在子線程中處理,會不會好點呢。那麼就引出了HandlerThread,他就是一個Thread

 

public class HandlerThreadActivity1 extends Activity {
    
    
    HandlerThread fetchThread = new HandlerThread("fetching_thread");
    Handler fetchHandler;
    private TextView tv;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread);
        tv = (TextView) findViewById(R.id.tv);
        
        //啓動線程
        fetchThread.start();
        //通過fetchHandler發送的消息,會被fetchThread線程創建的輪詢器拉取到
        fetchHandler = new Handler(fetchThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                //模擬訪問網絡延遲
                SystemClock.sleep(1000);
                
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        tv.setText("泰銖匯率:"+new Random().nextInt(10));
                    }
                });
                
                //循環執行
                fetchHandler.sendEmptyMessage(1);
            }
        };
        
        
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        fetchHandler.sendEmptyMessage(1);
    }
    
    
    @Override
    protected void onStop() {
        super.onStop();
        fetchThread.quit(); //取消
    }
}

3.1.HandlerThread的優點:

  • 1.減輕主線程的壓力,提高UI的流暢度(減少主線程的輪詢);
  • 2.可以處理多個任務,開啓一個線程起到多個線程的作用(原理是:looper共享)

3.2.例子

1.打開相機
2.預覽回調(編碼)

 

public class HandlerThreadActivity2 extends Activity implements Callback {

static final String TAG = "jason";
    
    Camera mCamera;
    SurfaceView surfaceView;
    SurfaceHolder surfaceHolder;
    byte[] buffers;
    
    HandlerThread mHandlerThread = new HandlerThread("my_handlerthread");
    Handler subHandler;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread2);
        
        surfaceView = (SurfaceView) findViewById(R.id.surface_view);
        surfaceHolder = surfaceView.getHolder();
        
        surfaceHolder.addCallback(this);
    }
    
    class MyTask implements Runnable, PreviewCallback{

        @Override
        public void run() {
            //打開相機
            //子線程中打開
            Log.d("jason", Thread.currentThread().getName() + "_open");
            mCamera = Camera.open(CameraInfo.CAMERA_FACING_BACK);
            try {
                mCamera.setPreviewDisplay(surfaceHolder);
            } catch (IOException e) {
                e.printStackTrace();
            }
            Camera.Parameters parameters = mCamera.getParameters();
            //設置相機參數
            parameters.setPreviewSize(480, 320); //預覽畫面寬高
            mCamera.setParameters(parameters);
            //獲取預覽圖像數據
            buffers = new byte[480 * 320 * 4];
            mCamera.addCallbackBuffer(buffers);
            mCamera.setPreviewCallbackWithBuffer(this);
            mCamera.startPreview();
            
            Log.d(TAG, Thread.currentThread().getName()+ "_run");
        }

        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            if(mCamera != null){
                mCamera.addCallbackBuffer(buffers);
                //編碼
                Log.d(TAG, Thread.currentThread().getName()+ "_onPreviewFrame");
            }
        }
        
    }
    

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mHandlerThread.start();
        subHandler = new Handler(mHandlerThread.getLooper());
        subHandler.post(new MyTask());
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        
    }
}

如果用AsyncTask,onPreviewFrame的執行就到了主線程。爲什麼呢?
相機中的代碼Camera:

異步任務的Looper,使用的MainLooper
Handler.handleMessage的執行,一定在它的Looper線程中
onPreviewFrame的執行,在Camera所持有的Looper線程中執行
new Camera -> looper -> EventHandler.handleMessage -> onPreviewFrame

 

    private int cameraInitVersion(int cameraId, int halVersion) {
        mShutterCallback = null;
        mRawImageCallback = null;
        mJpegCallback = null;
        mPreviewCallback = null;
        mPostviewCallback = null;
        mUsingPreviewAllocation = false;
        mZoomListener = null;

        Looper looper;
        if ((looper = Looper.myLooper()) != null) {
            mEventHandler = new EventHandler(this, looper);
        } else if ((looper = Looper.getMainLooper()) != null) {
            mEventHandler = new EventHandler(this, looper);
        } else {
            mEventHandler = null;
        }

        return native_setup(new WeakReference<Camera>(this), cameraId, halVersion,
                ActivityThread.currentOpPackageName());
    }

java中普通的線程其他方法調用執行情況:

 

public class ThreadTest2 {
    
    static class MyTask extends Thread{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "_run");
        }
        
        void onPreviewFrame(){
            System.out.println(Thread.currentThread().getName() + "_onPreviewFrame");
        }
    }
    
    public static void main(String[] args) {
        //子線程
        MyTask task = new MyTask();
        task.start();
        //在主線程執行
        task.onPreviewFrame();
    }

}

4.IntentService

4.1.原理和使用

IntentService(本質:Service+HandlerThread+Intent)

要通過startService來啓動,bindService沒什麼用;

 

public class IntentServiceActivity extends Activity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_intent_service);       
    }
    
    //發送意圖給IntentService,啓動子線程執行任務
    public void mClick(View btn){
        Intent intent = new Intent(this,MyIntentService.class);
        startService(intent);
    }
    

}

 

public class MyIntentService extends IntentService {

    //至少要有一個空的構造方法
    public MyIntentService() {
        super("MyIntentService");
    }
    
    public MyIntentService(String name) {
        super(name);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        Log.d("jason", Thread.currentThread().getName() + "_onStart");
    }
    
    
    //UI線程發送Intent,會在子線程中執行
    @Override
    protected void onHandleIntent(Intent intent) {
        Log.d("jason", Thread.currentThread().getName() + "_onHandleIntent");
    }

}

至少要有一個空的構造方法

4.2.優點

  • 1.提高子線程的優先級
  • 2.減輕主線程的壓力

IntentService內部會創建一個HandlerThread,onHandleIntent在HandlerThread線程中執行

5.Loader

Activity中啓動子線程存在的問題:

  • 1.內存泄露
  • 2.無效的更新UI

Loader保證子線程與Activity或者Fragment的生命週期一致
Activity和Fragment自帶LoaderManager

5.1.優點:

1.方便
2.Activity或者Fragment的生命週期一致
3.數據緩存與更新通知

5.2.例子:

查詢通話記錄,是一個比較耗時的操作,應該放在子線程中處理。

優點:
1.數據查詢和跟新UI不需要自己去做線程切換和處理;
2.數據更新後,自動更新;
3.Activity銷燬後,自動取消查詢數據庫操作。

使用加載器加載通話記錄:

 

public class MainActivity extends Activity {

    private static final String TAG = "jason";
    // 查詢指定的條目
    private static final String[] CALLLOG_PROJECTION = new String[] { CallLog.Calls._ID, CallLog.Calls.NUMBER,
            CallLog.Calls.CACHED_NAME, CallLog.Calls.TYPE, CallLog.Calls.DATE };
    private ListView mListView;
    private MyLoaderCallback mLoaderCallback = new MyLoaderCallback();
    private MyCursorAdapter mAdapter;

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

        mListView = (ListView) findViewById(R.id.lv_list);

        mAdapter = new MyCursorAdapter(MainActivity.this, null);
        mListView.setAdapter(mAdapter);

        //執行Loader的回調
        getLoaderManager().initLoader(0, null, mLoaderCallback);
    }

    
    private class MyLoaderCallback implements LoaderManager.LoaderCallbacks<Cursor> {

        //創建Loader
        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            //加載的過程在子線程中進行
            CursorLoader loader = new CursorLoader(MainActivity.this, CallLog.Calls.CONTENT_URI, CALLLOG_PROJECTION,
                    null, null, CallLog.Calls.DEFAULT_SORT_ORDER);
            Log.d(TAG, "onCreateLoader");
            return loader;
        }

        //Loader檢測底層數據,當檢測到改變時,自動執行新的載入獲取最新數據
        //Activity/Fragment所需要做的就是初始化Loader,並且對任何反饋回來的數據進行響應。
        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            if (data == null)
                return;
            mAdapter.swapCursor(data);
            Log.d(TAG, "onLoadFinished data count = " + data.getCount());
        }

        //OnDestroy,自動停止load
        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
            Log.d(TAG, "onLoaderReset");
            mAdapter.swapCursor(null);
        }
    }

}



作者:宋季航
鏈接:https://www.jianshu.com/p/15011b618c59
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章