【面试专栏】JAVA锁机制
1. 悲观锁 / 乐观锁
在Java和数据库中都存在悲观锁和乐观锁的应用。Mysql锁机制中的悲观锁和乐观锁请查看:
Mysql锁机制--悲观锁和乐观锁
悲观锁:在获得数据时先加锁,只到数据操作(更新)完成,确保不会被其他线程所影响。例如:Java中synchronized关键字和Lock的实现类都是悲观锁。
乐观锁:在获得数据时不会加锁,而是在操作数据时判断数据是否被修改过,因此可能会出现线程抢占的情况。当数据未被更新时,直接更新数据;当数据被更新后,抛出异常或通过程序自旋重试解决。
从上述中可以得出:
悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确性;
乐观锁适合读操作多的场景,不加锁可以使其读操作的性能大幅提升。
悲观锁和乐观锁使用示例:
// 悲观锁 - synchronized
public synchronized void sync() {
// ...
}
// 悲观锁 - Lock
private Lock lock = new ReentrantLock();// 多线程公用一把锁
public void lock() {
try {
// 加锁
lock.lock();
// ...
} finally {
// 释放锁
lock.unlock();
}
}
// 乐观锁 - AtomicInteger
private AtomicInteger atomicInteger = new AtomicInteger();
public void plus() {
atomicInteger.getAndIncrement();
}
从上述示例中可以看出,悲观锁都是显式加锁后再操作同步资源,乐观锁直接操作同步资源。乐观锁的主要实现是CAS
原理,请查看:
【面试专栏】JAVA CAS(Conmpare And Swap)原理
2. 自旋锁 / 适应性自旋锁
自旋锁:Java线程的阻塞、挂起和唤醒需要操作系统CPU状态切换才可完成,如果同步代码非常简单且耗时很短,则可能CPU状态切换的耗时比同步代码的耗时更长,无疑浪费了时间和性能。因此,为了能让线程暂停等待而不阻塞,则需要线程进行自旋,即无限循环当满足某种条件时跳出循环再继续执行。这种避免线程切换的开销方式,就是自旋锁。
自旋锁的主要实现也是CAS
原理。AtomicInteger的自增调用的Unsafe中getAndAddInt方法,查看OpenJDK 8中Unsafe中getAndAddInt方法源码:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatitle(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
从上述代码中可以看出,do-while就是自旋操作。当修改失败后,通过循环自旋直到修改成功。
自旋锁虽然避免了线程切换的问题,但是会占用CPU时间。如果自旋时间很短,则是非常好的解决方法;当自旋时间过长时,无疑会浪费CPU资源。因此,自旋的次数要有一定的限制,当超过限制后则直接挂起线程等待。JDK1.6中默认开启自旋次数(10),并且引入了自适应的自旋锁(适应性自旋锁)。
适应性自旋锁:适应其实意味着自旋的次数不再受限,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,线程通过自旋成功获得锁,并且持有锁的线程正在运行中,那么虚拟机认为本次自旋很有可能再次成功,因此允许本线程自旋等待。如果对于某个锁,自旋成功几率很小,那在以后尝试获取该对象锁时可能不再自旋,直接阻塞线程,避免浪费CPU资源。
3. 无锁 / 偏向锁 / 轻量级锁 / 重量级锁
JDK1.6之前synchronized是重量级锁,效率很低,JDK1.6后,对其进行优化,引入了无锁、偏向锁、轻量级锁、重量级锁。
无锁:CAS
原理。不对资源进行加锁,所有线程都能访问并修改同一资源,但同时只有一个线程能修改成功。修改操作循环尝试获得锁,如果可以获得,则直接操作,否则继续循环。
偏向锁:指一段同步代码一直被一个线程访问,那么该线程会自动获取锁,降低获取锁的代价。当其他线程尝试竞争偏向锁时,持有偏向锁的线程才释放锁,线程不会主动释放偏向锁。
轻量级锁:指当锁为偏向锁时有其他线程尝试获得锁,偏向锁会升级为轻量级锁,其他线程通过自旋方式尝试获取锁,但不会阻塞,从而提高性能。若当前只有一个等待线程,则该线程通过自旋尝试等待。但当自旋超过一定的次数,轻量级锁又升级为重量级锁。
重量级锁:等待锁的其他线程都会进入阻塞状态。
以为其实是锁升级过程:无锁 → 偏向锁 → 轻量级锁 → 重量级锁。
4. 公平锁 / 非公平锁
公平锁:线程按照顺序依次获取锁。优点:等待线程都会获得锁;缺点:其他等待线程都会阻塞,线程阻塞唤醒引起的CPU状态切换从而消耗资源。
非公平锁:先尝试获得锁,获取失败则采用公平锁方式。优点:减少线程阻塞唤醒引起的CPU状态切换从而消耗资源,提高吞吐量;缺点:等待线程可能很长时间或永远获得不到锁。
分析ReentrantLock部分源码:
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
//......
}
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
//......
}
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
//......
}
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//......
}
从源码可以看出,ReentrantLock中有一个内部类Sync,加锁和释放锁都是由Sync实现的:公平锁FairSync、非公平锁NonfairSync。通过构造函数可以看出,ReentrantLock默认使用公平锁,当带参构造传递false时,获得非公平锁。 再查看公平锁和非公平锁源码:
// 公平锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
// 非公平锁
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
可以看出,公平锁和非公平锁的区别在于,是否添加了hasQueuedPredecessors
(是否存在前置任务),再查看hasQueuedPredecessors
源码:
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
可以看出,此方法判断当前线程是否是同步队列中的第一个。因此得出,公平锁先判断是否为同步队列中第一个,则获得锁,否则排队等待;非公平锁先抢占,抢占成功,则获得锁,抢占失败,则排队。
5. 可重入锁 / 非可重入锁
可重入锁:又名递归锁。指同一线程在外层方法中获取锁,内层函数自动获取锁(同一对象或Class)。优点:避免死锁。
Java中ReentrantLock和synchronized都是可重入锁,为例分析:
/**
* 同步方法一
*/
public synchronized void method1() {
// ......
method2();
// ......
}
/**
* 同步方法二
*/
public synchronized void method2() {
// ......
}
在上述方法中,method1和都被synchronized修饰,当调用method1时,获取当前锁,method1调用method2,根据可重入锁规则,自动获取锁。
假如使用非可重入锁,当进入method1获取锁,在进入method2之前,先要释放当前对象锁,但是当前对象锁被当前线程所持有,无法释放,则出现死锁。
可重入锁ReentrantLock和非可重入锁NonReentrantLock都继承AQS(AbstractQueuedSynchronizer),AQS中存在一个属性state来表明同步状态(可重入锁时表示重入次数)。
public class ReentrantLock implements Lock, java.io.Serializable {
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
......
}
......
}
public final class NonReentrantLock extends AbstractQueuedSynchronizer implements Lock {
......
}
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
/**
* The synchronization state.
*/
private volatile int state;
/**
* Returns the current value of synchronization state.
* This operation has memory semantics of a {@code volatile} read.
* @return current state value
*/
protected final int getState() {
return state;
}
/**
* Sets the value of synchronization state.
* This operation has memory semantics of a {@code volatile} write.
* @param newState the new state value
*/
protected final void setState(int newState) {
state = newState;
}
/**
* Atomically sets synchronization state to the given updated
* value if the current state value equals the expected value.
* This operation has memory semantics of a {@code volatile} read
* and write.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that the actual
* value was not equal to the expected value.
*/
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
}
通过源码分析下为什么非可重入锁在重复调用同步资源时会出现死锁。首先查看可重入锁获取锁源码:
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
可以看出,可重入锁尝试获取锁时,先获取同步次数state,当state=0时,表示没有其他线程执行当前同步方法,则修改状态state=1,表示正在执行。当state!=0时,判断当前线程是否已经获得当前锁,若是则state+1表示再次获取锁。
查看非重入锁获取锁源码:
protected boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
owner = Thread.currentThread();
return true;
}
return false;
}
可以看出,非重入锁直接获取当前锁,当state !=0时,获取失败,进入死锁。
再查看释放锁的过程。首先查看可重入锁释放锁源码:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
可以看出,可重入锁在当前线程独占所有锁时,判断state-1=0,若true则表明当前线程所重复获取锁已经全部执行完成,释放锁。
查看非重入锁释放锁源码:
protected boolean tryRelease(int releases) {
if (Thread.currentThread() != owner) {
throw new IllegalMonitorStateException();
}
owner = null;
setState(0);
return true;
}
可以看出,非重入锁在当前线程独占所有锁时,直接将state置为0,释放锁。
6. 独享锁 / 共享锁
共享锁:又名排它锁。指当前锁只能被一个线程所持有。例如线程T1对同步资源A添加独享锁后,其他线程不能对同步资源A添加任何锁。获得独享锁的线程既能读数据也能写数据。
共享锁:指当前线程可以被多个线程所持有。例如线程T1对同步资源A添加共享锁后,其他线程可对该资源添加共享锁,但不能添加独享锁。获得共享锁的线程只能读数据,不能写数据。
通过ReentrantReadWriteLock源码来分析独享锁和共享锁:
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
private static final long serialVersionUID = -6992448646407690164L;
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;
/**
* Creates a new {@code ReentrantReadWriteLock} with
* default (nonfair) ordering properties.
*/
public ReentrantReadWriteLock() {
this(false);
}
/**
* Creates a new {@code ReentrantReadWriteLock} with
* the given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
/**
* Synchronization implementation for ReentrantReadWriteLock.
* Subclassed into fair and nonfair versions.
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
......
}
......
}
/**
* The lock returned by method {@link ReentrantReadWriteLock#readLock}.
*/
public static class ReadLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -5992448646407690164L;
private final Sync sync;
/**
* Constructor for use by subclasses
*
* @param lock the outer lock object
* @throws NullPointerException if the lock is null
*/
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
......
}
/**
* The lock returned by method {@link ReentrantReadWriteLock#writeLock}.
*/
public static class WriteLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -4992448646407690164L;
private final Sync sync;
/**
* Constructor for use by subclasses
*
* @param lock the outer lock object
* @throws NullPointerException if the lock is null
*/
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
......
}
可以看出ReentrantReadWriteLock中有两种锁:读锁(ReadLock)、写锁(WriteLock)。读锁和写锁都是由Sync实现的,Sync是AQSAbstractQueuedSynchronizer)的子类。AQS中存在属性state来表明同步状态(0/1)或者持有锁的数量,但ReentrantReadWriteLock中存在两种锁,因此需要将state分割,来表明读锁和写锁的数量。AQS中state为int类型,占4个字节,即32位,因此将state分割为高16位(读锁数量)和低16位(写锁数量)。
首先查看写锁添加锁的源码:
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
可以看出,在尝试获取锁时先获取锁的总数c,通过总数c再获取了写锁的数量w。
首先判断c不等于0,即已经有线程已经获得了锁;当写锁数w等于0(即存在读锁)或者当前线程不是该锁的持有者,则获取失败;当写锁数量超过MAX_COUNT(2的16次方 - 1),则抛出异常;否则获取锁成功。
当锁的总数c等于0,即没有线程获取锁;当当前线程需要阻塞或者通过CAS增加写锁数量失败时,获取所失败。
否则,设置当前锁的持有者为当前线程,返回true。
由上述程序可以看出,当存在读锁时,写锁获取失败。因此需要等待所有持有的读锁全部释放,写锁才能获取成功,当写锁持有时,所有的读写锁均被阻塞。写锁释放与重入锁释放过程基本类似,每次释放均减少写锁数量,当写锁数量为0时表示写锁全部释放,此时等待的读写线程才能够继续访问读写锁,同时前次写线程的修改操作对后续读写线程均可见。
查看读锁添加锁的源码:
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 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.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 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;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
可以看出,在读锁尝试获取锁时先判断是否存在另一个线程持有写锁,是则获取失败;如果为获取写锁,则根据CAS原理增加读锁数量,成功则获取锁成功。
通过分析读写锁获取锁源码可以得出,读读的过程共享,而读写、写读、写写的过程互斥。
再查看下公平锁和非公平锁获取锁源码:
// 公平锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
// 非公平锁
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
可以看出,公平锁和非公平锁获取锁时都会判断是否当前线程为当前锁的持有者,否则不能获取锁,即表明公平锁和非公平锁获取的都是独享锁。
【面试专栏】JAVA锁机制的更多相关文章
- 转 : 深入解析Java锁机制
深入解析Java锁机制 https://mp.weixin.qq.com/s?__biz=MzU0OTE4MzYzMw%3D%3D&mid=2247485524&idx=1&s ...
- Java 锁机制总结
锁的种类 独享锁 VS 共享锁 独享锁:锁只能被一个线程持有(synchronized) 共享锁:锁可以被多个程序所持有(读写锁) 乐观锁 VS 悲观锁 乐观锁:每次去拿数据的时候都乐观地认为别人不会 ...
- java锁机制的面试题
java锁机制的面试题 1.ABA问题 2.CAS乐观锁 3.synchronize实现原理 4.synchronize与lock的区别 5.volatile实现原理 6.乐观锁的业务场景及实现方式 ...
- Java锁机制深入理解
Java锁机制 背景知识 指令流水线 CPU的基本工作是执行存储的指令序列,即程序.程序的执行过程实际上是不断地取出指令.分析指令.执行指令的过程. 几乎所有的冯•诺伊曼型计算机的CPU,其工 ...
- JAVA面试常见问题之锁机制篇
1.说说线程安全问题,什么是线程安全,如何保证线程安全 线程安全:就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用.不 ...
- java锁机制
2.4 锁机制 临界区是指,使用同一个锁控制的同一段代码区或多段代码区之间,在同一时间内最多只能有一个线程在执行操作.这个概念与传统的临界区有略微的差别,这里不想强调这些概念上的差别,临 ...
- Java锁机制了解一下
前言 回顾前面: 多线程三分钟就可以入个门了! Thread源码剖析 多线程基础必要知识点!看了学习多线程事半功倍 只有光头才能变强! 本文章主要讲的是Java多线程加锁机制,有两种: Synchro ...
- JAVA锁机制-可重入锁,可中断锁,公平锁,读写锁,自旋锁,
如果需要查看具体的synchronized和lock的实现原理,请参考:解决多线程安全问题-无非两个方法synchronized和lock 具体原理(百度) 在并发编程中,经常遇到多个线程访问同一个 ...
- JAVA锁机制(上)
在实际开发中经常会用到多线程协作来处理问题,锁是处理线程安全不可缺少的机制.在JAVA中可以通过至少三种方式来实现线程锁. 1. synchronized修饰符,这种锁机制是虚拟机实现的一种锁. 2 ...
随机推荐
- 使用Actor模型管理Web Worker多线程
前端固有的编程思维是单线程,比如JavaScript语言的单线程.浏览器JS线程与UI线程互斥等等,Web Woker是HTML5新增的能力,为前端带来多线程能力.这篇文章简单记录一下搜狗地图WebG ...
- FTP漏洞利用复现
目录 FTP弱口令漏洞 FTP后门漏洞利用 FTP弱口令漏洞 漏洞描述 FTP弱口令或匿名登录漏洞,一般指使用FTP的用户启用了匿名登录功能,或系统口令的长度太短.复杂度不够.仅包含数字.或仅包含字母 ...
- python实现一个无序单链表
class Node: """先定一个node的类""" def __init__(self, value=None, next=None) ...
- 2个快速制作完成一幅思维导图的iMindMap思维导图用法
随着思维导图的流行,与其相关的思维导图制作软件如雨后春笋,纷纷进入我们的视野中,更让人难以选择.那想要入门的萌新该如何开始这个新的旅途呢? 各式各样的思维导图制作软件当中,有一个软件得到了大家一致的好 ...
- 如何灵活运用ABBYY FineReader的识别功能
由于工作的原因,经常会使用到文字识别工具,说真的,一款好用的文字识别工具能省不少事,前不久碰到一位职场新人,他的工作内容也离不开文字识别工具,他还问我有什么好用的软件推荐,说到好用,还是ABBYY F ...
- LeetCode周赛#212
1631. 最小体力消耗路径 #并查集 #最短路径 题目链接 题意 给定一二维 rows x columns 的地图 heights ,其中 heights[row][col] 表示格子 \((row ...
- L - Deque 题解(区间dp)
题目链接 题目大意 给你一个双端队列里面有n个数组元素(n<=3000) 有两个人,每次一个人都可以选择队列里的首元素或者尾元素删除,轮流进行,删除后那个人即可获得这个元素的值 第一个人的总权值 ...
- J2EE基本概念
XO POJO:Plain Ordinary Java Object,简单java对象 PO:Persistant Object,持久层对象(对应数据库中一条记录) BO:Business Objec ...
- 记录一下超大list引起oom
1.2g的堆内存 比较大的对象20w个,oom
- 【MySQL篇】Navicat导入SQL文件报错终极解决方案
面对大数据库文件(一般50M以上),使用Navicat导入的时候容易出现[ERR]2006等报错问题,此文提供了几种办法,包括修改MySQL的配置参数在网上也有很多详细教程介绍过,但此文精彩处在于前面 ...