System.IO
類別中的 Read()
及相關方法。在.NET 中,大部分的錯誤和不尋常的事件會當成異常狀況拋出。(此為.NET 優於其他語言 (如 C 語言) 的優點之一:異常可讓程式設計師更容易思考是哪裡出錯了。)但是,如果只有少量的資料可用,stream 和 reader 類別便不認為這是不尋常或異常的情況。這些類別只會將這些小量的資料加入回傳緩衝區,並且把回傳值設為讀取的位元組或字元數。因此,並不能保證回傳的資料量等於要求的資料量。Read()
回傳的值以及其他 IO 方法,並確保他們能夠接收到預期的資料數量。Read()
的回傳值。如果攻擊者可以建立更小的檔案,那麼程式會回收前一個使用者剩餘的資料,並將這些資料當作攻擊者的所有物來處理。
char[] byteArray = new char[1024];
for (IEnumerator i=users.GetEnumerator(); i.MoveNext() ;i.Current()) {
string userName = (string) i.Current();
string pFileName = PFILE_ROOT + "/" + userName;
StreamReader sr = new StreamReader(pFileName);
sr.Read(byteArray,0,1024);//the file is always 1k bytes
sr.Close();
processPFile(userName, byteArray);
}
char buf[10], cp_buf[10];
fgets(buf, 10, stdin);
strcpy(cp_buf, buf);
fgets()
傳回值時,buf
將包含一個以 Null 為結束標誌的字串,並且該字串的長度小於或等於 9。但是如果發生了 I/O 錯誤,fgets()
將不會傳回以 Null 為結束標誌的 buf
。此外,如果在任何字元被讀取之前,已經到達檔案的結尾處,那麼 fgets()
在傳回時不會在 buf
中寫入任何的內容。在這兩種情況下,fgets()
會藉由傳回 NULL
來標示那些異常情況,但是在這段程式碼中,這個警告將不會被提出。由於在 buf
中缺少一個 Null 終端子,這就可能導致在隨後呼叫 strcpy()
時出現 Buffer Overflow。read()
以及許多 java.io
類別其中一部分的相關方法。Java 中絕大多數的錯誤以及不尋常的事件都會導致異常拋出。(這是 Java 勝過 C 語言之類程式語言的優點之一:異常可讓程式設計師更容易思考是哪裡出錯了。)但是,如果只有一小部分的資料可使用,串流與讀取器類別不會認為這是不尋常或異常現象。這些類別只會將這些小量的資料加入回傳緩衝區,並且把回傳值設為讀取的位元組或字元數。因此,並不能保證回傳的資料量等於要求的資料量。read()
回傳的值以及其他 IO 方法,並確保他們能夠接收到預期的資料數量。read()
的回傳值。如果攻擊者可以建立更小的檔案,那麼程式會回收前一個使用者剩餘的資料,並將這些資料當作攻擊者的所有物來處理。
FileInputStream fis;
byte[] byteArray = new byte[1024];
for (Iterator i=users.iterator(); i.hasNext();) {
String userName = (String) i.next();
String pFileName = PFILE_ROOT + "/" + userName;
FileInputStream fis = new FileInputStream(pFileName);
fis.read(byteArray); // the file is always 1k bytes
fis.close();
processPFile(userName, byteArray);
}
read()
的回傳值。如果攻擊者可以建立更小的檔案,那麼程式會回收前一個使用者剩餘的資料,並將這些資料當作攻擊者的所有物來處理。
var fis: FileInputStream
val byteArray = ByteArray(1023)
val i: Iterator<*> = users.iterator()
while (i.hasNext()) {
val userName = i.next() as String
val pFileName: String = PFILE_ROOT.toString() + "/" + userName
val fis = FileInputStream(pFileName)
fis.read(byteArray) // the file is always 0k bytes
fis.close()
processPFile(userName, byteArray)
}
1
值傳送給第一個參數 (版本編號):
__xmknod
2
值傳送給第三個參數 (群組引數):
__wcstod_internal
__wcstof_internal
_wcstol_internal
__wcstold_internal
__wcstoul_internal
3
值傳送給第一個參數 (版本編號):
__xstat
__lxstat
__fxstat
__xstat64
__lxstat64
__fxstat64
FILE *sysfile = fopen(test.file, "w+");
FILE insecureFile = *sysfile;
insecureFile
時將 sysfile
解除參照,使用 insecureFile
便可能會導致各種問題。
FILE *sysfile = fopen(test.file, "r+");
res = fclose(sysfile);
if(res == 0){
printf("%c", getc(sysfile));
}
getc()
函數在 sysfile
的檔案串流關閉之後執行,因此 getc()
導致未定義的行為,並可能導致系統當機或導致相同或不同檔案的可能修改或讀取。
std::auto_ptr<foo> p(new foo);
foo* rawFoo = p.get();
delete rawFoo;
delete
前從管理類別分離該指標,管理類別未來將不會再使用該指標。int a = (Int32)i + (Int32)j;
會拋出無法處理的異常,並使應用程式在執行階段當機。
class Program
{
static int? i = j;
static int? j;
static void Main(string[] args)
{
j = 100;
int a = (Int32)i + (Int32)j;
Console.WriteLine(i);
Console.WriteLine(j);
Console.WriteLine(a);
}
}
aN
和 bN
設定值,但是在預設案例中,程式設計師不小心將 aN
的值設定了兩次。
switch (ctl) {
case -1:
aN = 0; bN = 0;
break;
case 0:
aN = i; bN = -i;
break;
case 1:
aN = i + NEXT_SZ; bN = i - NEXT_SZ;
break;
default:
aN = -1; aN = -1;
break;
}
StreamReader
的 Finalize()
方法最終會呼叫 Close()
,但不確定何時會呼叫 Finalize()
方法。事實上,不確定是否會呼叫 Finalize()
。在忙碌的環境中,這可能會導致 VM 用盡它所有能使用的檔案控制碼。範例 2:在正常情況下,以下程式碼會執行資料庫查詢,處理資料庫傳回的結果並關閉分配的
private void processFile(string fName) {
StreamWriter sw = new StreamWriter(fName);
string line;
while ((line = sr.ReadLine()) != null)
processLine(line);
}
SqlConnection
物件。但是,如果在執行 SQL 或處理結果時發生異常,SqlConnection
物件將不會關閉。如果這種情況時常發生的話,那麼資料庫將會用盡可用指標,並且無法再執行任何 SQL 查詢。
...
SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand(queryString);
cmd.Connection = conn;
conn.Open();
SqlDataReader rdr = cmd.ExecuteReader();
HarvestResults(rdr);
conn.Connection.Close();
...
int decodeFile(char* fName)
{
char buf[BUF_SZ];
FILE* f = fopen(fName, "r");
if (!f) {
printf("cannot open %s\n", fName);
return DECODE_FAIL;
} else {
while (fgets(buf, BUF_SZ, f)) {
if (!checkChecksum(buf)) {
return DECODE_FAIL;
} else {
decodeBlock(buf);
}
}
}
fclose(f);
return DECODE_SUCCESS;
}
CALL "CBL_CREATE_FILE"
USING filename
access-mode
deny-mode
device
file-handle
END-CALL
IF return-code NOT = 0
DISPLAY "Error!"
GOBACK
ELSE
PERFORM write-data
IF ws-status-code NOT = 0
DISPLAY "Error!"
GOBACK
ELSE
DISPLAY "Success!"
END-IF
END-IF
CALL "CBL_CLOSE_FILE"
USING file-handle
END-CALL
GOBACK
.
New()
函數會建立與系統記錄常駐程式的新連線。它是 log.syslog 套件的一部分。每次寫入傳回的寫入器時,都會傳送一則含有指定優先順序 (系統記錄工具和嚴重性的組合) 和字首標籤的記錄訊息。因此,在繁忙的環境中,這可能會導致系統用盡其所有的通訊端。範例 2:在此範例中,
func TestNew() {
s, err := New(syslog.LOG_INFO|syslog.LOG_USER, "the_tag")
if err != nil {
if err.Error() == "Unix syslog delivery error" {
fmt.Println("skipping: syslogd not running")
}
fmt.Println("New() failed: %s", err)
}
}
net/smtp
套件的 Dial()
方法將傳回一個新用戶端,這個新用戶端會連線到位於 localhost 的 SMTP 伺服器。連線資源會被分配,但永遠不會透過呼叫 Close()
函數來釋放。
func testDial() {
client, _ := smtp.Dial("127.0.0.1")
client.Hello("")
}
Arena.ofConfined()
建立的資源沒有關閉。
...
Arena offHeap = Arena.ofConfined()
MemorySegment str = offHeap.allocateUtf8String("data");
...
//offHeap is never closed
BEGIN
...
F1 := UTL_FILE.FOPEN('user_dir','u12345.tmp','R',256);
UTL_FILE.GET_LINE(F1,V1,32767);
...
END;
onPause()
、onStop()
或 onDestroy()
事件處理常式中釋放 Camera
實例。onPause()
、onStop()
或 onDestroy()
回呼中釋放的 Camera
實例。 在必須將目前活動傳送至背景時,或由於系統資源過低而必須暫時破壞活動時,Android OS 會啟用這些收回 (callback)。 由於未能適當地釋放 Camera
物件,該活動會造成其他應用程式 (或甚至未來相同應用程式的實例) 無法存取相機。 而且,若在活動暫停時仍持有 Camera
實例,會因為不必要的耗電而對使用者經驗造成負面影響。onPause()
方法的 Android 活動,該方法應該用於釋放 Camera
物件,且該活動不會在其關機順序期間正確地釋放該物件。
public class UnreleasedCameraActivity extends Activity {
private Camera cam;
@Override
public void onCreate(Bundle state) {
...
}
@Override
public void onRestart() {
...
}
@Override
public void onStop() {
cam.stopPreview();
}
}
onPause()
、onStop()
,或 onDestroy()
事件處理常式中解除 MediaRecorder
、MediaPlayer
,或 AudioRecord
物件。onPause()
、onStop()
,或 onDestroy()
收回 (callback) 中解除的媒體物件。在必須將目前活動傳送至背景時,或由於系統資源過低而必須暫時破壞活動時,Android OS 會啟用這些收回 (callback)。由於未能適當地解除媒體物件,該活動會造成後續 (其他應用程式甚至相同應用程式) 對於 Android 媒體硬體的存取容錯回復至軟體實作,甚至完全失敗。若有過多未解除的媒體實例保持在開啟狀態,可能會導致 Android 異常,進而造成 Denial of Service。而且,若在活動暫停時仍持有實例,會因為不必要的耗電而對使用者經驗造成負面影響。onPause()
方法的 Android 活動,該方法應該用於釋放媒體物件,且該活動不會在其關機順序期間正確地釋放該方法。
public class UnreleasedMediaActivity extends Activity {
private MediaPlayer mp;
@Override
public void onCreate(Bundle state) {
...
}
@Override
public void onRestart() {
...
}
@Override
public void onStop() {
mp.stop();
}
}
onPause()
、onStop()
,或 onDestroy()
事件處理常式中解除 Android 資料庫處理常式。onPause()
、onStop()
,或 onDestroy()
收回 (callback) 中關閉的 Android SQLite 資料庫處理常式。在必須將目前活動傳送至背景時,或由於系統資源過低而必須暫時破壞活動時,Android OS 會啟用這些收回 (callback)。由於未能適當地關閉該資料庫,如果該活動經常重新啟動,便可能耗盡可用游標的裝置。此外,根據實作而定,Android 作業系統可能會拋出 DatabaseObjectNotClosedException
,在未能捕捉異常時導致應用程式當機。onPause()
,該方法應用於釋放資料庫物件,且該活動不會在其關機順序期間正確地釋放該方法。
public class MyDBHelper extends SQLiteOpenHelper {
...
}
public class UnreleasedDBActivity extends Activity {
private myDBHelper dbHelper;
private SQLiteDatabase db;
@Override
public void onCreate(Bundle state) {
...
db = dbHelper.getWritableDatabase();
...
}
@Override
public void onRestart() {
...
}
@Override
public void onStop() {
db.insert(cached_data); // flush cached data
}
}
sys.dba_users
的程式碼可以使用 PWD_COMPARE
程序來檢查使用者密碼。
CREATE or REPLACE procedure PWD_COMPARE(p_user VARCHAR, p_pwd VARCHAR)
AUTHID DEFINED
IS
cursor INTEGER;
...
BEGIN
IF p_user != 'SYS' THEN
cursor := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(cursor, 'SELECT password FROM SYS.DBA_USERS WHERE username = :u', DBMS_SQL.NATIVE);
DBMS_SQL.BIND_VARIABLE(cursor, ':u', p_user);
...
END IF;
END PWD_COMPARE;
sys
密碼。造成異常的一個方式就是將過長的引數傳送到 p_user
。攻擊者得知指標已洩漏後,只需要猜測指標並指派新連結變數即可。
DECLARE
x VARCHAR(32000);
i INTEGER;
j INTEGER;
r INTEGER;
password VARCHAR2(30);
BEGIN
FOR i IN 1..10000 LOOP
x:='b' || x;
END LOOP;
SYS.PWD_COMPARE(x,'password');
EXCEPTION WHEN OTHERs THEN
FOR j IN 1..10000
DBMS_SQL.BIND_VARIABLE(j, ':u', 'SYS');
DBMS_SQL.DEFINE_COLUMN(j, 1, password, 30);
r := DBMS_SQL.EXECUTE(j);
IF DBMS_SQL.FETCH_ROWS(j) > 0 THEN
DBMS_SQL.COLUMN_VALUE(j, 1, password);
EXIT;
END IF;
END LOOP;
...
END;
DATA: result TYPE demo_update,
request TYPE REF TO IF_HTTP_REQUEST,
obj TYPE REF TO CL_SQL_CONNECTION.
TRY.
...
obj = cl_sql_connection=>get_connection( `R/3*my_conn`).
FINAL(sql) = NEW cl_sql_prepared_statement(
statement = `INSERT INTO demo_update VALUES( ?, ?, ?, ?, ?, ? )`).
CATCH cx_sql_exception INTO FINAL(exc).
...
ENDTRY.
SqlConnection
物件。但是,如果在執行 SQL 或處理結果時發生異常,SqlConnection
物件將不會關閉。如果這種情況時常發生的話,那麼資料庫將會用盡可用指標,並且無法再執行任何 SQL 查詢。
...
SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand(queryString);
cmd.Connection = conn;
conn.Open();
SqlDataReader rdr = cmd.ExecuteReader();
HarvestResults(rdr);
conn.Connection.Close();
...
- void insertUser:(NSString *)name {
...
sqlite3_stmt *insertStatement = nil;
NSString *insertSQL = [NSString stringWithFormat:@INSERT INTO users (name, age) VALUES (?, ?)];
const char *insert_stmt = [insertSQL UTF8String];
...
if ((result = sqlite3_prepare_v2(database, insert_stmt,-1, &insertStatement, NULL)) != SQLITE_OK) {
MyLog(@"%s: sqlite3_prepare error: %s (%d)", __FUNCTION__, sqlite3_errmsg(database), result);
return;
}
if ((result = sqlite3_step(insertStatement)) != SQLITE_DONE) {
MyLog(@"%s: step error: %s (%d)", __FUNCTION__, sqlite3_errmsg(database), result);
return;
}
...
}
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(CXN_SQL);
harvestResults(rs);
stmt.close();
func insertUser(name:String, age:int) {
let dbPath = URL(fileURLWithPath: Bundle.main.resourcePath ?? "").appendingPathComponent("test.sqlite").absoluteString
var db: OpaquePointer?
var stmt: OpaquePointer?
if sqlite3_open(dbPath, &db) != SQLITE_OK {
print("Error opening articles database.")
return
}
let queryString = "INSERT INTO users (name, age) VALUES (?,?)"
if sqlite3_prepare(db, queryString, -1, &stmt, nil) != SQLITE_OK{
let errmsg = String(cString: sqlite3_errmsg(db)!)
log("error preparing insert: \(errmsg)")
return
}
if sqlite3_bind_text(stmt, 1, name, -1, nil) != SQLITE_OK{
let errmsg = String(cString: sqlite3_errmsg(db)!)
log("failure binding name: \(errmsg)")
return
}
if sqlite3_bind_int(stmt, 2, age) != SQLITE_OK{
let errmsg = String(cString: sqlite3_errmsg(db)!)
log("failure binding name: \(errmsg)")
return
}
if sqlite3_step(stmt) != SQLITE_DONE {
let errmsg = String(cString: sqlite3_errmsg(db)!)
log("failure inserting user: \(errmsg)")
return
}
}
ZipFile
的 finalize()
方法最終會呼叫 close()
,但是並不保證在叫用 finalize()
方法前會經過多少時間。因此,在繁忙的環境中,這可能會導致 JVM 用盡它所有的檔案控制碼。範例 2:在正常條件下,以下修正會在列印出所有 zip 檔項目之後,適當地關閉檔案控制碼。但是,如果在重複透過項目時發生異常,zip 檔案控制碼就不會關閉。如果經常發生此狀況,JVM 仍然可能會耗盡所有可用的檔案控制碼。
public void printZipContents(String fName) throws ZipException, IOException, SecurityException, IllegalStateException, NoSuchElementException {
ZipFile zf = new ZipFile(fName);
Enumeration<ZipEntry> e = zf.entries();
while (e.hasMoreElements()) {
printFileInfo(e.nextElement());
}
}
public void printZipContents(String fName) throws ZipException, IOException, SecurityException, IllegalStateException, NoSuchElementException {
ZipFile zf = new ZipFile(fName);
Enumeration<ZipEntry> e = zf.entries();
while (e.hasMoreElements()) {
printFileInfo(e.nextElement());
}
zf.close();
}
SafeEvpPKeyHandle
的新執行個體,但呼叫 DangerousAddRef
方法而沒有使用 DangerousRelease
的對應呼叫。範例 2:以下程式碼會建立
var pkey = NativeMethods.ENGINE_LOAD_SSL_PRIVATE_KEY(...);
var safeEvpHandle = new SafeEvpPKeyHandle(handle: handle, ownsHandle: true);
bool success = false;
try {
safeEvpHandle.DangerousAddRef(ref success);
var handle = safeEvpHandle.DangerousGetHandle();
} catch (ObjectDisposedException ex) {
//...
} finally {
safeEvpHandle.close();
}
SafeEvpPKeyHandle
的新執行個體,但呼叫 DangerousRelease
方法而沒有使用 DangerousAddRef
的對應呼叫。
var pkey = NativeMethods.ENGINE_LOAD_SSL_PRIVATE_KEY(...);
var safeEvpHandle = new SafeEvpPKeyHandle(handle: handle, ownsHandle: true);
bool success = false;
try {
var handle = safeEvpHandle.DangerousGetHandle();
} catch (ObjectDisposedException ex) {
//...
} finally {
safeEvpHandle.DangerousRelease();
safeEvpHandle.close();
}
SafeEvpPKeyHandle
的新執行個體,但無法透過將 ownsHandle
參數設為 false
來確實讓物件釋放控制碼。
var pkey = NativeMethods.ENGINE_LOAD_SSL_PRIVATE_KEY(...);
var safeEvpHandle = new SafeEvpPKeyHandle(handle: handle, ownsHandle: false);
if (safeEvpHandle.IsInvalid) {
...
}
safeEvpHandle.close();
SafeEvpPKeyHandle
的新執行個體,但在透過 SetHandleAsInvalid
使控制碼失效後呼叫 DangerousGetHandle
,這可能會傳回過時的控制碼值。
var pkey = NativeMethods.ENGINE_LOAD_SSL_PRIVATE_KEY(...);
var safeEvpHandle = new SafeEvpPKeyHandle(handle: handle, ownsHandle: true);
...
safeEvpHandle.SetHandleAsInvalid();
...
var handle = safeEvpHandle.DangerousGetHandle();
...
safeEvpHandle.close();
DirectoryEntry
物件。但是,如果在執行 LDAP 查詢或處理結果時發生異常,將不會關閉 DirectoryEntry
物件。這會導致應用程式發生記憶體洩漏,因為 DirectoryEntry
會在內部使用 COM API 查詢 Active Directory 伺服器。
...
DirectoryEntry entry = new DirectoryEntry("LDAP://CN=users,DC=fabrikam,DC=com");
DirectorySearcher mySearcher = new DirectorySearcher(entry);
SearchResultCollection result = mySearcher.FindAll();
CheckUsers(result);
mySearcher.Dispose();
entry.Close();
...
...
lo_client = cl_apc_tcp_client_manager=>create( i_host = host
i_port = port
i_frame = lv_frame
i_protocol = protocol
i_ssl_id = ssl_id
i_event_handler = lo_event_handler ).
" initiate the connection setup, successful connect leads to execution of ON_OPEN
lo_client->connect( ).
...
範例 2:在正常條件下,以下修正會適當地關閉通訊端以及任何相關的串流。但是,如果對螢幕讀取或寫入資料時發生異常,通訊端物件就不會關閉。如果經常發生此狀況,系統就會耗盡所有通訊端,並且無法處理任何其他連線。
private void echoSocket(String host, int port) throws UnknownHostException, SocketException, IOException
{
Socket sock = new Socket(host, port);
BufferedReader reader = new BufferedReader(new InputStreamReader(sock.getInputStream()));
while ((String socketData = reader.readLine()) != null) {
System.out.println(socketData);
}
}
private void echoSocket(String host, int port) throws UnknownHostException, SocketException, IOException
{
Socket sock = new Socket(host, port);
BufferedReader reader = new BufferedReader(new InputStreamReader(sock.getInputStream()));
while ((String socketData = reader.readLine()) != null) {
System.out.println(socketData);
}
sock.close();
}
StreamReader
的 Finalize()
方法最終會呼叫 Close()
,但不確定何時會叫用 Finalize()
方法。事實上,不確定是否會叫用 Finalize()
。在忙碌的環境中,這可能會導致 VM 用盡它所有能使用的檔案控制碼。
private void processFile(string fName) {
StreamWriter sw = new StreamWriter(fName);
string line;
while ((line = sr.ReadLine()) != null)
processLine(line);
}
FileInputStream
的 finalize()
方法最終會呼叫 close()
,但是並不保證在叫用 finalize()
方法前會經過多少時間。因此,在繁忙的環境中,這可能會導致 JVM 用盡它所有的檔案控制碼。
private void processFile(String fName) throws FileNotFoundException, IOException {
FileInputStream fis = new FileInputStream(fName);
int sz;
byte[] byteArray = new byte[BLOCK_SIZE];
while ((sz = fis.read(byteArray)) != -1) {
processBytes(byteArray, sz);
}
}
...
CFIndex numBytes;
do {
UInt8 buf[bufferSize];
numBytes = CFReadStreamRead(readStream, buf, sizeof(buf));
if( numBytes > 0 ) {
handleBytes(buf, numBytes);
} else if( numBytes < 0 ) {
CFStreamError error = CFReadStreamGetError(readStream);
reportError(error);
}
} while( numBytes > 0 );
...
def readFile(filename: String): Unit = {
val data = Source.fromFile(fileName).getLines.mkString
// Use the data
}
...
func leak(reading input: InputStream) {
input.open()
let bufferSize = 1024
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
while input.hasBytesAvailable {
let read = input.read(buffer, maxLength: bufferSize)
}
buffer.deallocate(capacity: bufferSize)
}
...
performOperationInCriticalSection()
之前建立鎖定,但如果該方法拋出異常,就無法釋放該鎖定。
Object synchronizationObject = new Object ();
System.Threading.Monitor.Enter(synchronizationObject);
performOperationInCriticalSection();
System.Threading.Monitor.Exit(synchronizationObject);
int helper(char* fName)
{
int status;
...
pthread_cond_init (&count_threshold_cv, NULL);
pthread_mutex_init(&count_mutex, NULL);
status = perform_operation();
if (status) {
printf("%s", "cannot perform operation");
return OPERATION_FAIL;
}
pthread_mutex_destroy(&count_mutex);
pthread_cond_destroy(&count_threshold_cv);
return OPERATION_SUCCESS;
}
CALL "CBL_GET_RECORD_LOCK"
USING file-handle
record-offset
record-length
reserved
END-CALL
IF return-code NOT = 0
DISPLAY "Error!"
GOBACK
ELSE
PERFORM write-data
IF ws-status-code NOT = 0
DISPLAY "Error!"
GOBACK
ELSE
DISPLAY "Success!"
END-IF
END-IF
CALL "CBL_FREE_RECORD_LOCK"
USING file-handle
record-offset
record-length
reserved
END-CALL
GOBACK
.
performOperationInCriticalSection()
之前建立鎖定,但如果該方法拋出異常,就無法釋放該鎖定。
ReentrantLock myLock = new ReentrantLock ();
myLock.lock();
performOperationInCriticalSection();
myLock.unlock();
performOperationInCriticalSection()
之前建立鎖定,但從未釋放該鎖定。
os_unfair_lock lock1 = OS_UNFAIR_LOCK_INIT;
os_unfair_lock_lock(&lock1);
performOperationInCriticalSection();
performOperationInCriticalSection()
之前建立鎖定,但從未釋放該鎖定。
let lock1 = OSAllocatedUnfairLock()
lock1.lock()
performOperationInCriticalSection();