精华内容
下载资源
问答
  • android 多线程数据库读写分析与优化

    千次阅读 2015-03-05 09:23:35
    时间 2013-08-04 10:43:21 CSDN博客 ...最新需要给软件做数据库读写方面的优化,之前无论读写,都是用一个 SQLiteOpenHelper.getWriteableDataBase() 来操作数据库,现在需要多线程并发读写,项目用的是2.2的SD
     
    

    最新需要给软件做数据库读写方面的优化,之前无论读写,都是用一个 SQLiteOpenHelper.getWriteableDataBase() 来操作数据库,现在需要多线程并发读写,项目用的是2.2的SDK。

    android 的数据库系统用的是sqlite ,sqlite的每一个数据库其实都是一个.db文件,它的同步锁也就精确到数据库级了,不能跟别的数据库有表锁,行锁。

    所以对写实在有要求的,可以使用多个数据库文件。

    哎,这数据库在多线程并发读写方面本身就挺操蛋的。

    下面分析一下不同情况下,在同一个数据库文件上操作,sqlite的表现。

    测试程序在2.2虚拟手机,4.2.1虚拟手机,4.2.1真手机上跑。

    1,多线程写,使用一个SQLiteOpenHelper。也就保证了多线程使用一个SQLiteDatabase。

    先看看相关的源码

    //SQLiteDatabase.java 
    
    public long insertWithOnConflict(String table, String nullColumnHack,
                ContentValues initialValues, int conflictAlgorithm) {
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
    
            .... 省略
    
            lock();
            SQLiteStatement statement = null;
            try {
                statement = compileStatement(sql.toString());
    
                // Bind the values
                if (entrySet != null) {
                    int size = entrySet.size();
                    Iterator<Map.Entry<String, Object>> entriesIter = entrySet.iterator();
                    for (int i = 0; i < size; i++) {
                        Map.Entry<String, Object> entry = entriesIter.next();
                        DatabaseUtils.bindObjectToProgram(statement, i + 1, entry.getValue());
                    }
                }
    
                // Run the program and then cleanup
                statement.execute();
    
                long insertedRowId = lastInsertRow();
                if (insertedRowId == -1) {
                    Log.e(TAG, "Error inserting " + initialValues + " using " + sql);
                } else {
                    if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) {
                        Log.v(TAG, "Inserting row " + insertedRowId + " from "
                                + initialValues + " using " + sql);
                    }
                }
                return insertedRowId;
            } catch (SQLiteDatabaseCorruptException e) {
                onCorruption();
                throw e;
            } finally {
                if (statement != null) {
                    statement.close();
                }
                unlock();
            }
        }

    //SQLiteDatabase.java 
    
    
     private final ReentrantLock mLock = new ReentrantLock(true);
    
    /* package */ void lock() {
    
           if (!mLockingEnabled) return; 
    
                 mLock.lock(); 
    
                 if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) { 
    
                     if (mLock.getHoldCount() == 1) { 
    
                           // Use elapsed real-time since the CPU may sleep when waiting for IO
    
                           mLockAcquiredWallTime = SystemClock.elapsedRealtime(); 
    
                           mLockAcquiredThreadTime = Debug.threadCpuTimeNanos(); 
    
                     } 
    
          } 
    
    }

    通过源码可以知道,在执行插入时,会请求SQLiteDatabase对象的成员对象 mlock 的锁,来保证插入不会并发执行。

    经测试不会引发异常。

    但是我们可以通过使用多个SQLiteDatabase对象同时插入,来绕过这个锁。

    2,多线程写,使用多个SQLiteOpenHelper,插入时可能引发异常,导致插入错误。

    E/Database(1471): android.database.sqlite.SQLiteException: error code 5: database is locked08-01

     E/Database(1471):     at android.database.sqlite.SQLiteStatement.native_execute(Native Method)

    E/Database(1471):     at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:55)

    E/Database(1471):     at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1549)

    多线程写,每个线程使用一个SQLiteOpenHelper,也就使得每个线程使用一个SQLiteDatabase对象。多个线程同时执行insert, 最后调用到本地方法  SQLiteStatement.native_execute

    抛出异常,可见android 框架,多线程写数据库的本地方法里没有同步锁保护,并发写会抛出异常。

    所以,多线程写必须使用同一个SQLiteOpenHelper对象。

    3,多线程读

    看SQLiteDatabase的源码可以知道,insert  , update ,  execSQL   都会 调用lock(), 乍一看唯有query 没有调用lock()。可是。。。

    仔细看,发现


    最后,查询结果是一个SQLiteCursor对象。

    SQLiteCursor保存了查询条件,但是并没有立即执行查询,而是使用了lazy的策略,在需要时加载部分数据。

    在加载数据时,调用了SQLiteQuery的fillWindow方法,而该方法依然会调用SQLiteDatabase.lock()

    /**
         * Reads rows into a buffer. This method acquires the database lock.
         *
         * @param window The window to fill into
         * @return number of total rows in the query
         */
        /* package */ int fillWindow(CursorWindow window,
                int maxRead, int lastPos) {
            long timeStart = SystemClock.uptimeMillis();
            mDatabase.lock();
            mDatabase.logTimeStat(mSql, timeStart, SQLiteDatabase.GET_LOCK_LOG_PREFIX);
            try {
                acquireReference();
                try {
                    window.acquireReference();
                    // if the start pos is not equal to 0, then most likely window is
                    // too small for the data set, loading by another thread
                    // is not safe in this situation. the native code will ignore maxRead
                    int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,
                            maxRead, lastPos);
    
                    // Logging
                    if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
                        Log.d(TAG, "fillWindow(): " + mSql);
                    }
                    mDatabase.logTimeStat(mSql, timeStart);
                    return numRows;
                } catch (IllegalStateException e){
                    // simply ignore it
                    return 0;
                } catch (SQLiteDatabaseCorruptException e) {
                    mDatabase.onCorruption();
                    throw e;
                } finally {
                    window.releaseReference();
                }
            } finally {
                releaseReference();
                mDatabase.unlock();
            }
        }

    所以想要多线程读,读之间没有同步锁,也得每个线程使用各自的SQLiteOpenHelper对象,经测试,没有问题。

    4,多线程读写

    我们最终想要达到的目的,是多线程并发读写

    多线程写之前已经知道结果了,同一时间只能有一个写。

    多线程读可以并发

    所以,使用下面的策略:

    一个线程写,多个线程同时读,每个线程都用各自SQLiteOpenHelper。

    这样,在java层,所有线程之间都不会锁住,也就是说,写与读之间不会锁,读与读之间也不会锁。

    发现有插入异常。

    E/SQLiteDatabase(18263): Error inserting descreption=InsertThread#01375493606407
    E/SQLiteDatabase(18263): android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
    E/SQLiteDatabase(18263):     at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)

    插入异常,说明在有线程读的时候写数据库,会抛出异常。

    分析源码可以知道, SQLiteOpenHelper.getReadableDatabase() 不见得获得的就是只读SQLiteDatabase 。

    //  SQLiteOpenHelper.java
    
      public synchronized SQLiteDatabase getReadableDatabase() {
            if (mDatabase != null && mDatabase.isOpen()) {
                return mDatabase;  // The database is already open for business
            }
    
            if (mIsInitializing) {
                throw new IllegalStateException("getReadableDatabase called recursively");
            }
    
            try {
                return getWritableDatabase();
            } catch (SQLiteException e) {
                if (mName == null) throw e;  // Can't open a temp database read-only!
                Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e);
            }
    
            SQLiteDatabase db = null;
            try {
                mIsInitializing = true;
                String path = mContext.getDatabasePath(mName).getPath();
                db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);
                if (db.getVersion() != mNewVersion) {
                    throw new SQLiteException("Can't upgrade read-only database from version " +
                            db.getVersion() + " to " + mNewVersion + ": " + path);
                }
    
                onOpen(db);
                Log.w(TAG, "Opened " + mName + " in read-only mode");
                mDatabase = db;
                return mDatabase;
            } finally {
                mIsInitializing = false;
                if (db != null && db != mDatabase) db.close();
            }
        }
    因为它先看有没有已经创建的SQLiteDatabase,没有的话先尝试创建读写 SQLiteDatabase ,失败后才尝试创建只读SQLiteDatabase 。

    所以写了个新方法,来获得只读SQLiteDatabase

    //DbHelper.java 
    //DbHelper extends SQLiteOpenHelper
    public SQLiteDatabase getOnlyReadDatabase() {
        	try{
        		getWritableDatabase(); //保证数据库版本最新
        	}catch(SQLiteException e){
        		Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):",e);
        	}
        	
            SQLiteDatabase db = null;
            try {
                String path = mContext.getDatabasePath(mName).getPath();
                db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);
                if (db.getVersion() != mNewVersion) {
                    throw new SQLiteException("Can't upgrade read-only database from version " +
                            db.getVersion() + " to " + mNewVersion + ": " + path);
                }
    
                onOpen(db);
                readOnlyDbs.add(db);
                return db;
            } finally {
            }
    }

    使用策略:一个线程写,多个线程同时读,只用一个SQLiteOpenHelper,读线程使用自己写的getOnlyReadDatabase()方法获得只读。
    但是经过测试,还是会抛出异常,2.2上只有插入异常,4.1.2上甚至还有读异常。

    4.1.2上测试,读异常。
     E/SQLiteLog(18263): (5) database is locked
    W/dalvikvm(18263): threadid=21: thread exiting with uncaught exception (group=0x41e2c300)
     E/AndroidRuntime(18263): FATAL EXCEPTION: onlyReadThread#8
    E/AndroidRuntime(18263): android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5): , while compiling: SELECT * FROM test_t

    看来此路不同啊。

    其实SQLiteDataBase 在API 11 多了一个 属性 ENABLE_WRITE_AHEAD_LOGGING

    可以打,enableWriteAheadLogging(),可以关闭disableWriteAheadLogging(),默认是关闭的。

    这个属性是什么意思呢?

    参考api文档,这个属性关闭时,不允许读,写同时进行,通过 锁 来保证。

    当打开时,它允许一个写线程与多个读线程同时在一个SQLiteDatabase上起作用。实现原理是写操作其实是在一个单独的文件,不是原数据库文件。所以写在执行时,不会影响读操作,读操作读的是原数据文件,是写操作开始之前的内容。

    在写操作执行成功后,会把修改合并会原数据库文件。此时读操作才能读到修改后的内容。但是这样将花费更多的内存。
    有了它,多线程读写问题就解决了,可惜只能在API 11 以上使用。

    所以只能判断sdk版本,如果3.0以上,就打开这个属性

    public DbHelper(Context context , boolean enableWAL) {
        this(context, DEFAULT_DB_NAME, null, DEFAULT_VERSION);
        if( enableWAL && Build.VERSION.SDK_INT >= 11){
          getWritableDatabase().enableWriteAheadLogging();
        }
    }

    关于SQLiteDatabase的这个属性,参考api文档,也可以看看SQLiteSession.java里对多线程数据库读写的描述。

    SQLiteSession.java

    结论

    想要多线程并发读写,3.0以下就不要想了,3.0以上,直接设置enableWriteAheadLogging()就ok。

    如果还是达不到要求,就使用多个db文件吧。

    另:

    单位有一个三星 note2手机,上面所有的例子跑起来都啥问题也没有。。。。很好很强大。

    最后,附上我的测试程序。

    https://github.com/zebulon988/SqliteTest.git

    独家之言,如有问题请回复我,谢谢!


    SQLite数据库锁问题

    分类: SQLite 177人阅读 评论(0) 收藏 举报

    记得以前设计评审时,想用SQLite数据库实现某个功能,被教导说应该用Postgresql数据库,因为Postgresql数据库是行锁,而SQLite的锁粒度太粗了。当时还没有什么感觉。

    后来在另一个产品的群里面,经常看到其中的开发和测试说SQLite数据库死锁了。这才留了一下心。

    最近又要使用它,于是拜读了下《SQLite权威指南》,里面赫赫一句话:SQLite处理并发读没有什么问题,但是如果你的应用需要并发写的话,那么SQLite就不适合你了。

    看了一下SQLite数据库的锁机制:

    它包括四种锁,共享锁(Shared lock)、预留锁(Reserved lock)和未决锁(Pending lock)、排他锁(Exclusive lock)

    其中读操作,用的是Shared lock,所以并发的多个读数据库。如果有一个读操作存在,那么都不会允许写。

    而写就比较麻烦,

    1.它首先会申请一个预留锁(Reserved lock),在启用Reserved lock之后,已存在的读可以继续读,也可以有新的读请求。

    2.然后,它会把需要更新的数据写到缓冲区中。

    3.需要写到缓冲区的更新写完以后,就需要将更新刷到硬盘db了。这时,它会申请Pending lock,就不能再有新的Shared lock申请了,也就是阻止了新的读操作。但是已经存在的读操作还是可以继续读的。然后它就等待,直到没有读操作存在(即所有的读都已经结束)这个时候,它就会申请排他锁,此时不允许有其他锁的存在,然后进行commit,将缓冲区的数据写入db中。

    书上给举了个例子:

    B进行写操作,申请了预留锁;然后A进行读操作,申请了共享锁(有预留锁时,是允许读操作申请的);然后A又同时想进行写操作(未释放共享锁的情况),此时申请预留锁(因为已经有预留锁存在了)失败;B写完缓存,想commit时,申请了未决锁,但是无法从未决锁提升到排他锁(因为有共享锁存在)。此时,发生死锁,A和B都想等待对方释放锁。


    对应一下自己的场景:

    1.页面有多个读

    2.后台会定时写

    按照书上说的,写锁的时长大概是几毫秒。我写程序也尽量注意了。也许在极端情况下,在写时,恰好有读锁未释放,不过几毫秒内,概率不算很大。

    另外,就算是这种极端情况未写成功,在下一个5分钟写时,也会把上一个5分钟未commit的给补救上去。从前台看来,就是数据会有一定时延。


    另外一个隐含的需求:页面可能也需要进行更新数据的操作,这个写是有用户的某个动作触发的,那么在多用户的情况下,读写同时、写写同时的概率就会很大。对此,希望是采取规避的方式,在后台提供与此更新操作的脚本,而非在前台页面提供。


    由此可见,SQLite作为一个嵌入式数据库,不太适合用在高并发的场景下;另外,以上都是理论,希望有时间阅读源码,能够彻底弄清楚一切。

    讨论了下,为了规避风险,还是不用SQLite了。什么时候,SQLite才能把数据库级别的锁改为行锁?不过如果真的SQLite支持行锁,那么就违背它轻量、简单的初衷了。所以,这个终究是一个梦。
    展开全文
  • 来操作数据库,现在需要多线程并发读写。Android 的数据库系统用的是sqlite ,sqlite的每一个数据库其实都是一个.db文件,它的同步锁也就精确到文件级别了。下面分析一下不同情况下,在同一个数据库文件上操作...
        最新需要给软件做数据库读写方面的优化,之前无论读写,都是用一个 SQLiteOpenHelper.getWriteableDataBase() 来操作数据库,现在需要多线程并发读写。
    Android 的数据库系统用的是sqlite ,sqlite的每一个数据库其实都是一个.db文件,它的同步锁也就精确到文件级别了。
    下面分析一下不同情况下,在同一个数据库文件上操作sqlite的表现:
    

    1,多线程写,使用一个SQLiteOpenHelper。也就保证了多线程使用一个SQLiteDatabase。
      先看看相关的源码
      java代码
    1. //SQLiteDatabase.java

    2.   public long insertWithOnConflict(String table, String nullColumnHack,
    3.   ContentValues initialValues, int conflictAlgorithm) {
    4.   if (!isOpen()) {
    5.   throw new IllegalStateException("database not open");
    6.   }
    7.   .... 省略
    8.   lock();
    9.   SQLiteStatement statement = null;
    10.   try {
    11.   statement = compileStatement(sql.toString());
    12.   // Bind the values
    13.   if (entrySet != null) {
    14.   int size = entrySet.size();
    15.   Iterator> entriesIter = 
    16. entrySet.iterator();
    17.   for (int i = 0; i < size; i++) {
    18.   Map.Entry entry = entriesIter.next();
    19.   DatabaseUtils.bindObjectToProgram(statement, i + 1, entry.getValue());
    20.   }
    21.   }
    22.   // Run the program and then cleanup
    23.   statement.execute();
    24.   long insertedRowId = lastInsertRow();
    25.   if (insertedRowId == -1) {
    26.   Log.e(TAG, "Error inserting " + initialValues + " using " + sql);
    27.   } else {
    28.   if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) {
    29.   Log.v(TAG, "Inserting row " + insertedRowId + " from "
    30.   + initialValues + " using " + sql);
    31.   }
    32.   }
    33.   return insertedRowId;
    34.   } catch (SQLiteDatabaseCorruptException e) {
    35.   onCorruption();
    36.   throw e;
    37.   } finally {
    38.   if (statement != null) {
    39.   statement.close();
    40.   }
    41.   unlock();
    42.   }
    43.   }
    复制代码
      java代码
    1. //SQLiteDatabase.java

    2.   private final ReentrantLock mLock = new ReentrantLock(true);
    3.   /* package */ void lock() {
    4.   if (!mLockingEnabled) return;
    5.   mLock.lock();
    6.   if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
    7.   if (mLock.getHoldCount() == 1) {
    8.   // Use elapsed real-time since the CPU may sleep when waiting for IO
    9.   mLockAcquiredWallTime = SystemClock.elapsedRealtime();
    10.   mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();
    11.   }
    12.   }
    13.   }
    复制代码
      通过源码可以知道,在执行插入时,会请求SQLiteDatabase对象的成员对象 mlock 的锁,来保证插入不会并发执行。
      经测试不会引发异常。
      但是我们可以通过使用多个SQLiteDatabase对象同时插入,来绕过这个锁。
    2,多线程写,使用多个SQLiteOpenHelper,插入时可能引发异常,导致插入错误。
      E/Database(1471): android.database.sqlite.SQLiteException: error code 5: database is locked08-01
      E/Database(1471): at android.database.sqlite.SQLiteStatement.native_execute(Native Method)
      E/Database(1471): at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:55)
      E/Database(1471): at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1549)
      多线程写,每个线程使用一个SQLiteOpenHelper,也就使得每个线程使用一个SQLiteDatabase对象。多个线程同时执行insert, 最后调用到本地方法 SQLiteStatement.native_execute
      抛出异常,可见android 框架,多线程写数据库的本地方法里没有同步锁保护,并发写会抛出异常。
      所以,多线程写必须使用同一个SQLiteOpenHelper对象。
    3,多线程读
      看SQLiteDatabase的源码可以知道,insert , update , execSQL 都会 调用lock(), 乍一看唯有query 没有调用lock()。可是。。。
      仔细看,发现
      [attach]/data/attachment/forum/201308/eyeandroid.com69985050700111.jpg[/attach]
      最后,查询结果是一个SQLiteCursor对象。
      SQLiteCursor保存了查询条件,但是并没有立即执行查询,而是使用了lazy的策略,在需要时加载部分数据。
      [attach]/data/attachment/forum/201308/eyeandroid.com69985050700112.jpg[/attach]
      在加载数据时,调用了SQLiteQuery的fillWindow方法,而该方法依然会调用SQLiteDatabase.lock()
      java代码
    1. /**

    2.   * Reads rows into a buffer. This method acquires the database lock.
    3.   *
    4.   * @param window The window to fill into
    5.   * @return number of total rows in the query
    6.   */
    7.   /* package */ int fillWindow(CursorWindow window,
    8.   int maxRead, int lastPos) {
    9.   long timeStart = SystemClock.uptimeMillis();
    10.   mDatabase.lock();
    11.   mDatabase.logTimeStat(mSql, timeStart, 
    12. SQLiteDatabase.GET_LOCK_LOG_PREFIX);
    13.   try {
    14.   acquireReference();
    15.   try {
    16.   window.acquireReference();
    17.   // if the start pos is not equal to 0, then most likely window is
    18.   // too small for the data set, loading by another thread
    19.   // is not safe in this situation. the native code will ignore maxRead
    20.   int numRows = native_fill_window(window, window.getStartPosition(), 
    21. mOffsetIndex,
    22.   maxRead, lastPos);
    23.   // Logging
    24.   if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
    25.   Log.d(TAG, "fillWindow(): " + mSql);
    26.   }
    27.   mDatabase.logTimeStat(mSql, timeStart);
    28.   return numRows;
    29.   } catch (IllegalStateException e){
    30.   // simply ignore it
    31.   return 0;
    32.   } catch (SQLiteDatabaseCorruptException e) {
    33.   mDatabase.onCorruption();
    34.   throw e;
    35.   } finally {
    36.   window.releaseReference();
    37.   }
    38.   } finally {
    39.   releaseReference();
    40.   mDatabase.unlock();
    41.   }
    42.   }
    复制代码
      所以想要多线程读,读之间没有同步锁,也得每个线程使用各自的SQLiteOpenHelper对象,经测试,没有问题。
    4,多线程读写
      我们最终想要达到的目的,是多线程并发读写
      多线程写之前已经知道结果了,同一时间只能有一个写。
      多线程读可以并发
      所以,使用下面的策略:
      一个线程写,多个线程同时读,每个线程都用各自SQLiteOpenHelper。
      这样,在java层,所有线程之间都不会锁住,也就是说,写与读之间不会锁,读与读之间也不会锁。
      发现有插入异常。
      E/SQLiteDatabase(18263): Error inserting descreption=InsertThread#01375493606407
      E/SQLiteDatabase(18263): android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
      E/SQLiteDatabase(18263): at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
      插入异常,说明在有线程读的时候写数据库,会抛出异常。
      分析源码可以知道, SQLiteOpenHelper.getReadableDatabase() 不见得获得的就是只读SQLiteDatabase 。
      java代码
    1. // SQLiteOpenHelper.java

    2.   public synchronized SQLiteDatabase getReadableDatabase() {
    3.   if (mDatabase != null && mDatabase.isOpen()) {
    4.   "color:#FF0000;"> return mDatabase; // The database is already open for 
    5. business
    6.   }
    7.   if (mIsInitializing) {
    8.   throw new IllegalStateException("getReadableDatabase called 
    9. recursively");
    10.   }
    11.   try {
    12.   return getWritableDatabase();
    13.   } catch (SQLiteException e) {
    14.   if (mName == null) throw e; // Can't open a temp database read-only!
    15.   Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", 
    16. e);
    17.   }
    18.   SQLiteDatabase db = null;
    19.   try {
    20.   mIsInitializing = true;
    21.   String path = mContext.getDatabasePath(mName).getPath();
    22.   db = SQLiteDatabase.openDatabase(path, mFactory, 
    23. SQLiteDatabase.OPEN_READONLY);
    24.   if (db.getVersion() != mNewVersion) {
    25.   throw new SQLiteException("Can't upgrade read-only database from version " 
    26. +
    27.   db.getVersion() + " to " + mNewVersion + ": " + path);
    28.   }
    29.   onOpen(db);
    30.   Log.w(TAG, "Opened " + mName + " in read-only mode");
    31.   mDatabase = db;
    32.   return mDatabase;
    33.   } finally {
    34.   mIsInitializing = false;
    35.   if (db != null && db != mDatabase) db.close();
    36.   }
    37.   }
    复制代码
      因为它先看有没有已经创建的SQLiteDatabase,没有的话先尝试创建读写 SQLiteDatabase ,失败后才尝试创建只读SQLiteDatabase 。
      所以写了个新方法,来获得只读SQLiteDatabase
      java代码
    1. //DbHelper.java

    2.   //DbHelper extends SQLiteOpenHelper
    3.   public SQLiteDatabase getOnlyReadDatabase() {
    4.   try{
    5.   getWritableDatabase(); //保证数据库版本最新
    6.   }catch(SQLiteException e){
    7.   Log.e(TAG, "Couldn't open " + mName + " for writing (will try 
    8. read-only):",e);
    9.   }
    10.   SQLiteDatabase db = null;
    11.   try {
    12.   String path = mContext.getDatabasePath(mName).getPath();
    13.   db = SQLiteDatabase.openDatabase(path, mFactory, 
    14. SQLiteDatabase.OPEN_READONLY);
    15.   if (db.getVersion() != mNewVersion) {
    16.   throw new SQLiteException("Can't upgrade read-only database from version " 
    17. +
    18.   db.getVersion() + " to " + mNewVersion + ": " + path);
    19.   }
    20.   onOpen(db);
    21.   readOnlyDbs.add(db);
    22.   return db;
    23.   } finally {
    24.   }
    25.   }
    复制代码
      使用策略:一个线程写,多个线程同时读,只用一个SQLiteOpenHelper,读线程使用自己写的getOnlyReadDatabase()方法获得只读。
      但是经过测试,还是会抛出异常,2.2上只有插入异常,4.1.2上甚至还有读异常。
      4.1.2上测试,读异常。
      E/SQLiteLog(18263): (5) database is locked
      W/dalvikvm(18263): threadid=21: thread exiting with uncaught exception (group=0x41e2c300)
      E/AndroidRuntime(18263): FATAL EXCEPTION: onlyReadThread#8
      E/AndroidRuntime(18263): android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5): , while compiling: SELECT * FROM test_t
      看来此路不同啊。
      其实SQLiteDataBase 在API 11 多了一个 属性 ENABLE_WRITE_AHEAD_LOGGING。
      可以打,enableWriteAheadLogging(),可以关闭disableWriteAheadLogging(),默认是关闭的。
      这个属性是什么意思呢?
      参考api文档,这个属性关闭时,不允许读,写同时进行,通过 锁 来保证。
      当打开时,它允许一个写线程与多个读线程同时在一个SQLiteDatabase上起作用。实现原理是写操作其实是在一个单独的文件,不是原数据库文件。所以写在执行时,不会影响读操作,读操作读的是原数据文件,是写操作开始之前的内容。
      在写操作执行成功后,会把修改合并会原数据库文件。此时读操作才能读到修改后的内容。但是这样将花费更多的内存。
      有了它,多线程读写问题就解决了,可惜只能在API 11 以上使用。
      所以只能判断sdk版本,如果3.0以上,就打开这个属性。
      java代码
    1. public DbHelper(Context context , boolean enableWAL) {

    2.   this(context, DEFAULT_DB_NAME, null, DEFAULT_VERSION);
    3.   if( enableWAL && Build.VERSION.SDK_INT >= 11){
    4.   getWritableDatabase().enableWriteAheadLogging();
    5.   }
    6.   }
    复制代码

    结论:想要多线程并发读写,3.0以下就不要想了,3.0以上,直接设置enableWriteAheadLogging()就ok。如果还是达不到要求,就使用多个db文件吧。



    展开全文
  • 多线程查询数据库

    2021-03-19 15:37:54
    多线程查询数据库 项目中遇到了需要一次性查询大量数据的情况,于是想到了使用多线程并发查询,顺便记录一下。 原理是多个线程进行分页查询再将查询结果合并取出。 一、公共接口 public interface BaseService<T&...

    多线程查询数据库

    项目中遇到了需要一次性查询大量数据的情况,于是想到了使用多线程并发查询,顺便记录一下。 原理是多个线程进行分页查询再将查询结果合并取出。

    一、公共接口

    public interface BaseService<T> {
    	T template();
    }
    

    二、模板方法

    public interface BaseThreadServiceTemplate<T> {
    
    	// index为查询索引, num为查询记录数 
        List<T> getList(int index, int num);
    
    	// 查询总记录数 
        int getCount();
    }
    

    三、Service

    需要进行线程查询的继承模板
    public interface RegionService extends BaseThreadServiceTemplate<Region> {
    
    }
    
    public interface MultiThreadQueryService {
    	// 获取最后合并的结果
        List<List> getResult(BaseThreadServiceTemplate baseThreadServiceTemplate);
    }
    
    @Service
    public class RegionServiceImpl implements RegionService {
    
    	@Autowired
        private RegionMapper regionMapper;
        
        @Override
        public List<Region> getList(int index, int num) {
            return regionMapper.selectRegions(index, num);
        }
    
        @Override
        public int getCount() {
            return regionMapper.count();
        }
    }
    
    @Slf4j
    @Service
    public class MultiThreadQueryServiceImpl implements MultiThreadQueryService {
    
        private int corePoolSize = 4;
        private int maximumPoolSize = 8;
        private long keepAliveTime = 30L;
    
        @Override
        public List<List> getResult(BaseThreadServiceTemplate baseThreadServiceTemplate) {
    
            // cpu密集型 参考自己cpu核心数定义
            ExecutorService executorService = new ThreadPoolExecutor(corePoolSize,
                    maximumPoolSize,
                    keepAliveTime,
                    TimeUnit.SECONDS,
                    new ArrayBlockingQueue<>(10));
    
            // 查询数据库总记录数
            int count = baseThreadServiceTemplate.getCount();
            log.info("count=" + count);
            
            Map<String, String> splitMap = SplitUtil.splitMap(count, corePoolSize);
    
            // 封装Callable产生的结果
            List<Callable<List>> tasks = new ArrayList<>();
            for (int i = 1; i <= corePoolSize; i++) {
                String[] split = splitMap.get(String.valueOf(i)).split(":");
                // 查询结果的索引值
                int index = Integer.parseInt(split[0]);
                // 查询的数量
                int num = Integer.parseInt(split[1]);
                // 获得结果
                Callable<List> res = new ThreadQuery(() -> baseThreadServiceTemplate.getList(index, num));
                tasks.add(res);
            }
            
    		// 封装查询数据结果集
            List<List> result = new ArrayList<>();
            try {
                // Future获取结果
                List<Future<List>> futures = executorService.invokeAll(tasks);
                if (futures != null && futures.size() > 0) {
                    // 迭代结果
                    for (Future<List> future : futures) {
                        result.addAll(future.get());
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } finally {
                // 关闭线程池
                executorService.shutdown();
                while (true){
                    if (executorService.isTerminated()){
                        log.info("任务已完成");
                        break;
                    }
                }
            }
            return result;
        }
    }
    
    

    四、工具类

    public class SplitUtil {
    
    	// 根据查询记录数和线程数量分配每条线程查询记录数
        public static Map<String, String> splitMap(int count, int threadCoreNum) {
            Map<String, String> splitMap = new HashMap<>(threadCoreNum);
    
            // 每个线程分配的查询记录数
            int offsetNum = count / threadCoreNum;
            int residue = count % threadCoreNum;
    
            for (int i = 1; i <= threadCoreNum; i++) {
                if (i == 1) {
                    splitMap.put(String.valueOf(i), i - 1 + ":" + offsetNum);
                } else if(i < threadCoreNum) {
                    splitMap.put(String.valueOf(i), (i - 1) * offsetNum + ":" + offsetNum);
                } else {
                    splitMap.put(String.valueOf(i), (i - 1) * offsetNum + ":" + offsetNum + residue);
                }
            }
            return splitMap;
        }
    }
    
    public class ThreadQuery implements Callable<List> {
    
        private BaseService baseService;
    
        public ThreadQuery() {}
    
        public ThreadQuery(BaseService baseService) {
            this.baseService = baseService;
        }
    
        @Override
        public List call() throws Exception {
            // 查询数据库
            return (List)  baseService.template();
        }
    
        public BaseService getBaseService() {
            return baseService;
        }
    
        public void setBaseService(BaseService baseService) {
            this.baseService = baseService;
        }
    }
    
    展开全文
  • 多线程操作数据库

    千次阅读 2014-02-13 18:51:22
    1、多线程操作数据库--- FMDatabaseQueue 这个东西固然好用,但是一定要注意:他还是有弊端的,由于里边原理是block GCD的使用,容易造成死锁,原因为明,继续研究中

    fmdb使用注意问题:

    1、导入:导入库 libsqlite3;

    2、多线程操作数据库---FMDatabaseQueue   

      FMResultSet *rs = [db executeQuery:sql,resourceName];
            if ([rs next])
            {
                isHtmlExist = YES;
            }else
            {
                isHtmlExist = NO;
            }
            [rs close];

         FMResultSet 注意结果集的关闭;(单线程操作不关闭,没问题,但是)多线程操作,必须注意,良好的编程习惯很重要;关于 db关闭,写代码的时候没有写,也没报错


    3、测试结果

         如果涉及到多线程操作数据库  要点:一个线程FMDatabase操作数据库,同时 多个线程使用FMDatabaseQueue操作数据库是不允许的,必须全部使用FMDatabaseQueue操作,(如果同时写入一张标的话)否则,有些情况,会造成死锁。

        简而言之:项目中,要么只有一个线程操作数据库,只用FMDatabase;要么多线程操作,全部使用FMDatabaseQueue操作(FMDatabaseQueue,这个类的原理,也是把让多个线程的数据库同步排队操作

        测试过程:

         

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
        // Override point for customization after application launch.
        self.window.backgroundColor = [UIColor whiteColor];
        [self.window makeKeyAndVisible];
        
        [NSThread detachNewThreadSelector:@selector(writeDbOne) toTarget:self withObject:nil];
        
        [NSThread detachNewThreadSelector:@selector(writeDbTwo) toTarget:self withObject:nil];
        
        [NSThread detachNewThreadSelector:@selector(readDb) toTarget:self withObject:nil];
        
        return YES;
    }
    
    
    
    - (void)writeDbOne
    {
       DbDao *dao = [DbDao  sharedInstance];
        for (int i = 0; i < 500; i++)
        {
            @autoreleasepool
            {
                UserEntity *user = [[[UserEntity alloc] init] autorelease];
                user.name = [NSString stringWithFormat:@"name %d", i];
                user.password = [NSString stringWithFormat:@"password %d", i];
                [dao addUserInMuliThread:user];
                NSLog(@"writeDbOne %d ", i);
            }
        }
    }
    
    - (void)writeDbTwo
    {
        DbDao *dao = [DbDao  sharedInstance];
        for (int i = 600; i < 1200; i++)
        {
            @autoreleasepool
            {
                UserEntity *user = [[[UserEntity alloc] init] autorelease];
                user.name = [NSString stringWithFormat:@"name %d", i];
                user.password = [NSString stringWithFormat:@"password %d", i];
                [dao addUser:user];
                NSLog(@"writeDbTwo %d ", i);
            }
        }
    }
       一个线程操作fmdbdatabase,一个线程操作FMDBdatabaseQueue,测试结果:

        

    2014-08-22 17:28:43.615 FMDBThreadTest[2153:3f03] DB Error: 5 "database is locked"

    2014-08-22 17:28:43.617 FMDBThreadTest[2153:3f03] DB Query: select * from tbl_user 

    2014-08-22 17:28:43.618 FMDBThreadTest[2153:3f03] DB Path: /Users/pekall_song/Library/Application Support/iPhone Simulator/7.1/Applications/80F58FBE-96B9-4121-94A0-04018877953E/Library/Caches/denghuihua.sqlite

             证明queue采用同步操作的代码:

             

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
        // Override point for customization after application launch.
        self.window.backgroundColor = [UIColor whiteColor];
        [self.window makeKeyAndVisible];
        
        [NSThread detachNewThreadSelector:@selector(writeDbOne) toTarget:self withObject:nil];
        
        [NSThread detachNewThreadSelector:@selector(writeDbTwo) toTarget:self withObject:nil];
        
    //    [NSThread detachNewThreadSelector:@selector(readDb) toTarget:self withObject:nil];
        
        return YES;
    }
    
    
    
    - (void)writeDbOne
    {
       DbDao *dao = [DbDao  sharedInstance];
        for (int i = 0; i < 500; i++)
        {
            @autoreleasepool
            {
                UserEntity *user = [[[UserEntity alloc] init] autorelease];
                user.name = [NSString stringWithFormat:@"name %d", i];
                user.password = [NSString stringWithFormat:@"password %d", i];
                [dao addUserInMuliThread:user];
                NSLog(@"writeDbOne %d ", i);
            }
        }
    }
    
    - (void)writeDbTwo
    {
        DbDao *dao = [DbDao  sharedInstance];
        for (int i = 600; i < 1200; i++)
        {
            @autoreleasepool
            {
                UserEntity *user = [[[UserEntity alloc] init] autorelease];
                user.name = [NSString stringWithFormat:@"name %d", i];
                user.password = [NSString stringWithFormat:@"password %d", i];
                [dao addUserInMuliThread:user];
                NSLog(@"writeDbTwo %d ", i);
            }
        }
    }
    
         

    2014-08-22 19:45:17.987 FMDBThreadTest[2449:3507] 同步开始

    2014-08-22 19:45:17.989 FMDBThreadTest[2449:3507] *****************sql--name 0--db<FMDatabase: 0x8fa22a0>

    2014-08-22 19:45:17.999 FMDBThreadTest[2449:3507] 同步完成

    2014-08-22 19:45:18.001 FMDBThreadTest[2449:3507] writeDbOne 0 

    2014-08-22 19:45:18.001 FMDBThreadTest[2449:3c03] 同步开始

    2014-08-22 19:45:18.004 FMDBThreadTest[2449:3c03] *****************sql--name 600--db<FMDatabase: 0x8fa22a0>

    2014-08-22 19:45:18.007 FMDBThreadTest[2449:3c03] 同步完成

    2014-08-22 19:45:18.007 FMDBThreadTest[2449:3c03] writeDbTwo 600 

    2014-08-22 19:45:18.007 FMDBThreadTest[2449:4103] 同步开始

    2014-08-22 19:45:18.086 FMDBThreadTest[2449:4103] 同步完成

    2014-08-22 19:45:18.087 FMDBThreadTest[2449:3507] 同步开始

    2014-08-22 19:45:18.087 FMDBThreadTest[2449:3507] *****************sql--name 1--db<FMDatabase: 0x8fa22a0>

    2014-08-22 19:45:18.089 FMDBThreadTest[2449:3507] 同步完成

    2014-08-22 19:45:18.090 FMDBThreadTest[2449:3507] writeDbOne 1 

    2014-08-22 19:45:18.090 FMDBThreadTest[2449:3c03] 同步开始

    2014-08-22 19:45:18.091 FMDBThreadTest[2449:3c03] *****************sql--name 601--db<FMDatabase: 0x8fa22a0>

    2014-08-22 19:45:18.093 FMDBThreadTest[2449:3c03] 同步完成

    2014-08-22 19:45:18.097 FMDBThreadTest[2449:3c03] writeDbTwo 601 

    2014-08-22 19:45:18.097 FMDBThreadTest[2449:3507] 同步开始

    2014-08-22 19:45:18.099 FMDBThreadTest[2449:3507] *****************sql--name 2--db<FMDatabase: 0x8fa22a0>

    2014-08-22 19:45:18.101 FMDBThreadTest[2449:3507] 同步完成

    2014-08-22 19:45:18.102 FMDBThreadTest[2449:3507] writeDbOne 2 

    2014-08-22 19:45:18.103 FMDBThreadTest[2449:3c03] 同步开始

    2014-08-22 19:45:18.103 FMDBThreadTest[2449:3c03] *****************sql--name 602--db<FMDatabase: 0x8fa22a0>

    2014-08-22 19:45:18.105 FMDBThreadTest[2449:3c03] 同步完成

       由打印结果可知,在一个数据库操作没有执行之前,另外一个数据库操作是不会执行的。                            

    4、不重要,但是也不太确定的经验

       每个线程分别操作自己的FMDatabase对象-----会导致数据库死锁

        两个线程操作一个FMDatabase对象--in use 

         一个线程操作多个FMDatabase对象--(不科学)但是平常使用都用单例对象 ---估计是出于内存原因考虑

      

       补充关于操作队列的认识:

        根据cpu繁忙程度,自己分配线程,相同代码任务,不一定是同一个线程;

       切记,也就是说如果操作到共有资源,注意只用同步锁;否则报些数据库 busy  ,ID not 唯一的错,也是很烦人的。。。。


    5、下面的情况需要使用同步锁

          

    -(void)downAndWritePictureToLocalWithUrl:(NSString *)url
    {
        [self.lock lock];
       NSString *imageName = [[[[url componentsSeparatedByString:@"/"] lastObject] componentsSeparatedByString:@"?"] firstObject];
        if ([NDTResouceVerificationHelper shouldDownResourceInSubThread:imageName])
        {
            NSURL *downImageURL = [NSURL URLWithString:url];
            NSError *error;
            NSData *imageData = [NSData dataWithContentsOfURL:downImageURL options:NSDataReadingUncached error:&error];
            if (error)
            {
                [self performSelectorOnMainThread:@selector(showAlert:) withObject:url waitUntilDone:YES];
            }else
            {
                
                UIImage *image = [[UIImage alloc] initWithData:imageData];
                [UIImageJPEGRepresentation(image, 1.0) writeToFile:[self getImagePath:imageName] atomically:YES];
                
                    //文件名称写入数据库
                    NDTResouceModel *resourceItem = [[NDTResouceModel alloc] init];
                    resourceItem.resouceID = url;
                    resourceItem.resouceName = imageName;
                    [[NDDatabase sharedDatabase] insertResourceItemInMultipleThread :resourceItem];
            }
        }
        [self.lock unlock];
    }
         上述代码中,downAndWritePictureToLocalWithUrl方法,在分线程中执行且有多个对象实例调用该方法,虽然2步数据库操作都在队列中执行,但是不能确定哪一步先放入队列,所以还是需要使用同步锁

    展开全文
  • python使用多线程查询数据库

    万次阅读 2019-02-01 17:04:39
     当数据量过大时,一个程序的执行时间就会主要花费在等待单次查询返回结果,在这个过程中cpu无疑是处于等待io的空闲状态的,这样既浪费了cpu资源,又花费了大量时间(当然这里主要说多线程,批量查询不在考虑范围,...
  • 深入了解多线程原理

    万次阅读 2018-05-25 15:35:48
    即便不考虑多核心,在单核下,多线程也是有意义的,因为在一些操作,比如IO操作阻塞的时候,是不需要CPU参与的,这时候CPU就可以另开一个线程去做别的事情,等待IO操作完成再回到之前的线程继续执行即可 为什么...
  • 最近项目需求,要写入比较的数据到db中,同时又不能让用户在写的过程中停止其他查询操作,也就是需要满足写读并发。通过一阵Google,发现了WAL模式。 下面介绍WAL模式的优缺点: 优点: 1. 读和写可以完全地...
  • 一文看懂数据库原理

    万次阅读 多人点赞 2018-06-25 16:16:02
    你可以自己谷歌/百度一下『关系型数据库原理』,看看结果多么的稀少【译者注:百度为您找到相关结果约1,850,000个…】 ,而且找到的那些文章都很短。现在如果你查找最近时髦的技术(大数据、NoSQL或JavaScript...
  • java多线程读取多个文件 导入数据库

    万次阅读 2016-10-14 16:59:21
    近期在做java读文件的项目,由于数据量较大,因此研究了一下多线程,总结了一下:一. 多个线程读文件和单个线程读文件,效率差不多,甚至可能不如单线程,原因如下:如果只是单纯的读文件,一个线程足够了,因为一般...
  • 多线程实现数据库的并发操作

    万次阅读 2016-10-11 22:08:34
    http://www.cnblogs.com/hanfight/p/4701763.html
  • mysql数据库原理分析

    千次阅读 2018-01-30 12:08:01
    你可以自己谷歌/百度一下『关系型数据库原理』,看看结果多么的稀少【译者注:百度为您找到相关结果约1,850,000个…】 ,而且找到的那些文章都很短。现在如果你查找最近时髦的技术(大数据、NoSQL或JavaScript
  • 万字图解Java多线程

    万次阅读 多人点赞 2020-09-06 14:45:07
    java多线程我个人觉得是javaSe中最难的一部分,我以前也是感觉学会了,但是真正有多线程的需求却不知道怎么下手,实际上还是对多线程这块知识了解不深刻,不知道多线程api的应用场景,不知道多线程的运行流程等等,...
  • 数据库大数据量导出多线程版本

    千次阅读 2012-04-20 21:28:00
    当时写了一篇博客《在集群上支持数据库大数据量导出》,简单地讲了一些原理,并贴出了部分的源码。原理用了一张图来表述: 基本就是客户在页面申请导出请求,把请求存在数据库中,再由定时任务取出来运行:...
  • Mysql数据库原理

    千次阅读 2016-07-24 20:26:16
    Mysql是一个单进程的服务,对于每一个请求都是用线程来...用户发起SQL语句查询数据库  3.查询缓存:记录用户的SQL查询语句。如果再次查询同样内容。就返回缓存  4.如果缓存没有进入分析器。(分析器也可能借鉴缓存)
  • 今天总结一下java多线程机制,以及volatile  首先,为什么需要多线程?  主要是因为计算机的运算能力远远大于I/O,通信传输,还有数据库访问等操作。所以缓存出现了,从而提高了访问速度。但是由于会有多个缓存,...
  • Java 多线程:彻底搞懂线程池

    万次阅读 多人点赞 2019-07-09 19:27:00
    熟悉Java多线程编程的同学都知道,当我们线程创建过多时,容易引发内存溢出,因此我们就有必要使用线程池的技术了。 目录 1 线程池的优势 2 线程池的使用 3 线程池的工作原理 4 线程池的参数 4.1 任务队列...
  • Oracle数据库学习(一)--数据库原理及SQL标签: oracle 数据库 数据库原理 sql2016年03月22日 22:59:543089人阅读 评论(0) 收藏 举报 分类:数据库(6) 版权声明:本博客内容多为学习和工作笔记,有用的...
  • 数据库原理分析(强烈推荐)

    万次阅读 多人点赞 2017-04-01 10:29:50
    你可以自己谷歌/百度一下『关系型数据库原理』,看看结果多么的稀少【译者注:百度为您找到相关结果约1,850,000个…】 ,而且找到的那些文章都很短。现在如果你查找最近时髦的技术(大数据、NoSQL或JavaScript
  • Nginx多线程原理

    千次阅读 2015-11-11 09:12:01
    一、问题 一般情况下,nginx 是一个事件处理器,一个从内核获取连接事件并告诉系统如何处理的控制器。实际上,在操作系统做读写数据调度的时候,nginx是协同...但是所有处理过程都是在一个简单的线程循环中完成的
  • 多线程断点续传(数据库保存进度)

    千次阅读 2016-05-17 16:35:15
    今天来看看多线程断点续传(数据库保存进度)效果图原理图基本流程点击下载按钮后启动Service,去获取文件信息,获取文件信息成功后启动下载任务并把文件信息传过去,由于是多线程断点下载,在开始下载前需要根据当前...
  • 数据库原理及应用学习笔记

    千次阅读 多人点赞 2018-10-02 16:09:30
    这时在B站上看的东南大学的视频的学习笔记,主要是看了一天的数据库系统概念这本神书感觉有点吃力很累,所以还是决定这种看视频做笔记的模式了 引言 什么是数据库 什么事数据库管理系统 文件VS数据库 文件...
  • Android多线程断点续传原理解析

    千次阅读 2017-08-29 21:28:52
    在下载大文件的时候,我们往往要使用多线程断点续传,保证数据的完整性,首先说多线程,我们要多线程下载一个大文件,就有开启多个线程,多个connection,既然是一个文件分开几个线程来下载,那肯定就是一个线程下载...
  • android多线程断点续传原理解析

    千次阅读 2014-12-15 19:56:28
    下面我来解析一下多线程断点续传的原理 首先说多线程,我们要多线程下载一个大文件,就有开启多个线程,多个connection,既然是一个文件分开几个线程来下载,那肯定就是一个线程下载一个部分,不能重复 那么我们...
  • 通过前三篇博文的学习,已经编码实现多线程下载功能的核心代码,通过多个线程之间的管理和调度来处理下载任务,...多线程下载添加数据库支持 greenDao开源库自动生成数据库相关代码 完善网络请求接口中的进度更新功能
  • 前文分享了Python网络攻防相关基础知识,包括正则表达式、Web编程和套接字通信,本文将继续分析Python攻防之多线程、C段扫描和数据库编程。本文参考了爱春秋ADO老师的课程内容,这里也推荐大家观看他Bilibili和...
  • 数据库插入失败引出的多线程问题

    千次阅读 2016-06-29 15:38:35
    昨天遇到一个奇葩问题, 一组不重复的数据在插入数据库的时候 数据 a 出现unique failed , 但是插入成功 数据 b 没有报错, 但是插入失败 并且发现for循环内都遍历不到数据b , 但是for循环外是可以打印到它...
  • 多线程锁定同一资源会造成死锁 线程池中的任务使用当前线程池也可能出现死锁 参考连接: https://blog.csdn.net/qq_35064774/article/details/51793656 情况一: 死锁是两个或多个线程互相等待对方所有用的资源...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 207,912
精华内容 83,164
关键字:

多线程数据库原理