Use a função CAS da classe de ferramenta no pacote atômico de Java para escrever manualmente um bloqueio de leitura e gravação com uma função de bloqueio otimista

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ê!

おすすめ

転載: blog.csdn.net/xiaomaomixj/article/details/126852372