1. 程式人生 > >Android資料庫併發操作解決思路

Android資料庫併發操作解決思路

資料庫作為Android資料儲存重要的一部分,相信很多應用中都會用到,面試也會遇到很多關於資料庫的問題。實際開發中我沒遇到過特別複雜的資料庫使用,所以對這一塊的優化沒怎麼研究過。
以前面試的時候被問到過這麼一個問題:
面試官:資料庫併發訪問怎麼處理?
我:給增刪改查方法加鎖。
面試官:那樣會有什麼問題?
我:效率低。
面試官:怎麼解決?
我:不太清楚啊。。。

我一直認為Android開發不會涉及到資料庫大量執行緒訪問資料庫的問題,所以方法加鎖的效率問題是可以容忍的,搜尋這個問題的解決方式,發現大家也都是粗暴的用synchronize來解決。
我覺得即使優化也是在加鎖的方式上優化,關於加鎖的高階用法我沒有研究過,所以一直沒有太在意過這個問題(不要罵我不思進取不去學習高階用法。。。)。

後來不知道在哪看了什麼東東受到了啟發,我們可以用佇列來解決這個問題啊!!

一個思路就是:建立一個單執行緒的執行緒池,所有的資料庫操作都用這個執行緒池來操作

下面是我的demo,非常簡單:

public class DBHelper extends SQLiteOpenHelper{

    private static String DBName = "TestDB";
    private static int DBVersion = 1;
    private SQLiteDatabase db;
    private static DBHelper INSTANCE;
    // 不用的時候需要關閉執行緒池
private ExecutorService pool; private DBHelper(Context context) { super(context, DBName, null, DBVersion); db = getReadableDatabase(); // 建立一個單執行緒池 pool = Executors.newSingleThreadExecutor(); } // 單例 public static DBHelper getInstance(Context context){ if
(INSTANCE == null){ synchronized (DBHelper.class){ if (INSTANCE == null){ INSTANCE = new DBHelper(context.getApplicationContext()); } } } return INSTANCE; } @Override public void onCreate(SQLiteDatabase db) { } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } // 模擬一個插入,引數隨便寫了,返回值和SQLiteDatabase的插入方法一樣都是long型別,表示插入的id public long insert(final String param){ Future<Long> submit = pool.submit(new Callable<Long>() { @Override public Long call() throws Exception { // 休眠1秒,模擬耗時 Thread.sleep(1000); Log.e("lzw","插入 param: " + param); return 100L; } }); long id = -1; try { // 拿操作結果,這裡會阻塞 id = submit.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } return id; } }

整個DBHelper是一個單例,在構造裡實例化了一個單執行緒池,insert方法是表示插入,引數隨便寫了一個,我們通過pool.submit方法把任務提交到了執行緒池進行執行,返回了一個Future型別的變數,
後面呼叫Futureget方法可以拿到返回值,get方法會阻塞,直到執行完call方法裡的內容並拿到返回值。關於ExecutorServiceFuture的使用不是本文重點,大家自行學習。

下面是測試Activity:

public class MainActivity extends AppCompatActivity {

    private TextView tv;
    private DBHelper helper;
    private int param = 0;
    static Handler handler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) findViewById(R.id.content);
        helper = DBHelper.getInstance(this);
        findViewById(R.id.insert).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        param++;
                        final int param2 = param;
                        final long id = helper.insert("----> " + param2);
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                tv.append(id + ":" + param2 + "\n");
                            }
                        });
                    }
                }).start();
            }
        });
    }
}

所有的操作都會逐條執行。
效果圖如下:
這裡寫圖片描述

以上就是所有的程式碼,只是講了一下思路,用的時候大家根據自己的情況去封裝就好了。

其實擴充套件一下,大家可以自己寫一個生產者消費者模型也可以解決這個問題,核心思路和上面的方法都一樣,就是一個阻塞佇列,需要修改資料庫的,把需求新增進佇列,DBHelper從佇列中不斷取並操作資料庫。

其實Handler的思路也是這麼個套路。