Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
openanolis
dragonwell8_jdk
提交
f112b5be
D
dragonwell8_jdk
项目概览
openanolis
/
dragonwell8_jdk
通知
4
Star
2
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
D
dragonwell8_jdk
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
提交
f112b5be
编写于
3月 10, 2008
作者:
M
martin
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
6625723: Excessive ThreadLocal storage used by ReentrantReadWriteLock
Reviewed-by: dholmes Contributed-by:
N
Doug Lea
<
dl@cs.oswego.edu
>
上级
15b52b61
变更
2
隐藏空白更改
内联
并排
Showing
2 changed file
with
253 addition
and
83 deletion
+253
-83
src/share/classes/java/util/concurrent/locks/ReentrantReadWriteLock.java
...es/java/util/concurrent/locks/ReentrantReadWriteLock.java
+139
-48
test/java/util/concurrent/locks/ReentrantReadWriteLock/Count.java
...a/util/concurrent/locks/ReentrantReadWriteLock/Count.java
+114
-35
未找到文件。
src/share/classes/java/util/concurrent/locks/ReentrantReadWriteLock.java
浏览文件 @
f112b5be
...
...
@@ -222,7 +222,7 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab
/** Inner class providing writelock */
private
final
ReentrantReadWriteLock
.
WriteLock
writerLock
;
/** Performs all synchronization mechanics */
private
final
Sync
sync
;
final
Sync
sync
;
/**
* Creates a new {@code ReentrantReadWriteLock} with
...
...
@@ -239,7 +239,7 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public
ReentrantReadWriteLock
(
boolean
fair
)
{
sync
=
(
fair
)
?
new
FairSync
()
:
new
NonfairSync
();
sync
=
fair
?
new
FairSync
()
:
new
NonfairSync
();
readerLock
=
new
ReadLock
(
this
);
writerLock
=
new
WriteLock
(
this
);
}
...
...
@@ -256,8 +256,8 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab
/*
* Read vs write count extraction constants and functions.
* Lock state is logically divided into two
shorts: The lower
* one representing the exclusive (writer) lock hold count,
* Lock state is logically divided into two
unsigned shorts:
*
The lower
one representing the exclusive (writer) lock hold count,
* and the upper the shared (reader) hold count.
*/
...
...
@@ -279,13 +279,6 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab
int
count
;
// Use id, not reference, to avoid garbage retention
final
long
tid
=
Thread
.
currentThread
().
getId
();
/** Decrement if positive; return previous value */
int
tryDecrement
()
{
int
c
=
count
;
if
(
c
>
0
)
count
=
c
-
1
;
return
c
;
}
}
/**
...
...
@@ -303,7 +296,7 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab
* The number of read locks held by current thread.
* Initialized only in constructor and readObject.
*/
transient
ThreadLocalHoldCounter
readHolds
;
private
transient
ThreadLocalHoldCounter
readHolds
;
/**
* The hold count of the last thread to successfully acquire
...
...
@@ -312,7 +305,17 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab
* acquire. This is non-volatile since it is just used
* as a heuristic, and would be great for threads to cache.
*/
transient
HoldCounter
cachedHoldCounter
;
private
transient
HoldCounter
cachedHoldCounter
;
/**
* firstReader is the first thread to have acquired the read lock.
* firstReaderHoldCount is firstReader's hold count.
* This allows tracking of read holds for uncontended read
* locks to be very cheap.
*/
private
final
static
long
INVALID_THREAD_ID
=
-
1
;
private
transient
long
firstReader
=
INVALID_THREAD_ID
;
private
transient
int
firstReaderHoldCount
;
Sync
()
{
readHolds
=
new
ThreadLocalHoldCounter
();
...
...
@@ -390,12 +393,25 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab
}
protected
final
boolean
tryReleaseShared
(
int
unused
)
{
HoldCounter
rh
=
cachedHoldCounter
;
Thread
current
=
Thread
.
currentThread
();
if
(
rh
==
null
||
rh
.
tid
!=
current
.
getId
())
rh
=
readHolds
.
get
();
if
(
rh
.
tryDecrement
()
<=
0
)
throw
new
IllegalMonitorStateException
();
long
tid
=
Thread
.
currentThread
().
getId
();
if
(
firstReader
==
tid
)
{
// assert firstReaderHoldCount > 0;
if
(
firstReaderHoldCount
==
1
)
firstReader
=
INVALID_THREAD_ID
;
else
firstReaderHoldCount
--;
}
else
{
HoldCounter
rh
=
cachedHoldCounter
;
if
(
rh
==
null
||
rh
.
tid
!=
tid
)
rh
=
readHolds
.
get
();
int
count
=
rh
.
count
;
if
(
count
<=
1
)
{
readHolds
.
remove
();
if
(
count
<=
0
)
throw
unmatchedUnlockException
();
}
--
rh
.
count
;
}
for
(;;)
{
int
c
=
getState
();
int
nextc
=
c
-
SHARED_UNIT
;
...
...
@@ -404,12 +420,16 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab
}
}
private
IllegalMonitorStateException
unmatchedUnlockException
()
{
return
new
IllegalMonitorStateException
(
"attempt to unlock read lock, not locked by current thread"
);
}
protected
final
int
tryAcquireShared
(
int
unused
)
{
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 2. If count saturated, throw error.
* 3. Otherwise, this thread is eligible for
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
...
...
@@ -417,23 +437,33 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
*
4. If step 3
fails either because thread
* apparently not eligible or CAS fails
,
* chain to version with full retry loop.
*
3. If step 2
fails either because thread
* apparently not eligible or CAS fails
or count
*
saturated,
chain to version with full retry loop.
*/
Thread
current
=
Thread
.
currentThread
();
int
c
=
getState
();
if
(
exclusiveCount
(
c
)
!=
0
&&
getExclusiveOwnerThread
()
!=
current
)
return
-
1
;
if
(
sharedCount
(
c
)
==
MAX_COUNT
)
throw
new
Error
(
"Maximum lock count exceeded"
);
int
r
=
sharedCount
(
c
);
if
(!
readerShouldBlock
()
&&
r
<
MAX_COUNT
&&
compareAndSetState
(
c
,
c
+
SHARED_UNIT
))
{
HoldCounter
rh
=
cachedHoldCounter
;
if
(
rh
==
null
||
rh
.
tid
!=
current
.
getId
())
cachedHoldCounter
=
rh
=
readHolds
.
get
();
rh
.
count
++;
long
tid
=
current
.
getId
();
if
(
r
==
0
)
{
firstReader
=
tid
;
firstReaderHoldCount
=
1
;
}
else
if
(
firstReader
==
tid
)
{
firstReaderHoldCount
++;
}
else
{
HoldCounter
rh
=
cachedHoldCounter
;
if
(
rh
==
null
||
rh
.
tid
!=
tid
)
cachedHoldCounter
=
rh
=
readHolds
.
get
();
else
if
(
rh
.
count
==
0
)
readHolds
.
set
(
rh
);
rh
.
count
++;
}
return
1
;
}
return
fullTryAcquireShared
(
current
);
...
...
@@ -450,20 +480,56 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab
* complicating tryAcquireShared with interactions between
* retries and lazily reading hold counts.
*/
HoldCounter
rh
=
cachedHoldCounter
;
if
(
rh
==
null
||
rh
.
tid
!=
current
.
getId
())
rh
=
readHolds
.
get
();
HoldCounter
rh
=
null
;
for
(;;)
{
int
c
=
getState
();
int
w
=
exclusiveCount
(
c
);
if
((
w
!=
0
&&
getExclusiveOwnerThread
()
!=
current
)
||
((
rh
.
count
|
w
)
==
0
&&
readerShouldBlock
()))
return
-
1
;
if
(
exclusiveCount
(
c
)
!=
0
)
{
if
(
getExclusiveOwnerThread
()
!=
current
)
//if (removeNeeded) readHolds.remove();
return
-
1
;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
}
else
if
(
readerShouldBlock
())
{
// Make sure we're not acquiring read lock reentrantly
long
tid
=
current
.
getId
();
if
(
firstReader
==
tid
)
{
// assert firstReaderHoldCount > 0;
}
else
{
if
(
rh
==
null
)
{
rh
=
cachedHoldCounter
;
if
(
rh
==
null
||
rh
.
tid
!=
tid
)
{
rh
=
readHolds
.
get
();
if
(
rh
.
count
==
0
)
readHolds
.
remove
();
}
}
if
(
rh
.
count
==
0
)
return
-
1
;
}
}
if
(
sharedCount
(
c
)
==
MAX_COUNT
)
throw
new
Error
(
"Maximum lock count exceeded"
);
if
(
compareAndSetState
(
c
,
c
+
SHARED_UNIT
))
{
cachedHoldCounter
=
rh
;
// cache for release
rh
.
count
++;
long
tid
=
current
.
getId
();
if
(
sharedCount
(
c
)
==
0
)
{
firstReader
=
tid
;
firstReaderHoldCount
=
1
;
}
else
if
(
firstReader
==
tid
)
{
firstReaderHoldCount
++;
}
else
{
if
(
rh
==
null
)
{
rh
=
cachedHoldCounter
;
if
(
rh
!=
null
&&
rh
.
tid
==
tid
)
{
if
(
rh
.
count
==
0
)
readHolds
.
set
(
rh
);
}
else
{
rh
=
readHolds
.
get
();
}
}
else
if
(
rh
.
count
==
0
)
readHolds
.
set
(
rh
);
cachedHoldCounter
=
rh
;
// cache for release
rh
.
count
++;
}
return
1
;
}
}
...
...
@@ -472,14 +538,14 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab
/**
* Performs tryLock for write, enabling barging in both modes.
* This is identical in effect to tryAcquire except for lack
* of calls to writerShouldBlock
* of calls to writerShouldBlock
.
*/
final
boolean
tryWriteLock
()
{
Thread
current
=
Thread
.
currentThread
();
int
c
=
getState
();
if
(
c
!=
0
)
{
int
w
=
exclusiveCount
(
c
);
if
(
w
==
0
||
current
!=
getExclusiveOwnerThread
())
if
(
w
==
0
||
current
!=
getExclusiveOwnerThread
())
return
false
;
if
(
w
==
MAX_COUNT
)
throw
new
Error
(
"Maximum lock count exceeded"
);
...
...
@@ -493,7 +559,7 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab
/**
* Performs tryLock for read, enabling barging in both modes.
* This is identical in effect to tryAcquireShared except for
* lack of calls to readerShouldBlock
* lack of calls to readerShouldBlock
.
*/
final
boolean
tryReadLock
()
{
Thread
current
=
Thread
.
currentThread
();
...
...
@@ -502,13 +568,24 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab
if
(
exclusiveCount
(
c
)
!=
0
&&
getExclusiveOwnerThread
()
!=
current
)
return
false
;
if
(
sharedCount
(
c
)
==
MAX_COUNT
)
int
r
=
sharedCount
(
c
);
if
(
r
==
MAX_COUNT
)
throw
new
Error
(
"Maximum lock count exceeded"
);
if
(
compareAndSetState
(
c
,
c
+
SHARED_UNIT
))
{
HoldCounter
rh
=
cachedHoldCounter
;
if
(
rh
==
null
||
rh
.
tid
!=
current
.
getId
())
cachedHoldCounter
=
rh
=
readHolds
.
get
();
rh
.
count
++;
long
tid
=
current
.
getId
();
if
(
r
==
0
)
{
firstReader
=
tid
;
firstReaderHoldCount
=
1
;
}
else
if
(
firstReader
==
tid
)
{
firstReaderHoldCount
++;
}
else
{
HoldCounter
rh
=
cachedHoldCounter
;
if
(
rh
==
null
||
rh
.
tid
!=
tid
)
cachedHoldCounter
=
rh
=
readHolds
.
get
();
else
if
(
rh
.
count
==
0
)
readHolds
.
set
(
rh
);
rh
.
count
++;
}
return
true
;
}
}
...
...
@@ -546,7 +623,20 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab
}
final
int
getReadHoldCount
()
{
return
getReadLockCount
()
==
0
?
0
:
readHolds
.
get
().
count
;
if
(
getReadLockCount
()
==
0
)
return
0
;
long
tid
=
Thread
.
currentThread
().
getId
();
if
(
firstReader
==
tid
)
return
firstReaderHoldCount
;
HoldCounter
rh
=
cachedHoldCounter
;
if
(
rh
!=
null
&&
rh
.
tid
==
tid
)
return
rh
.
count
;
int
count
=
readHolds
.
get
().
count
;
if
(
count
==
0
)
readHolds
.
remove
();
return
count
;
}
/**
...
...
@@ -557,6 +647,7 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab
throws
java
.
io
.
IOException
,
ClassNotFoundException
{
s
.
defaultReadObject
();
readHolds
=
new
ThreadLocalHoldCounter
();
firstReader
=
INVALID_THREAD_ID
;
setState
(
0
);
// reset to unlocked state
}
...
...
test/java/util/concurrent/locks/ReentrantReadWriteLock/Count.java
浏览文件 @
f112b5be
/*
* Copyright 2005-200
6
Sun Microsystems, Inc. All Rights Reserved.
* Copyright 2005-200
8
Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
...
...
@@ -23,21 +23,89 @@
/*
* @test
* @bug 6207928 6328220 6378321
* @bug 6207928 6328220 6378321
6625723
* @summary Recursive lock invariant sanity checks
* @author Martin Buchholz
*/
import
java.io.*
;
import
java.util.*
;
import
java.util.concurrent.*
;
import
java.util.concurrent.locks.*
;
// I am the Cownt, and I lahve to cownt.
public
class
Count
{
private
static
void
realMain
(
String
[]
args
)
throws
Throwable
{
final
ReentrantLock
rl
=
new
ReentrantLock
();
final
ReentrantReadWriteLock
rwl
=
new
ReentrantReadWriteLock
();
final
Random
rnd
=
new
Random
();
void
lock
(
Lock
lock
)
{
try
{
switch
(
rnd
.
nextInt
(
4
))
{
case
0
:
lock
.
lock
();
break
;
case
1
:
lock
.
lockInterruptibly
();
break
;
case
2
:
check
(
lock
.
tryLock
());
break
;
case
3
:
check
(
lock
.
tryLock
(
45
,
TimeUnit
.
MINUTES
));
break
;
}
}
catch
(
Throwable
t
)
{
unexpected
(
t
);
}
}
void
test
(
String
[]
args
)
throws
Throwable
{
for
(
boolean
fair
:
new
boolean
[]
{
true
,
false
})
for
(
boolean
serialClone
:
new
boolean
[]
{
true
,
false
})
{
testReentrantLocks
(
fair
,
serialClone
);
testConcurrentReadLocks
(
fair
,
serialClone
);
}
}
void
testConcurrentReadLocks
(
final
boolean
fair
,
final
boolean
serialClone
)
throws
Throwable
{
final
int
nThreads
=
10
;
final
CyclicBarrier
barrier
=
new
CyclicBarrier
(
nThreads
);
final
ExecutorService
es
=
Executors
.
newFixedThreadPool
(
nThreads
);
final
ReentrantReadWriteLock
rwl
=
serialClone
?
serialClone
(
new
ReentrantReadWriteLock
(
fair
))
:
new
ReentrantReadWriteLock
(
fair
);
for
(
int
i
=
0
;
i
<
nThreads
;
i
++)
{
es
.
submit
(
new
Runnable
()
{
public
void
run
()
{
try
{
int
n
=
5
;
for
(
int
i
=
0
;
i
<
n
;
i
++)
{
barrier
.
await
();
equal
(
rwl
.
getReadHoldCount
(),
i
);
equal
(
rwl
.
getWriteHoldCount
(),
0
);
check
(!
rwl
.
isWriteLocked
());
equal
(
rwl
.
getReadLockCount
(),
nThreads
*
i
);
barrier
.
await
();
lock
(
rwl
.
readLock
());
}
for
(
int
i
=
0
;
i
<
n
;
i
++)
{
rwl
.
readLock
().
unlock
();
barrier
.
await
();
equal
(
rwl
.
getReadHoldCount
(),
n
-
i
-
1
);
equal
(
rwl
.
getReadLockCount
(),
nThreads
*(
n
-
i
-
1
));
equal
(
rwl
.
getWriteHoldCount
(),
0
);
check
(!
rwl
.
isWriteLocked
());
barrier
.
await
();
}
THROWS
(
IllegalMonitorStateException
.
class
,
new
F
(){
void
f
(){
rwl
.
readLock
().
unlock
();}},
new
F
(){
void
f
(){
rwl
.
writeLock
().
unlock
();}});
barrier
.
await
();
}
catch
(
Throwable
t
)
{
unexpected
(
t
);
}}});}
es
.
shutdown
();
check
(
es
.
awaitTermination
(
10
,
TimeUnit
.
SECONDS
));
}
void
testReentrantLocks
(
final
boolean
fair
,
final
boolean
serialClone
)
throws
Throwable
{
final
ReentrantLock
rl
=
serialClone
?
serialClone
(
new
ReentrantLock
(
fair
))
:
new
ReentrantLock
(
fair
);
final
ReentrantReadWriteLock
rwl
=
serialClone
?
serialClone
(
new
ReentrantReadWriteLock
(
fair
))
:
new
ReentrantReadWriteLock
(
fair
);
final
int
depth
=
10
;
equal
(
rl
.
isFair
(),
fair
);
equal
(
rwl
.
isFair
(),
fair
);
check
(!
rl
.
isLocked
());
check
(!
rwl
.
isWriteLocked
());
check
(!
rl
.
isHeldByCurrentThread
());
...
...
@@ -50,28 +118,11 @@ public class Count {
equal
(
rwl
.
getReadHoldCount
(),
i
);
equal
(
rwl
.
getWriteHoldCount
(),
i
);
equal
(
rwl
.
writeLock
().
getHoldCount
(),
i
);
switch
(
i
%
4
)
{
case
0
:
rl
.
lock
();
rwl
.
writeLock
().
lock
();
rwl
.
readLock
().
lock
();
break
;
case
1
:
rl
.
lockInterruptibly
();
rwl
.
writeLock
().
lockInterruptibly
();
rwl
.
readLock
().
lockInterruptibly
();
break
;
case
2
:
check
(
rl
.
tryLock
());
check
(
rwl
.
writeLock
().
tryLock
());
check
(
rwl
.
readLock
().
tryLock
());
break
;
case
3
:
check
(
rl
.
tryLock
(
454
,
TimeUnit
.
MILLISECONDS
));
check
(
rwl
.
writeLock
().
tryLock
(
454
,
TimeUnit
.
NANOSECONDS
));
check
(
rwl
.
readLock
().
tryLock
(
454
,
TimeUnit
.
HOURS
));
break
;
}
equal
(
rl
.
isLocked
(),
i
>
0
);
equal
(
rwl
.
isWriteLocked
(),
i
>
0
);
lock
(
rl
);
lock
(
rwl
.
writeLock
());
lock
(
rwl
.
readLock
());
}
for
(
int
i
=
depth
;
i
>
0
;
i
--)
{
...
...
@@ -95,20 +146,48 @@ public class Count {
rwl
.
writeLock
().
unlock
();
rl
.
unlock
();
}
THROWS
(
IllegalMonitorStateException
.
class
,
new
F
(){
void
f
(){
rl
.
unlock
();}},
new
F
(){
void
f
(){
rwl
.
readLock
().
unlock
();}},
new
F
(){
void
f
(){
rwl
.
writeLock
().
unlock
();}});
}
//--------------------- Infrastructure ---------------------------
static
volatile
int
passed
=
0
,
failed
=
0
;
static
void
pass
()
{
passed
++;}
static
void
fail
()
{
failed
++;
Thread
.
dumpStack
();}
static
void
fail
(
String
msg
)
{
System
.
out
.
println
(
msg
);
fail
();}
static
void
unexpected
(
Throwable
t
)
{
failed
++;
t
.
printStackTrace
();}
static
void
check
(
boolean
cond
)
{
if
(
cond
)
pass
();
else
fail
();}
static
void
equal
(
Object
x
,
Object
y
)
{
volatile
int
passed
=
0
,
failed
=
0
;
void
pass
()
{
passed
++;}
void
fail
()
{
failed
++;
Thread
.
dumpStack
();}
void
fail
(
String
msg
)
{
System
.
err
.
println
(
msg
);
fail
();}
void
unexpected
(
Throwable
t
)
{
failed
++;
t
.
printStackTrace
();}
void
check
(
boolean
cond
)
{
if
(
cond
)
pass
();
else
fail
();}
void
equal
(
Object
x
,
Object
y
)
{
if
(
x
==
null
?
y
==
null
:
x
.
equals
(
y
))
pass
();
else
fail
(
x
+
" not equal to "
+
y
);}
public
static
void
main
(
String
[]
args
)
throws
Throwable
{
try
{
realMain
(
args
);}
catch
(
Throwable
t
)
{
unexpected
(
t
);}
new
Count
().
instanceMain
(
args
);}
void
instanceMain
(
String
[]
args
)
throws
Throwable
{
try
{
test
(
args
);}
catch
(
Throwable
t
)
{
unexpected
(
t
);}
System
.
out
.
printf
(
"%nPassed = %d, failed = %d%n%n"
,
passed
,
failed
);
if
(
failed
>
0
)
throw
new
AssertionError
(
"Some tests failed"
);}
abstract
class
F
{
abstract
void
f
()
throws
Throwable
;}
void
THROWS
(
Class
<?
extends
Throwable
>
k
,
F
...
fs
)
{
for
(
F
f
:
fs
)
try
{
f
.
f
();
fail
(
"Expected "
+
k
.
getName
()
+
" not thrown"
);}
catch
(
Throwable
t
)
{
if
(
k
.
isAssignableFrom
(
t
.
getClass
()))
pass
();
else
unexpected
(
t
);}}
static
byte
[]
serializedForm
(
Object
obj
)
{
try
{
ByteArrayOutputStream
baos
=
new
ByteArrayOutputStream
();
new
ObjectOutputStream
(
baos
).
writeObject
(
obj
);
return
baos
.
toByteArray
();
}
catch
(
IOException
e
)
{
throw
new
RuntimeException
(
e
);
}}
static
Object
readObject
(
byte
[]
bytes
)
throws
IOException
,
ClassNotFoundException
{
InputStream
is
=
new
ByteArrayInputStream
(
bytes
);
return
new
ObjectInputStream
(
is
).
readObject
();}
@SuppressWarnings
(
"unchecked"
)
static
<
T
>
T
serialClone
(
T
obj
)
{
try
{
return
(
T
)
readObject
(
serializedForm
(
obj
));
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录