Android 数据库综述(二) 程序计算器与信号量来处理多线程并发问题

原创 2017年10月17日 15:20:40

Android 数据库综述(二) 程序计算器与信号量来处理多线程并发问题


多线程操作数据库,为处理并发问题,大家第一想到的是加锁操作 ,SQLite是文件级别的锁.SQLite3对于并发的处理机制是允许同一个进程的多个线程同时读取一个数据库,但是任何时刻只允许一个线程/进程写入数据库。在操行写操作时,数据库文件被琐定,此时任何其他读/写操作都被阻塞,如果阻塞超过5秒钟(默认是5秒,能过重新编译sqlite可以修改超时时间),就报”database is locked”错误

SQLiteDatabaseLockedException: database is locked和java.lang.IllegalStateException: attempt to re-open an already-closed object.这两个是我们最常见的数据库并发异常

SQLiteDatabaseLockedException: database is locked 异常可通过 Android 数据库综述(一) 中的方法,也就是将SqliteHelper对象设置为单例就可以解决

    // 私有的构造函数,只能自己使用,防止绕过同步方法生成多个实例,
    private SqlDBHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

     /私有的静态对象,为整个应用程序提供一个sqlite操作的静态实例,
    //并保证只能通过下面的静态方法getHelper(Context context)获得,
    //防止使用时绕过同步方法改变它
    //这里主要解决死锁问题,是static就能解决死锁问题
    private static SqlDBHelper instance;

    /**
     * 为应用程序提供一个单一的入口,保证应用程序使用同一个对象操作数据库,不会因为对象不同而使同步方法失效
     * 其实就是获取数据库操作的实例
     * @param context 上下文
     * @return instance
     */
    public static SqlDBHelper getHelper(Context context) {
        if (instance == null)
            instance = new SqlDBHelper(context);
        return instance;
    }

线程A打开数据,正在使用数据库,这时cpu片段分到线程B,线程A挂起。线程B进入执行获取打开db时没有问题,线程B进行操作,在片段时间内数据操作完成,最后关闭数据库database.close()。线程B执行结束,线程A执行,插入数据或者其他操作。。。我靠,怎么数据库关闭了呢,然后抛出java.lang.IllegalStateException: attempt to re-open an already-closed object异常

加同步锁是其中的一种解决方案,我这里提供一种更优雅的方式来处理 并发问题,就是使用计数器原理 ,结合 CountDownLatch 与 Semaphore这两个类来完成

CountDownLatch

CountDownLatch是在java1.5被引入的,跟它一起被引入的并发工具类还有CyclicBarrier、Semaphore、ConcurrentHashMap和BlockingQueue,它们都存在于java.util.concurrent包下。CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行

CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。

Semaphore

Semaphore也是一个线程同步的辅助类,可以维护当前访问自身的线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。
Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态

// 创建一个计数阈值为5的信号量对象
// 只能5个线程同时访问
Semaphore semp = new Semaphore(5);
try {
    // 申请许可
    semp.acquire();
    try {
        // 业务逻辑
    } catch (Exception e) {

    } finally {
        // 释放许可
        semp.release();
    }
} catch (InterruptedException e) {

}
CountDownLatch 与 Semaphore 结合对数据的增册改查
public class ArtSqlDBHelper  {
    //数据库实例对象 
    private SQLiteDatabase mLiteDatabase;
    //数据库辅助类操作对象
    protected static final SqlDBHelper mSqlDBHelper;
    /**
     * CountDownLatch是JAVA提供在java.util.concurrent包下的一个辅助类,
     * 可以把它看成是一个计数器,其内部维护着一个count计数,只不过对这个计数器的操作都是原子操作,同时只能有一个线程去操作这个计数器,
     * CountDownLatch通过构造函数传入一个初始计数值,调用者可以通过调用CounDownLatch对象的cutDown()方法,来使计数减1;
     * 如果调用对象上的await()方法,那么调用者就会一直阻塞在这里,直到别人通过cutDown方法,将计数减到0,才可以继续执行
     */
    /**
     * 在这里创建了一个计数器,初始值为0 ,也就是说当前有0个线程在操作
     */
    private static CountDownLatch latch =
            new CountDownLatch(0);
    /**
     * 创建一个信号量,初始值为1 只允许一个线程来操作
     * 通过初始值为1的Semaphore,很好的实现了资源的互斥访问
     */
    private static Semaphore lock =
            new Semaphore(1, true);


    public ArtSqlDBHelper(Context context) {
        super(context);
        //通过内部方法获取静态对象
        mSqlDBHelper =SqlDBHelper.getHelper(context);
    }
}

这里是执行的是批量操作,与单条数据的操作思想一至

public void insertArtList(List<ArtModel> artModelList) {

   try {
       if (latch.getCount() == 0 || mLiteDatabase == null) {
           mLiteDatabase = mSqlDBHelper.getWritableDatabase();
       }
       //await()方法,那么调用者就会一直阻塞在这里,直到别人通过cutDown方法,将计数减到0
       latch.await();
       //请求许可 
       lock.acquire();
       // 开启事务
       mLiteDatabase.beginTransaction();

       // 循环插入数据
       for (ArtModel artModel : artModelList) {
           //构建 ContentValues
            ContentValues classValues = new ContentValues();
            classValues.put("art_id", artModel.getId());
            classValues.put("user_id", articlPariseModel);
            //执行操作
            mLiteDatabase.insert("t_art_list", null, classValues);     
       }
       // 操作成功
       mLiteDatabase.setTransactionSuccessful();

   } catch (InterruptedException e) {
       e.printStackTrace();
   } finally {
       //结束事务
       mLiteDatabase.endTransaction();
       //释放许可
       lock.release();
       if (latch.getCount() == 1) {
           //关闭数据库
           mLiteDatabase.close();
           mLiteDatabase = null;
       }
       //计数器减1
       latch.countDown();
   }
}
/**
* 删除
* 清除数据库中的数据
*/
public void clearArtDb() {
   try {
       if (latch.getCount() == 0 || mLiteDatabase == null) {
           mLiteDatabase = mSqlDBHelper.getWritableDatabase();
       }
       latch.await();
       lock.acquire();
   } catch (InterruptedException e) {
       e.printStackTrace();
   }
   //这里没指定 where 限定条件,删除表中的的所有的数据
   String clearsQL = "delete from  t_art_list ";
   //执行
   mLiteDatabase.execSQL(clearsQL);
   lock.release();
   if (latch.getCount() == 1) {
       mLiteDatabase.close();
       mLiteDatabase = null;
   }
   latch.countDown();
}
public void updateArtList(List<ArtModel> artModelList) {

   try {
       if (latch.getCount() == 0 || mLiteDatabase == null) {
           mLiteDatabase = mSqlDBHelper.getWritableDatabase();
       }
       latch.await();
       lock.acquire();


       // 开启事务
       mLiteDatabase.beginTransaction();

       // 循环更新数据
       for (ArtModel artModel : artModelList) {
           //构建 ContentValues
            ContentValues classValues = new ContentValues();
            classValues.put("art_id", artModel.getId());
            classValues.put("user_id", articlPariseModel);

            //执行操作
            mLiteDatabase.update("t_art_list", classValues, "art_id=?", new String[]{String.valueOf(artModel.getId())}); 
       }
       // 操作成功
       mLiteDatabase.setTransactionSuccessful();

   } catch (InterruptedException e) {
       e.printStackTrace();
   } finally {
       mLiteDatabase.endTransaction();
       lock.release();

       if (latch.getCount() == 1) {
           mLiteDatabase.close();
           mLiteDatabase = null;
       }

       latch.countDown();
   }
}
public List<ArtModel> queryArtModelList(int type) {
   try {
       if (latch.getCount() == 0 || mLiteDatabase == null) {
           mLiteDatabase = mSqlDBHelper.getReadableDatabase();
       }
       latch.await();
       lock.acquire();
   } catch (InterruptedException e) {
       e.printStackTrace();
   }
   List<ArtModel> list = new ArrayList<>();


   String sql = "select * from " + SqlDBHelper.TABLE_NAME_ART;
   if (type == 0) {
       sql = sql + " where art_is_top=1 or art_is_recommend=1";
   }

   Cursor cursor = mLiteDatabase.rawQuery(sql, null);
   if (cursor != null) {
       while (cursor.moveToNext()) {
           ArtModel artModel = new ArtModel();


           artModel.id = cursor.getInt(cursor.getColumnIndex("art_id"));
           artModel.artName = cursor.getString(cursor.getColumnIndex("art_name"));

           ... ...

           list.add(artModel);
       }
   }

   lock.release();

   if (latch.getCount() == 1) {
       mLiteDatabase.close();
       mLiteDatabase = null;
   }

   latch.countDown();

   return list;
}
版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

C# 多线程并发处理数据库数据,发送信号等待处理完统一插入

public class JPService      {          public JPService()          {              //设定最大的Net并发连接...

C# 多线程并发处理数据库数据,发送信号等待处理完统一插入.

http://hi.baidu.com/jiang_yy_jiang/blog/item/23294013384be4c7f6039e2e.html public class JPService   ...
  • plean
  • plean
  • 2011年06月24日 13:06
  • 1315

Android中Sqlite数据库多线程并发问题

本文出自:http://www.cnblogs.com/jdsjlzx/archive/2011/07/19/2110901.html 最近在做一个Android项目, 为了改善用户体验,把原先...

使用Spring4.3解决缓存过期后多线程并发访问数据库的问题

缓存过期之后,如果多个线程同时请求对某个数据的访问,会同时去到数据库,导致数据库瞬间负荷增高。Spring4.3为@Cacheable注解提供了一个新的参数“sync”(boolean类型,缺省为fa...

几个简单程序加深你对多线程和信号量的理解

#include #include HANDLE g_hSemp1 = NULL; HANDLE g_hSemp2 = NULL; DWORD WINAPI ThreadProc(LPVOID...
  • stpeace
  • stpeace
  • 2013年10月06日 22:58
  • 1599

Linux多线程实践(五 )Posix信号量和互斥锁解决生产者消费者问题

一点区别: system v 信号量只能用于进程间同步,而posix 信号量除了可以进程间同步,还可以线程间同步。system v 信号量每次PV操作可以是N,但Posix 信号量每次PV只能是1。除...
  • NK_test
  • NK_test
  • 2016年01月03日 19:24
  • 2975

多线程14: 关键段,事件,互斥量,信号量的“遗弃”问题

这篇文章对Windows系统下常用的线程同步互斥机制——关键段、事件、互斥量、信号量进行了总结。有网友问到互斥量能处理“遗弃”问题,事件和信号量是否也能处理“遗弃”问题。因此本文将对事件和信号量作个试...

Linux多线程实践(5) --Posix信号量与互斥量解决生产者消费者问题

Posix信号量 Posix 信号量 有名信号量 无名信号量 sem_open sem_init sem_c...

有关水果问题的信号量程序

  • 2016年11月16日 20:45
  • 7.23MB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android 数据库综述(二) 程序计算器与信号量来处理多线程并发问题
举报原因:
原因补充:

(最多只允许输入30个字)