当前位置:网站首页>Analysis of reentrantlock source code of AQS
Analysis of reentrantlock source code of AQS
2022-06-09 05:14:00 【smartjiang-java】
List of articles
1:AQS Basic knowledge of
AQS, The full name is AbstractQueuedSynchronizer , abstract class , It is the framework of blocking lock and related synchronizer tools . Has the following characteristics :
1. use state Property to represent the state of the resource ( It can be divided into exclusive mode and shared mode ), Subclasses need to define how to maintain this state , Controls how locks are acquired and released
1. getState - obtain state state
2. setState - Set up state state
3. compareAndSetState - cas Mechanism settings state state
4. Exclusive mode is that only one thread can access the resource , Shared mode allows multiple threads to access resources
2. Provides the basis for FIFO Waiting queue , Be similar to Monitor Of EntryList
3. Conditional variables to achieve waiting 、 Wake up mechanism , Support multiple conditional variables , Be similar to Monitor Of WaitSet
Subclasses mainly need to implement AQS Here are some ways , Default throw UnsupportedOperationException
- tryAcquire
- tryRelease
- tryAcquireShared
- tryReleaseShared
- isHeldExclusively
2: Lock principle
because ReentrantLock Two synchronizers are provided , Achieve fair lock FairSync And unfair lock NonfairSync , Default is unfair lock .
2.1: Not fair lock NonfairSync
2.1.1: Lock principle
Suppose there are only thread-0 Thread to lock , So enter lock() Method
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
// Come here first through cas Try to state The value of is determined by 0 become 1 : Modification successful , Set the owner of the lock to thread-0
// thus it can be seen , The first incoming thread only needs to try once to acquire the lock successfully , Locking success
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
Here comes again thread-1 Threads , Also go to lock() Method
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
// Come here first through cas Try to state The value of is determined by 0 become 1 , So because state The value of has been thread-0 It has been modified into 1 ,
// So the attempt to replace here is bound to fail , Enter into acquire(1);
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
Get into acquire(1) Method
public final void acquire(int arg) {
// Try calling tryAcquire() Method , Get the lock again , This is the second acquisition
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
Enter into tryAcquire(1) Method , find NonfairSync Implementation method
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
// Go to this method , And then call nonfairTryAcquire(1)
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
Enter into nonfairTryAcquire(1) Method
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// Judge again state Is it 0, If it is 0, Then pass again cas Attempt to acquire lock . This is obviously not the place to go
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// state Not for 0, This indicates that a thread has been locked , Determine whether the locking thread is the current thread , Lock reentry ,state Add 1 . Obviously not here
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// Come here , Return here false
return false;
}
Back to acquire(1) Method
public final void acquire(int arg) {
// tryAcquire(1) return fasle, !tryAcquire(arg) Namely true , Enter into addWaiter(Node.EXCLUSIVE) Method
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
Get into addWaiter(Node.EXCLUSIVE) Method
private Node addWaiter(Node mode) {
// A new node is created for the current thread node
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// Judge AQS If the end node of the waiting queue of is not null, Then put the new node Node passing cas Set as tail node , And back to .
// obviously , We haven't let the thread go in the waiting queue yet , Then the beginning and end nodes of the queue are null, Obviously not here
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// Enter into enq(node) Method
enq(node);
return node;
}
}
Enter into enq(node) Method
private Node enq(final Node node) {
// Here we enter the cycle operation
for (;;) {
Node t = tail;
// First cycle after entering , If the tail node is null, that Create a New nodes , We call it a subatomic node , adopt cas Replace with the head node
// hold The sub element node is set as the tail node . At this time, the sub element node is both the head node and the tail node
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
// The second cycle , The tail node is a sub element node , No null, Try to use cas hold node The node is set as the tail node .
// here , The head node is a sub element node , No node is node node , Out of the loop .
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
Back to addWaiter(Node.EXCLUSIVE) Method
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
// Returns... Based on the node created by the current thread
return node;
}
}
Get into acquire(1) Method
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
// Enter into acquireQueued(node,1) Method
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
Enter into acquireQueued(node,1) Method
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// Enter the loop
for (;;) {
// find node The previous node of the node , This we know is a sub - element node p
final Node p = node.predecessor();
// If p Head node , that The current thread attempts to acquire the lock again , The third attempt to acquire the lock . It must have been a failure
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// Enter into shouldParkAfterFailedAcquire(p,node) Method
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
Enter into shouldParkAfterFailedAcquire(p,node) Method
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// Get the initialized state value of the sub element node 0
int ws = pred.waitStatus;
// Judge whether the value is -1, Obviously not , be equal to 0
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// Try to use cas Set the state value of the sub - element node from 0 become -1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
// return false
return false;
}
go back to acquireQueued(node,1) Method
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// Enter the loop
for (;;) {
// The second cycle : find node The previous node of the node , This we know is a sub - element node p
final Node p = node.predecessor();
// If p Head node , that The current thread attempts to acquire the lock again , The fourth attempt to acquire the lock . It must have been a failure
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// shouldParkAfterFailedAcquire(p, node) Return to false, Enter the second loop
// The second cycle goes into shouldParkAfterFailedAcquire(p, node) Method
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
Enter into shouldParkAfterFailedAcquire(p, node) Method
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// Get the sub element node state value -1
int ws = pred.waitStatus;
// Judge whether the value is -1, Obviously , return true
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
go back to acquireQueued(node,1) Method
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// shouldParkAfterFailedAcquire(p, node) Return to true, Get into parkAndCheckInterrupt() Method
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
Get into parkAndCheckInterrupt() Method
private final boolean parkAndCheckInterrupt() {
// Block the current thread , And clear the break mark
LockSupport.park(this);
return Thread.interrupted();
}
go back to acquireQueued(node,1) Method
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// shouldParkAfterFailedAcquire(p, node) Return to true,parkAndCheckInterrupt() Return to true,
// The thread is blocked , If the middle is interrupted , that interrupted by true, Just set the break flag to true, No other operation .
// Will still reside in AQS In line , Wait for the lock to be acquired before continuing operation ( Continue operation , The break mark is marked as true)
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
Here comes again thread-2 Threads , Also go to lock() Method
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
// Come here first through cas Try to state The value of is determined by 0 become 1 , So because state The value of has been thread-0 It has been modified into 1 ,
// So the attempt to replace here is bound to fail , Enter into acquire(1);
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
Get into acquire(1) Method
public final void acquire(int arg) {
// Try calling tryAcquire() Method , Get the lock again , This is the second acquisition
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
Enter into tryAcquire(1) Method , find NonfairSync Implementation method
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
// Go to this method , And then call nonfairTryAcquire(1)
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
Enter into nonfairTryAcquire(1) Method
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// Judge again state Is it 0, If it is 0, Then pass again cas Attempt to acquire lock . This is obviously not the place to go
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// state Not for 0, This indicates that a thread has been locked , Determine whether the locking thread is the current thread , Lock reentry ,state Add 1 . Obviously not here
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// Come here , Return here false
return false;
}
Back to acquire(1) Method
public final void acquire(int arg) {
// tryAcquire(1) return fasle, !tryAcquire(arg) Namely true , Enter into addWaiter(Node.EXCLUSIVE) Method
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
private Node addWaiter(Node mode) {
// A new node is created for the current thread node
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// Judge AQS If the end node of the waiting queue of is not null, Then put the new node Node passing cas Set as tail node , And back to .
// obviously , The tail node is thread-1, No null, Replacement successful , return node
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// Enter into enq(node) Method
enq(node);
return node;
}
}
Back to acquire(1) Method , It's almost the same later , Change the state of the previous node of the node from 0 become -1, And block , Interrupt tasks
public final void acquire(int arg) {
// Enter into acquireQueued(node, 1) Method
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
2.1.1: Unlock the principle
When the thread occupying the lock completes its work , call unLock() Method
public void unlock() {
// Calling the synchronizer release() Method
sync.release(1);
}
Calling the synchronizer release(1) Method
public final boolean release(int arg) {
// Get into tryRelease(1) Method
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
Get into tryRelease(1) Method
protected final boolean tryRelease(int releases) {
// state -1
int c = getState() - releases;
// If the current thread is not a locked thread , Throw an exception
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// If state -1 be equal to 0 , that The lock identification is set to null , return true
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// This is based on state To determine the reentry of the lock , If re entered by the lock , There is only one layer of solution here , It needs to be solved several times
setState(c);
return free;
}
Back to the synchronizer release(1) Method
public final boolean release(int arg) {
// tryRelease(1) return true, unlocked
if (tryRelease(arg)) {
Node h = head;
// Determine whether the head node of the waiting queue is not null, also state The value is not 0 , We know it is -1 , Enter into unparkSuccessor(h) Method
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
Enter into unparkSuccessor(h) Method
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
// Judge the head node ( Yayuan ) The state of , cas Set first as 0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// Find the next node after the head node , waitStatus yes -1 No 0
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// If the following node of the head node is not null, Wake up the first thread behind the header node
if (s != null)
LockSupport.unpark(s.thread);
}
If there is no new thread to compete with , that thread-1 go back to acquireQueued(node,1) Method
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// find thread-1 The previous node of
final Node p = node.predecessor();
// If the previous node is head node , Then let thread-1 Try competing locks , If successful ,
// take thread-1 The node Set as head node
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
2.1: Fair lock FairSync
Not fair lock : When trying to acquire the lock , Direct use cas Competitive lock , Don't check AQS queue
Fair lock : When trying to acquire the lock , Check it out first AQS Whether there are waiting threads in the queue , If not, just compete ; Some words . Go into the waiting queue
Fair lock :
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// First, check whether there is a precursor node in the waiting queue , No more competition . If there is , Enter the waiting queue
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
Not fair lock :
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// Try to use cas Get the lock , No inspection AQS queue , Reflects the unfairness
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
3: Interrupted principle
ReentrantLock The default is non interruptible , Interruptible needs to call lockInterruptibly() Method
public void lockInterruptibly() throws InterruptedException {
// Get into acquireInterruptibly(1) Method
sync.acquireInterruptibly(1);
}
Get into acquireInterruptibly(1) Method
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// If you don't get the lock , Interruptible locking process , Enter into doAcquireInterruptibly(1) Method
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
Enter into doAcquireInterruptibly(1) Method
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
// Judge AQS If the end node of the waiting queue of is not null, Then put the new node Node passing cas Set as tail node , And back to .
// If it is the first queue element , The head node is a sub element node . This has been analyzed before
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
// Get the previous node of the current node
final Node p = node.predecessor();
// Determine whether the previous node is a header node , Then try to get the lock
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
// The current thread is blocked , But if it is interrupted during blocking , Throw an exception .
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
4: The principle of conditional variable implementation
4.1: Create conditional variables
newCondition() Method , The return value is Condition object , yes AQS The inner class of
public Condition newCondition() {
return sync.newCondition();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
so : Each condition variable actually corresponds to a waiting queue , The implementation class is ConditionObject Maintain a two-way linked list , As not meeting the conditions , Where the threads rest
4.2: Variables that do not satisfy the condition
public final void await() throws InterruptedException {
// If interrupted , Throw an exception
if (Thread.interrupted())
throw new InterruptedException();
// Add the current thread to Condition Waiting queue , New node's waitStatus Set to -2, Returns the current node
Node node = addConditionWaiter();
// Release the lock held by the current thread , Wake up the second node after the head node in the waiting queue to compete for the lock
int savedState = fullyRelease(node);
int interruptMode = 0;
// If the node's state be equal to -2 Or the precursor node is null , that park live , At this point, the thread is park live
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
4.3: Satisfy conditional variables
public final void signal() {
// Determine whether the current node is the lock holder , If not, throw an exception
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// Find the first node in the condition variable , And wake up
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
Enter into doSignal() Method
private void doSignal(Node first) {
do {
// If the next node of the head node is null, Then the linked list header node is set to null
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
//ps: Here is the transfer node , The possibility of failure : Be interrupted , Timeout etc.
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
Enter into transferForSignal() Method
final boolean transferForSignal(Node node) {
// use cas Try to change the state of the node from -2 become 0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// Put the node in AQS End of queue , Return the original tail node to
Node p = enq(node);
int ws = p.waitStatus;
// Return the status of the node through cas from 0 become -1, Make it qualified to wake up
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
边栏推荐
- [005] [esp32 Development Notes] ADF basic framework
- Baidu AI Cloud's revenue in 2021 was RMB 15.1 billion, a year-on-year increase of 64%
- The half year revenue of mushroom street was 168million yuan: a year-on-year decrease of 29% and an operating loss of 240million yuan
- EF core uses scaffold dbcontext to generate model from database
- 聊聊保证线程安全的10个小技巧
- Myql error expression 1 of select list is not in group by claim and contains nonaggregated column
- Product weekly report issue 29 | creation center optimization: the sending assistant adds the quality score detection function, and the blog adds the historical version of the content
- June 2022 Tsinghua Management Tsinghua University Ning Xiangdong
- Design idea of three party account authorization login system
- 基础集群部署 - kubernetes-simple
猜你喜欢

Apache Devlake 代码库导览

数据库的三大范式

AI video cloud: a good wife in the era of we media

ps如何给图像加白边

Marathon环境下fastdfs和vsftpd和miniIo文件服务器搭建的方式

Camtasia studio2022 free key serial number installation trial detailed graphic tutorial

Why do I need a thread pool? What is pooling technology?

优视慕V8投影仪,打开高清新“视”界

LRU cache

Transformer里面的缓存机制
随机推荐
[004] [esp32 Development Notes] audio development framework ADF environment construction - based on esp-idf
Transformer里面的缓存机制
聊聊保证线程安全的10个小技巧
拉下新项目代码爆红
How WPS ppt pictures come out one by one
故障排查:阿里云轻量应用服务器中的MySQL容器自行停止
Product weekly report issue 29 | creation center optimization: the sending assistant adds the quality score detection function, and the blog adds the historical version of the content
MQ message loss, message consistency, repeated consumption solution
The 27th issue of product weekly report | members' new interests of black users; CSDN app v5.1.0 release
爬取html入mysql插入失败
1030. distance sequence matrix cells ●
2021 national vocational skills competition Liaoning "Cyberspace Security Competition" and its analysis (ultra detailed)
TCP explanation (Wireshark packet capturing analysis TCP three handshakes and TCP four waves)
基础集群部署 - kubernetes-simple
P1743 Audiophobia
ETF operation practice record: March 2, 2022
[005] [esp32 Development Notes] ADF basic framework
Li Kou today's question -1037 Effective boomerang
Openstack Learning Series 12: installing CEPH and docking openstack
CLCNet:用分类置信网络重新思考集成建模(附源代码下载)