Faça isso passo a passo primeiro.
A primeira coisa é entender a função do bloqueio de leitura e gravação:
Bloqueio de leitura e bloqueio de leitura ------ não exclusivo
Bloqueio de gravação e bloqueio de gravação ------ exclusão
Bloqueio de leitura e bloqueio de gravação ------ exclusão
Vamos primeiro definir algumas propriedades:
//读写互斥,读读不互斥,写写互斥
private AtomicStampedReference<Thread> reference=new AtomicStampedReference<>(null,1);
private AtomicStampedReference<Thread> id=new AtomicStampedReference<>(null,1);
Bloqueio de leitura e bloqueio de leitura ------ não exclusivo:
Portanto, ao escrever, as condições de julgamento do bloqueio de leitura devem ser duas ou mais, de modo a garantir que enquanto o primeiro thread possa entrar pela condição, outros threads também possam entrar por outras condições.
public void readLock(){
Thread thread=Thread.currentThread();
while ((!id.compareAndSet(null,null,id.getStamp(),id.getStamp()+1))
&&(!reference.compareAndSet(null,thread, 1,1))){
//循环堵塞
}
}
Como você pode ver, usei o símbolo && na condição de julgamento while aqui para garantir a simultaneidade do bloqueio de leitura.
id.compareAndSet(null,null,id.getStamp(),id.getStamp()+1) É verdadeiro apenas quando o valor é nulo
reference.compareAndSet(null,thread, 1,1) Será verdadeiro somente quando o valor for nulo
Bloqueio de gravação e bloqueio de gravação ------ exclusão
Bloqueio de leitura e bloqueio de gravação ------ exclusão
public void writeLock(){
Thread thread=Thread.currentThread();
while (!reference.compareAndSet(null,thread,reference.getStamp(),1)
||!id.compareAndSet(null,thread,1,1)){
//循环堵塞
}
}
Como você pode ver, usei o símbolo || na condição de julgamento while aqui para garantir a exclusão mútua do bloqueio de gravação.
id.compareAndSet(null,null,id.getStamp(),id.getStamp()+1) É verdadeiro apenas quando o valor é nulo
reference.compareAndSet(null,thread, 1,1) Observe aqui: só é verdadeiro quando seu valor é nulo e seu número de versão é 1
Podemos ver que uma vez que o bloqueio de leitura (ou bloqueio de gravação) é executado primeiro, o bloqueio de gravação (ou bloqueio de leitura) de outros threads cairá em um loop infinito.
Então, após a execução do bloqueio de leitura (ou bloqueio de gravação), como iniciar o bloqueio de gravação (ou bloqueio de leitura)?
Então podemos inferir:
Se o bloqueio de leitura for executado primeiro, o que precisa ser liberado para o bloqueio de gravação é:
O número da versão do id é 1 e o valor da referência é nulo
public void unReadLock(){
reference.compareAndSet(reference.getReference(),null,reference.getStamp(),1);
id.compareAndSet(reference.getReference(),null,id.getStamp(),id.getStamp()-1);
}
id.compareAndSet(null,null,id.getStamp(),id.getStamp()-1);
Observe aqui que, como os bloqueios de leitura são simultâneos, esperamos que, após a execução de vários bloqueios de leitura, vários bloqueios de leitura sejam desbloqueados antes que o bloqueio de gravação possa ser executado.
Se o bloqueio de gravação for executado primeiro, tanto o bloqueio de gravação quanto o bloqueio de leitura precisarão ser liberados:
O número da versão do id é 1 e o valor da referência não é nulo.
public void unWriteLock(){
reference.compareAndSet(reference.getReference(),null,1,1);
id.compareAndSet(id.getReference(),null,1,1);
}
id.compareAndSet(id.getReference(),null,1,1);
O bloqueio de gravação é mutuamente exclusivo, portanto basta alterá-lo para o valor inicial.
Ok, depois de escrever o acima, implementamos um bloqueio de leitura e gravação. A leitura e a escrita são mutuamente exclusivas, portanto é um bloqueio pessimista.
Após a conclusão do bloqueio pessimista, esperamos adicionar um bloqueio otimista.
Em primeiro lugar, entenda que o bloqueio otimista não é um tipo de bloqueio, portanto não precisa ser desbloqueado.Você também deve entender que sua existência é para resolver o problema de exclusão mútua do bloqueio de leitura e do bloqueio de gravação.
Por exemplo, se estivermos lendo, o thread responsável pela escrita pode nunca ter a chance de ser executado. Isso é uma coisa muito ruim.
Como o bloqueio otimista não é mutuamente exclusivo com o bloqueio de gravação, como podemos garantir a segurança dos dados?
A resposta é julgamento. Se ocorrer uma operação de gravação, adicionamos um bloqueio de leitura. Se a operação de gravação não ocorrer, não adicionamos um bloqueio de leitura.
Iniciar: adicione alguns atributos:
private AtomicInteger ifWrite=new AtomicInteger(0);
private volatile int stamp;
ifWrite é usado para determinar se o bloqueio de gravação ocorreu
stamp é usado para atualizar o status do ifWrite no tempo
Assim que o bloqueio de gravação for executado, alteramos ifWrite
public void writeLock(){
Thread thread=Thread.currentThread();
while (!reference.compareAndSet(null,thread,reference.getStamp(),1)
||!id.compareAndSet(null,thread,1,1)){
//循环堵塞
}
ifWrite.compareAndSet(ifWrite.get(),99);
}
Enquanto o bloqueio de gravação for executado, abandonaremos o bloqueio otimista e usaremos o bloqueio de leitura.Quando o bloqueio de leitura for desativado, o estado será retornado.
public void unReadLock(){
reference.compareAndSet(reference.getReference(),null,reference.getStamp(),1);
id.compareAndSet(reference.getReference(),null,id.getStamp(),id.getStamp()-1);
ifWrite.compareAndSet(ifWrite.get(),0);
}
Bem, nosso bloqueio otimista só precisa garantir que ifWrite seja atribuído ao carimbo.
public void OptimisticReadLock(){
this.stamp=ifWrite.get();
}
public Boolean validate(){
if(this.stamp==99)
return true;
return false;
}
Ok, o código completo é:
public class XjggLock {
private AtomicStampedReference<Thread> reference=new AtomicStampedReference<>(null,1);
private AtomicStampedReference<Thread> id=new AtomicStampedReference<>(null,1);
private AtomicInteger ifWrite=new AtomicInteger(0);
private volatile int stamp;
public void readLock(){
Thread thread=Thread.currentThread();
while ((!id.compareAndSet(null,null,id.getStamp(),id.getStamp()+1))
&&(!reference.compareAndSet(null,thread, 1,1))){
//循环堵塞
}
}
public void unReadLock(){
reference.compareAndSet(reference.getReference(),null,reference.getStamp(),1);
id.compareAndSet(reference.getReference(),null,id.getStamp(),id.getStamp()-1);
ifWrite.compareAndSet(ifWrite.get(),0);
}
public void writeLock(){
Thread thread=Thread.currentThread();
while (!reference.compareAndSet(null,thread,reference.getStamp(),1)
||!id.compareAndSet(null,thread,1,1)){
//循环堵塞
}
ifWrite.compareAndSet(ifWrite.get(),99);
}
public void unWriteLock(){
reference.compareAndSet(reference.getReference(),null,1,1);
id.compareAndSet(id.getReference(),null,1,1);
}
public void OptimisticReadLock(){
this.stamp=ifWrite.get();
}
public Boolean validate(){
if(this.stamp==99)
return true;
return false;
}
}
O código é mínimo e muito simples.
Vamos testar:
Leia simultaneamente:
class Test{
private static XjggLock xjggLock=new XjggLock();
private static int num;
static void read(){
xjggLock.readLock();
try {
int n=num;
System.out.println(Thread.currentThread().getName()+"===>读执行");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"===>读结束");
} catch (Exception e) {
e.printStackTrace();
} finally {
xjggLock.unReadLock();
}
}
public static void main(String[] args) {
//测试读能并发
for (int i = 0; i < 3; i++) {
new Thread(()->{
read();
}).start();
}
}
}
Resultado: você pode ver que não há problemas
Thread-1===>Ler a execução
Thread-2===>Ler a execução
Thread-0===>Ler a execução
Thread-0===>Ler o final
Thread-2===>Ler o final
Thread-1== =>Fim da leitura
Escreva mutex:
class Test{
private static XjggLock xjggLock=new XjggLock();
private static int num;
static void write(){
xjggLock.writeLock();
try {
num+=99;
System.out.println(Thread.currentThread().getName()+"===>写执行");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"===>写结束");
} catch (Exception e) {
e.printStackTrace();
} finally {
xjggLock.unWriteLock();
}
}
public static void main(String[] args) {
//测试写不能并发
for (int i = 0; i < 3; i++) {
new Thread(()->{
write();
}).start();
}
}
}
Resultado: você pode ver que não há problemas
Thread-0===>Escrever e executar
Thread-0===>Escrever e executar
Thread-2===>Escrever e executar
Thread-2===>Escrever e executar
Thread-1===>Escrever e executar
Tópico-1== =>Fim da escrita
Ler e escrever mutuamente exclusivos:
class Test{
private static XjggLock xjggLock=new XjggLock();
private static int num;
static void read(){
xjggLock.readLock();
try {
int n=num;
System.out.println(Thread.currentThread().getName()+"===>读执行");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"===>读结束");
} catch (Exception e) {
e.printStackTrace();
} finally {
xjggLock.unReadLock();
}
}
static void write(){
xjggLock.writeLock();
try {
num+=99;
System.out.println(Thread.currentThread().getName()+"===>写执行");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"===>写结束");
} catch (Exception e) {
e.printStackTrace();
} finally {
xjggLock.unWriteLock();
}
}
public static void main(String[] args) {
//测试读写不能并发
for (int i = 0; i < 2; i++) {
new Thread(()->{
read();
}).start();
}
for (int i = 0; i < 2; i++) {
new Thread(()->{
write();
}).start();
}
for (int i = 0; i < 2; i++) {
new Thread(()->{
read();
}).start();
}
}
}
Resultado: você pode ver que não há problemas
Thread-0===>Ler e executar
Thread-4===>Ler e executar
Thread-1===>Ler e executar
Thread-5===>Ler e executar
Thread-1===>Ler e terminar
Thread-0== =>Ler final
Thread-4===>Ler final
Thread-5===>Ler final
Thread-3===>Escrever execução
Thread-3===>Escrever final
Thread-2== =>
Thread de execução de escrita -2 ===> Fim da escrita
Teste novamente para testar o bloqueio otimista:
class Test{
private static XjggLock xjggLock=new XjggLock();
private static int num;
static void read(){
xjggLock.OptimisticReadLock();//加一个乐观锁
if(xjggLock.validate()){
xjggLock.readLock();
System.out.println("当前写操作已发生,为了安全,我们加上读锁,此时写锁不可以进来。");
try {
int n=num;
System.out.println(Thread.currentThread().getName()+"===>读执行,写锁不可以进来");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"===>读结束,写锁不可以进来");
} catch (Exception e) {
e.printStackTrace();
} finally {
xjggLock.unReadLock();
}
}else{
int n=num;
System.out.println("当前没有发生写的操作,我们可以直接取值,此时写锁可以进来。");
System.out.println(Thread.currentThread().getName()+"===>读执行,写锁可以进来");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"===>读结束,写锁可以进来");
}
}
static void write(){
xjggLock.writeLock();
try {
num+=99;
System.out.println(Thread.currentThread().getName()+"===>写执行");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"===>写结束");
} catch (Exception e) {
e.printStackTrace();
} finally {
xjggLock.unWriteLock();
}
}
public static void main(String[] args) {
//测试乐观锁
for (int i = 0; i < 2; i++) {
new Thread(()->{
read();
}).start();
}
for (int i = 0; i < 2; i++) {
new Thread(()->{
write();
}).start();
}
}
}
Resultado: você pode ver que não há problemas
Atualmente não há operação de gravação, podemos obter o valor diretamente e o bloqueio de gravação pode entrar neste momento.
Thread-1 ===> execução de leitura, bloqueio de gravação pode entrar
Thread-3 ===> execução de gravação
Thread-1 ===> final de leitura, bloqueio de gravação pode entrar
Thread-3 ===> final de gravação
O atual operação de gravação ocorreu, por segurança, adicionamos um bloqueio de leitura e o bloqueio de gravação não pode entrar neste momento.
Thread-0 ===> Execução de leitura, bloqueio de gravação não pode entrar
Thread-0 ===> Fim de leitura, bloqueio de gravação não pode entrar
Thread-2 ===> Execução de gravação
Thread-2 ===> Fim de gravação
Resumindo, o bloqueio de leitura e gravação pode melhorar a eficiência da leitura e garantir a segurança dos dados. A adição do bloqueio otimista garante que a escrita possa ser feita durante a leitura, o que melhora ainda mais a eficiência da execução. Juntamente com o bloqueio de leitura, também garante a segurança dos dados.
Claro, não é realista escrever um bloqueio de leitura e gravação perfeito com apenas essas poucas linhas de código. Pode haver alguns problemas com isso. Embora eu ainda não tenha testado e não tenha encontrado nenhum problema, ele não deve ser confiável! ! !
O JDK definitivamente terá um bloqueio muito útil. JDK8 começou a adicionar novos bloqueios (java.util.concurrent.locks.StampedLock), então podemos usar essa classe diretamente nos negócios.
Espero que isso ajude você!