Programação simultânea JUC (1) Thread, TreadPoolExecutor, BlockingQueue, Synchronized, Lock, classes auxiliares JUC

Fio

Como criar multithread

Herdar Tópico

  1. Método 1: herdar a classe Thread e criar etapas:
    • Defina a subclasse MyThread para herdar Thread e reescrever o método run()
    • Crie um objeto da classe MyThread
    • Chame o método start() do objeto thread para iniciar o thread (o método run() é executado após a inicialização)

Como esse método de criação já herdou a classe Thread, ele não pode herdar outras classes, o que não favorece a expansão.

  1. Método 2: Declare uma classe que implemente a interface Runnable.
    • Definição Defina uma classe de tarefa de thread MyRunnable para implementar a interface Runnable e substituir o método run().
    • Crie o objeto MyRunnable
    • Entregue o objeto MyRunnable ao Thread para processamento
    • Chame o método start do objeto Thread para iniciar

Implementar a interface executável

O segundo método implementa apenas a interface, você pode continuar a herdar a classe e implementar a interface, e a escalabilidade é mais forte. Mas a desvantagem é que há uma camada extra de empacotamento para programação (o objeto executável precisa ser passado para o thread para construir o objeto thread).
insira a descrição da imagem aqui
Implementação do método 1:

public class MyThread extends Thread{
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 5; ++i) {
    
    
            System.out.println("子线程执行输出:" + i);
        }
    }
}
 Thread t1 = new MyThread();
 t1.start();

Implementação do método dois:

Thread t = new Thread(new Runnable() {
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            System.out.println(i);
        }
    }
});
for (int i = 0; i < 10; i++) {
    
    
    System.out.println("主线程:" + i);
}

Claro, você pode usar expressões lambda para abreviar.

 Thread t = new Thread(() -> {
    
    
     for (int i = 0; i < 10; i++) {
    
    
         System.out.println( "子线程" + i);
     }
 });
 for (int i = 0; i < 10; i++) {
    
    
     System.out.println("主线程:" + i);
 }
  1. Método 3
    Há um problema nos dois primeiros métodos de criação:
    • Nenhum de seus métodos run() reescritos pode retornar resultados diretamente
    • Não é adequado para cenários de negócios que precisam retornar resultados de execução de thread

Defina uma classe que implemente a interface Callable e monte-a em uma FutureTask

jdk5 usa interfaces Callable e FutureTask para realizar as funções acima.

Crie etapas:

  • Defina a classe para implementar a interface Callable, reescreva o método de chamada e encapsule as coisas a serem feitas.
  • Use FutureTask para encapsular o objeto Callable em um objeto de tarefa de thread.
  • Entregue o objeto de tarefa do thread ao Thread para processamento
  • Chame o método start de Thread para iniciar o thread e executar a tarefa.
  • Após a execução do thread, use o método get() de FutureTask para obter o resultado da execução da tarefa.

Implementação do método três:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadDemo3 {
    
    
    public static void main(String[] args) {
    
    
        Callable<String> call1 = new MyCallable(100000);
        FutureTask<String> f1 = new FutureTask<>(call1);
        Thread t1 = new Thread(f1);
        t1.start();

        Callable<String> call2 = new MyCallable(100000);
        FutureTask<String> f2 = new FutureTask<>(call2);
        Thread t2 = new Thread(f2);
        t2.start();

        try {
    
    
            // 如果f1没有执行完毕,那么get这里会等待,直至完成
            String r1 =  f1.get();
            System.out.println("第一个结果:" + r1);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
            // 如果f2没有执行完毕,那么get这里会等待,直至完成
        try {
    
    
            String r2 =  f2.get();
            System.out.println("第二个结果:" + r2);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

class MyCallable implements Callable<String> {
    
    
    private int n;
    public MyCallable (int n) {
    
    
        this.n = n;
    }
    @Override
    public String call() throws Exception {
    
    
        int sum = 0;
        for (int i = 0; i <= n; i++) {
    
    
            sum += i;
        }
        return "子线程执行的结果是:" + sum;
    }
}

APIs comuns

insira a descrição da imagem aqui
insira a descrição da imagem aqui

API nativa

dormir

Quando o método sleep de um thread é chamado, faz com que o thread em execução no momento entre em suspensão (interrompe temporariamente a execução) pelo número especificado de milissegundos, sujeito à precisão e exatidão dos temporizadores e agendadores do sistema. Se algum thread interromper o thread atual, uma InterruptedException será lançada para limpar o status de interrupção do thread atual.

interromper

  • Quando um thread chama interrupção (), se o thread estiver em um estado ativo normal, o sinalizador de interrupção do thread será definido como verdadeiro, nada mais, o thread com o sinalizador de interrupção definido continuará a ser executado normalmente, sem ser afetado. Ou seja, interrupt() apenas define o sinalizador de interrupção, mas na verdade não interrompe o thread e requer a cooperação do thread chamado. É como se você dissesse a uma pessoa para calar a boca, mas no final, se essa pessoa cala a boca ou não, depende da sua cooperação.
  • Se o thread estiver em um estado bloqueado (como suspensão, espera, junção, etc.), chame o método interrupt() do objeto de thread atual em outro thread, então o thread sairá imediatamente do estado bloqueado e o status de interrupção sinalizador será limpo e lançará uma exceção InterruptedException.
  • Para threads inativos, chamar interrupção() não tem efeito.

juntar

O método join permite que um thread seja executado antes de ingressar em outro thread. Durante a execução deste thread, outros threads entram no estado de bloqueio. Claro, você também pode especificar o parâmetro de entrada join (especificar o período de tempo limite para espera de execução) e esperar durante alguns milissegundos no máximo para que o thread termine.Um tempo limite de 0 significa esperar para sempre.

public class demo1 {
    
    
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(() -> {
    
    
            try {
    
    
                TimeUnit.SECONDS.sleep(2);
                System.out.println("+++++++++++++");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        });
        t1.start();
        try {
    
    
            t1.join();
            System.out.println("ok");
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

colheita

O significado literal de rendimento é ceder. Chamar esse método indica ao escalonador que o thread atual está disposto a renunciar ao uso atual do processador, o que o escalonador está livre para ignorar.

rendimento é uma tentativa heurística de melhorar o progresso relativo entre threads que, de outra forma, usariam excessivamente a CPU. Geralmente há dois cenários de uso ao usar o método de rendimento:

  • A utilização do rendimento deve ser associada a uma análise detalhada e a uma avaliação comparativa para garantir que realmente produz o efeito desejado, mas esta abordagem raramente é utilizada. Pode ser útil para fins de depuração ou teste, pode ajudar a reproduzir bugs devido a condições de corrida
  • Também pode ser útil ao projetar estruturas de controle de simultaneidade, como aquelas do pacote java.util.concurrent.locks
public class TestYield {
    
    
    public static void main(String[] args) {
    
    
        MyThread thread1 = new MyThread("thread-1");
        MyThread thread2 = new MyThread("thread-2");
        thread1.start();
        thread2.start();
    }
 
    private static class MyThread extends Thread {
    
    
        public MyThread(String name) {
    
    
            super(name);
        }
        @Override
        public void run() {
    
    
            for (int i = 1; i <= 5; i++) {
    
    
                if (i % 2 == 0) {
    
    
                    Thread.yield();
                    System.out.println(getName() + ":" + i);
                }
            }
        }
    }
}

TreadPoolExecutor

Pool de threads é uma tecnologia que pode reutilizar threads. Como a sobrecarga de criação de novos threads é muito alta, o uso do pool de threads pode reutilizar threads e melhorar o desempenho do programa.
insira a descrição da imagem aqui

Obtenha o objeto do pool de threads

Desde o JDK5.0, uma interface que representa o conjunto de encadeamentos foi fornecida:
Como o ExecutorService obtém o objeto do conjunto de encadeamentos?

  • Método 1: Use a classe de implementação ThreadPoolExecutor do ExecutorService para autocriar um objeto de pool de threads. Este método é o mais flexível.

  • Método 2: Use Executores (uma classe de ferramenta para pools de threads) para chamar métodos para retornar objetos de pool de threads com características diferentes.

Pool de threads integrado de executores

1. newCachedThreadPool
cria um pool de threads em cache. Se o comprimento do pool de threads exceder as necessidades de processamento, ele poderá reciclar threads inativos de maneira flexível. Se não houver thread reciclável, um novo thread será criado. As características deste tipo de pool de threads são:
quase não há limite para o número de threads de trabalho criados (na verdade, há um limite, o número é Inteiro. MAX_VALUE), para que os threads possam ser adicionados ao pool de threads de maneira flexível .

Se nenhuma tarefa for enviada ao pool de threads por um longo período, ou seja, se o thread de trabalho estiver ocioso por um tempo especificado (1 minuto por padrão), o thread de trabalho será encerrado automaticamente. Após o encerramento, se você enviar uma nova tarefa, o conjunto de encadeamentos recriará um encadeamento de trabalho. Ao usar CachedThreadPool, você deve prestar atenção ao controle do número de tarefas, caso contrário, devido a um grande número de threads em execução ao mesmo tempo, é provável que cause OOM do sistema.

2. newFixedThreadPool
cria um conjunto de threads com um número especificado de threads de trabalho. Um thread de trabalho é criado sempre que uma tarefa é enviada e, se o número de threads de trabalho atingir o número máximo inicial de conjuntos de threads, a tarefa enviada será armazenada na fila do pool.

FixedThreadPool é um pool de threads típico e excelente, que tem as vantagens de melhorar a eficiência do programa e economizar a sobrecarga de criação de threads. Porém, quando o conjunto de encadeamentos está ocioso, ou seja, quando não há tarefas executáveis ​​no conjunto de encadeamentos, ele não liberará o encadeamento de trabalho e também ocupará determinados recursos do sistema.

3. newSingleThreadExecutor
cria um executor de thread único, ou seja, cria apenas um thread de trabalho exclusivo para executar tarefas e usa apenas o único thread de trabalho para executar tarefas, garantindo que todas as tarefas sejam executadas na ordem especificada (FIFO, LIFO, prioridade). Se esta thread terminar de forma anormal, outra tomará o seu lugar, garantindo a execução sequencial. A maior característica de um único thread de trabalho é que as tarefas são garantidamente executadas sequencialmente e nenhum thread múltiplo estará ativo em um determinado momento.
4. newScheduleThreadPool
cria um pool de threads de comprimento fixo e oferece suporte ao tempo e à execução periódica de tarefas e ao tempo e à execução periódica de tarefas.

ThreadPoolExecutor

insira a descrição da imagem aqui
Quando os threads temporários são criados?

  • Quando uma nova tarefa é enviada, os threads principais ficam ocupados, a fila de tarefas também fica cheia e threads temporários também podem ser criados, e só então os threads temporários serão criados.
    Quando você começa a rejeitar tarefas?
  • Tanto o thread principal quanto o thread temporário estão ocupados, e a fila de tarefas também está cheia, e a tarefa será rejeitada quando uma nova tarefa chegar.

insira a descrição da imagem aqui

Pool de threads lida com tarefas executáveis

        ExecutorService pool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        Runnable target = () -> {
    
    
            try {
    
    
                Thread.sleep(100000);
                System.out.println(Thread.currentThread().getName() + "输出");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        };
        // 三个核心线程
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        // 五个在等待队列
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        // 等待队列满了,新加两个临时线程
        pool.execute(target);
        pool.execute(target);
        
        // 拒绝任务,抛出异常
        pool.execute(target);

    }

O pool de threads lida com tarefas que podem ser chamadas

insira a descrição da imagem aqui
execute executa tarefas executáveis ​​e envia processos que podem ser chamados.

BlockingQueue fila de bloqueio

BlockingQueue é uma fila FIFO (primeiro a entrar, primeiro a sair), que resolve o problema de como "transmitir" dados de forma eficiente e segura em multithreading.
insira a descrição da imagem aqui
As filas de bloqueio possuem quatro APIs para adicionar e remover elementos:

  • sem bloqueio, retorna booleano
    • Uma exceção será lançada: add(), remove(), element()
    • Nenhuma exceção será lançada: offer(), poll(), peek()
  • bloquear
    • Sempre bloqueado: put(), take()
    • Você pode definir o tempo de espera, retornar quando o tempo limite: offer(e, timeout, unit), poll(timeout, unit)

insira a descrição da imagem aqui

LinkedBlockingQueue fila de bloqueio de lista vinculada

Fila de bloqueio de lista duplamente vinculada.

Para FixedThreadPool e SingleThreadExector, a fila de bloqueio que eles usam é uma LinkedBlockingQueue com capacidade de Integer.MAX_VALUE, que pode ser considerada uma fila ilimitada.

Como
o número de threads no pool de threads FixedThreadPool é fixo, não há como adicionar um número particularmente grande de threads para processar tarefas.Neste momento, uma fila de bloqueio sem limite de capacidade, como LinkedBlockingQueue, é necessária para armazenar tarefas.

Deve-se notar aqui que, como a fila de tarefas do conjunto de encadeamentos nunca estará cheia, o conjunto de encadeamentos criará apenas encadeamentos com o número de encadeamentos principais, portanto, o número máximo de encadeamentos neste momento não faz sentido para o conjunto de encadeamentos, porque não acionará a geração de vários threads.Threads baseados no número de threads principais.

Fila síncrona SynchronousQueue

A fila síncrona SynchronousQueue não armazena elementos, desde que um elemento seja colocado nela, um elemento precisa ser retirado e retirado.

O conjunto de threads correspondente é CachedThreadPool. O número máximo de threads no pool de threads CachedThreadPool é o valor máximo de Integer, pode-se entender que o número de threads pode ser expandido infinitamente.
CachedThreadPool é exatamente o oposto do pool de threads anterior, FixedThreadPool. No caso de FixedThreadPool, a capacidade da fila de bloqueio é ilimitada. Aqui, o número de threads em CachedThreadPool pode ser expandido infinitamente, então o pool de threads CachedThreadPool não precisa de um fila de tarefas para armazenar tarefas, porque uma vez que uma tarefa é enviada, ela é encaminhada diretamente para o thread ou cria um novo thread para executar sem salvá-los separadamente.

SynchronousQueue<String> bq = new SynchronousQueue();
new Thread(() -> {
    
    
    try {
    
    
        bq.put("1");
        System.out.println(Thread.currentThread().getName());
        bq.put("1");
        System.out.println(Thread.currentThread().getName());
        bq.put("1");
        System.out.println(Thread.currentThread().getName());
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
}, "A").start();

new Thread(() -> {
    
    
    try {
    
    
        TimeUnit.SECONDS.sleep(2);
        bq.take();
        System.out.println(Thread.currentThread().getName());
        bq.take();
        System.out.println(Thread.currentThread().getName());
        bq.take();
        System.out.println(Thread.currentThread().getName());
    } catch (Exception e){
    
    
        e.printStackTrace();
    }
}, "B").start();
}

DelayedWorkQueue fila de bloqueio atrasado

A terceira fila de bloqueio é DelayedWorkQueue. Seus pools de threads correspondentes são ScheduledThreadPool e SingleThreadScheduledExecutor. A maior característica desses dois pools de threads é que eles podem atrasar a execução de tarefas, como executar tarefas após um determinado período de tempo ou executar tarefas em intervalos regulares .

A característica do DelayedWorkQueue é que os elementos internos não são classificados de acordo com o momento em que são colocados, mas as tarefas são classificadas de acordo com a duração do atraso, e o uso interno é uma estrutura de dados "heap". A razão pela qual o pool de threads ScheduledThreadPool e SingleThreadScheduledExecutor escolhem DelayedWorkQueue é porque eles próprios executam tarefas com base no tempo, e a fila de atraso pode apenas classificar as tarefas por tempo para facilitar a execução da tarefa.

Sincronizado

1.并发就是多线程操作同一个资源。

2.在Java中,线程就是一个单独的资源类,没有任何附属操作。资源中包含并发操作的属性、方法。

Caso de simultaneidade - um exemplo de compra de ingressos multithread:
No meu exemplo, a classe de recurso público é Ticket, à esquerda está o número restante de ingressos, cnt é o número registrado de ingressos vendidos e o método sale() imprime quem comprou. quando houver ingressos excedentes Um ingresso e exibir os ingressos restantes atuais e o total de ingressos vendidos.

class Ticket {
    
    
    int left;
    int cnt = 0;
    public int getLeft() {
    
    
        return left;
    }

    public void setLeft(int left) {
    
    
        this.left = left;
    }

    public Ticket(int n) {
    
    
        this.left = n;
    }
    public void sale() {
    
    
        try {
    
    
            Thread.sleep(100); // 模拟卖票耗时
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        if (left > 0) {
    
    
            ++cnt;
            --left;
            System.out.println("线程:" + Thread.currentThread().getName() + "剩余:" + left + ",共卖出:" + cnt);
        }
    }
}

Na função principal, defina o número de votos, o número de pessoas que compram ingressos e o número de tentativas de cada pessoa para comprar ingressos:

public static void main(String[] args) {
    
    
    int ticketNum = 8, people = 4, chance = 5;
    Ticket t = new Ticket(ticketNum);
    System.out.println("开售前:---------" + t.getLeft());
    for (int i = 0; i < people; ++i) {
    
    
        new Thread(() -> {
    
    
            for (int j = 0; j < chance; ++j) {
    
    
                t.sale();
            }
        }, Integer.toString(i)).start();
    }
    try {
    
    
        Thread.sleep(3000);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
}

resultado:
insira a descrição da imagem aqui

solução sincronizada

A base da implementação sincronizada da sincronização: cada objeto em Java pode ser usado como um bloqueio. Especificamente, assume as três formas a seguir:
1. 对于普通同步方法,锁是当前实例对象。
2. 对于静态同步方法,锁是当前类的Class对象。
3. 对于同步方法块,锁是Synchonized括号里配置的对象。

Deixe sale() se tornar um método síncrono diretamente, ou seja, quando cada thread acessar o método sale(), ele irá bloquear o objeto da instância (ou seja, o recurso público) onde o método atual está localizado, então apenas um thread pode operar por vez e outros threads devem esperar:
insira a descrição da imagem aqui
insira a descrição da imagem aqui

Bloqueio de bloqueio

O bloqueio usa três etapas:

  1. Crie um bloqueio: Lock lk = new ReentrantLock();
  2. Obtenha um bloqueio: lk.lock
  3. try - catch - finalmente, onde a lógica de negócios que requer controle de simultaneidade é escrita em try e o bloqueio é liberado em finalmente para garantir que o bloqueio seja liberado normalmente quando ocorre uma exceção.lk.lock().
class Ticket2 {
    
    
    int left;
    int cnt = 0;
    Lock lk = new ReentrantLock();
    public int getLeft() {
    
    
        return left;
    }

    public void setLeft(int left) {
    
    
        this.left = left;
    }

    public Ticket2(int left) {
    
    
        this.left = left;
    }

    public void sale() {
    
    
        lk.lock();
        try {
    
    
            if (left > 0) {
    
    
                ++cnt;
                --left;
                System.out.println("线程:" + Thread.currentThread().getName() + "剩余:" + left + ",共卖出:" + cnt);
            }
        }
        catch (Exception e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            lk.unlock();
        }
    }
}

ReentrantLock também pode garantir segurança de simultaneidade.
insira a descrição da imagem aqui

A diferença entre bloqueio sincronizado e bloqueio de bloqueio

  1. Synchronized é uma palavra-chave interna do Java e Lock é uma classe Java.
  2. Synchronized não pode adquirir o estado do bloqueio e Lock pode determinar se o bloqueio foi adquirido.
  3. Sincronizado liberará automaticamente o bloqueio, e o bloqueio deverá ser bloqueado manualmente. Se o bloqueio não for liberado, ocorrerá um deadlock.
  4. Sincronizado, o thread que não adquiriu o bloqueio irá esperar para sempre.Lock lock, existe um mecanismo para tentar adquirir o bloqueio, e não necessariamente espera para sempre.
  5. sincronizado é reentrante e injusto; bloqueio de bloqueio é reentrante e bloqueios justos podem ser definidos, ou seja, um é uma palavra-chave integrada que não pode ser modificada e o outro é personalizado.
  6. Synchronized é adequado para bloquear uma pequena quantidade de problemas de sincronização de código e Lock é adequado para bloquear um grande número de códigos de sincronização.

problema produtor-consumidor

implementação sincronizada

Os threads A e B operam a mesma variável num, A permite num + 1,
B permite num - 1 e os dois são usados ​​alternadamente.

Aqui, após A ter concluído a operação, B precisa ser notificado, e B precisa ser notificado após a conclusão da operação, para realizar a sincronização do thread.É equivalente a entregar A a B para consumo após A ser produzido, e notificar A após B concluir o consumo.

Complete esta trilogia de programação do modelo de produção-consumo:

  • Espere: quando a condição não é atendida, o loop while espera
  • Negócio: Quando as condições forem atendidas, execute o negócio.
  • Notificação: Após a conclusão do negócio, notifique outros tópicos.

Construa uma classe de recurso Data, que possui uma variável de membro num, construa dois métodos de sincronização, um executa +1 e o outro executa -1. No método principal, inicie dois threads A e B e tente operar +1 e - 1 respectivamente:

class Data {
    
    
    private int num = 0;

    public synchronized void increase() {
    
    
        while (num != 0) {
    
    
            try {
    
    
                this.wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        ++num;
        System.out.println(Thread.currentThread().getName() +":->"+ num);
        this.notifyAll();
    }

    public synchronized void decrease() {
    
    
        while (num != 1) {
    
    
            try {
    
    
                this.wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        --num;
        System.out.println(Thread.currentThread().getName() +":->"+ num);
        this.notifyAll();
    }
}

    public static void main(String[] args) {
    
    
        Data d = new Data();
        new Thread(() -> {
    
    
            for (int i = 0; i < 5; i++) {
    
    
                d.increase();
            }
        }, "A").start();

        new Thread(() -> {
    
    
            for (int i = 0; i < 5; i++) {
    
    
                d.decrease();
            }
        }, "B").start();
    }
}

implementação de versão de bloqueio - condição

insira a descrição da imagem aqui
Você pode usar lock para construir variáveis ​​de condição.condição fornece o método wait() e os métodos signal() e signalAll(), semelhantes a wait(), notify() e notifyAll;

Etapas principais:

  • 1. Crie ReentrantLock() lk e obtenha a condição de lk
  • 2. Bloqueio: lk.lock()
  • 3、tentar - pegar - final
    • Escreva a lógica de negócios no try:
      • Espere: Quando a condição não é atendida, o loop while espera: condição.await();
      • Negócio: Quando as condições forem atendidas, execute o negócio
      • Notificação: Após a conclusão do negócio, notifique outros threads: condição.signalAll();
    • Solte o bloqueio no final: lk.unlock();
class Data2 {
    
    
    private int num = 0;
    Lock lk = new ReentrantLock();
    Condition condition = lk.newCondition();
    
    public  void increase() {
    
    
        lk.lock();
        try {
    
    
            while (num != 0) condition.await();
            ++num;
            System.out.println(Thread.currentThread().getName() +":->"+ num);
            condition.signalAll();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lk.unlock();
        }
    }

    public  void decrease() {
    
    
        lk.lock();
        try {
    
    
            while (num != 1) condition.await();
            --num;
            System.out.println(Thread.currentThread().getName() +":->"+ num);
            condition.signalAll();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lk.unlock();
        }
    }
}

condição realiza ativação de notificação precisa

insira a descrição da imagem aqui

Chamável

Usado para solicitações assíncronas com valores de retorno para obter resultados.
O primeiro passo é construir seu próprio objeto Callable e implementar a interface Callable, que precisa de um parâmetro genérico para identificar o tipo de resultado esperado.

class  MyCall implements Callable<Integer> {
    
    
    int a, b;

    public MyCall(int a, int b) {
    
    
        this.a = a;
        this.b = b;
    }

    @Override
    public Integer call() throws Exception {
    
    
        TimeUnit.SECONDS.sleep(2);
        return a + b;
    }
}

O objeto Callable é empacotado com FutureTask, ou seja, é empacotado como uma tarefa a ser executada no futuro.

MyCall call = new MyCall(3, 4);
FutureTask future = new FutureTask(call);

insira a descrição da imagem aqui
A interface composta RunnableFuture é implementada em FutureTask, ou seja, possui a implementação de Runnable, portanto pode ser colocada em Thead para iniciar:

        new Thread(future).start();
        Integer a = 0;
        try {
    
    
            a = (Integer) future.get();
        } catch (InterruptedException | ExecutionException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(a.toString());

Este future.get() irá bloquear!

Aulas auxiliares comumente utilizadas pela JUC

CountDownLatch (cronômetro de contagem regressiva)

CountDownLatch permite que a contagem de threads seja bloqueada em um local até que as tarefas de todos os threads sejam executadas.

Simule uma cena, há 6 alunos na sala, e a porta só poderá ser fechada depois que todos os alunos saírem!

public static void main(String[] args) {
    
    
    // 1、统计num个线程的倒计时器
    int num = 6;
    CountDownLatch cn = new CountDownLatch(num);
    for (int i = 0; i < num; ++i) {
    
    
        new Thread(()->{
    
    
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "离开");
            // 2、线程结束前,倒数一下
            cn.countDown();
        }, String.valueOf(i)).start();
    }
    try {
    
    
        // 3、等待所有线程结束
        cn.await();
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    System.out.println("关门!");
}

insira a descrição da imagem aqui

CyclicBarrier (cerca cíclica)

CyclicBarrier é muito semelhante ao CountDownLatch. Ele também pode implementar espera técnica entre threads. O que ele precisa fazer é bloquear um grupo de threads quando eles alcançam uma barreira (também chamado de ponto de sincronização) até que o último thread alcance a barreira. Só então a porta será aberta e todos os fios bloqueados pela barreira continuarão funcionando.

O cenário atual é invertido, assumindo que o número de pessoas que chegam antes do professor atingir o número especificado, a porta pode ser aberta:

Etapa 1: Crie um CyclicBarrier, especifique o número de threads a serem atendidos e o objeto executável a ser executado após a chegada de todos os threads.

CyclicBarrier cb = new CyclicBarrier(num, () -> {
    
    
   System.out.println("开门!");
});

Etapa 2: Antes do final da execução de cada thread, use cb.await();para aguardar a sincronização de outros threads.

public static void main(String[] args) {
    
    
    // 1、等待的人数到达num后,才开门!
    int num = 6;
    CyclicBarrier cb = new CyclicBarrier(num, () -> {
    
    
        System.out.println("开门!");
    });
    for (int i = 0; i < num; ++i) {
    
    
        new Thread(()->{
    
    
            System.out.println(Thread.currentThread().getName() + "到达");
            try {
    
    
                // 2、线程结束前,需等待其他线程同步
                cb.await();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }, String.valueOf(i)).start();
    }
}

insira a descrição da imagem aqui

Semáforo semáforo - permite que vários threads acessem ao mesmo tempo

Tanto o sincronizado quanto o ReentrantLock permitem que apenas um thread acesse um recurso por vez.Semaphore(信号量)可以指定多个线程同时访问某个资源。

O semáforo possui dois modos: modo justo e modo injusto.

  • Modo justo: a ordem em que a aquisição é chamada é a ordem em que as licenças são adquiridas, seguindo o FIFO
  • Modo Injusto: Preemptivo

Método de construção:

public Semaphore(int permits) {
    
    
        sync = new NonfairSync(permits);
    }
public Semaphore(int permits, boolean fair) {
    
    
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

Ambos os métodos de construção devem fornecer o número de licenças.O segundo método de construção pode especificar se é um modo justo ou injusto, e o padrão é o modo injusto.

insira a descrição da imagem aqui

O cenário mais comumente usado é quando os recursos são limitados, apenas um número especificado de threads tem permissão para acessar um determinado recurso ao mesmo tempo, como simular uma cena de ocupação de uma vaga de estacionamento:

Passo 1:
Simule a situação do recurso: num vagas de estacionamento, e o usuário tem 10
int num = 3, total = 6;
Semáforo semáforo = novo Semáforo (num);

Etapa 2:
try -catch -final:
try: semaphore.acquire(); // Adquira o recurso
finalmente: semaphore.release(); // Libera o recurso

public static void main(String[] args) {
    
    
    // 1、num个车位,而用户有total 个
    int num = 3, total = 6;
    Semaphore sm = new Semaphore(num);
    for (int i = 0; i < total; ++i) {
    
    
        new Thread(()->{
    
    
            try {
    
    
                sm.acquire();
                System.out.println(Thread.currentThread().getName() + "抢到车位");
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + "离开车位");
            } catch (Exception e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                sm.release();
            }
        }, String.valueOf(i)).start();
    }
}

Apenas 3 usuários podem ocupar a vaga de estacionamento ao mesmo tempo.
insira a descrição da imagem aqui

Bloqueio de leitura e gravação ReadWriteLock

Use o bloqueio de leitura e gravação para implementar um cache personalizado e apenas uma operação é permitida durante a gravação:

Etapa 1: Definir um bloqueio de leitura-gravação:
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Etapa 2: Definir uma função de operação de leitura:
adicionar um bloqueio de leitura antes de ler
readWriteLock.readLock().lock();
Liberar o bloqueio após a leitura
Etapa 3: Defina a função de operação de gravação ()
para adicionar um bloqueio de gravação ao escrever e liberar o bloqueio após a gravação.

class MyCache {
    
    
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Map<String, Object> mp = new HashMap<>();

    public void put(String s, Object o) {
    
    
        readWriteLock.writeLock().lock();
        try {
    
    
            mp.put(s, o);
            System.out.println(Thread.currentThread().getName() + "插入:" + s);
        } catch (Exception exception) {
    
    
            exception.printStackTrace();
        } finally {
    
    
            readWriteLock.writeLock().unlock();
        }
    }

    public Object get(String s) {
    
    
        Object ans = null;
        readWriteLock.readLock().lock();
        try {
    
    
            ans = mp.get(s);
            System.out.println(Thread.currentThread().getName() + "查询:" + s);
        } catch (Exception exception) {
    
    
            exception.printStackTrace();
        } finally {
    
    
            readWriteLock.readLock().unlock();
        }
        return ans;
    }
}

Quatro interfaces de função principais

Uma interface funcional é uma interface que define apenas um método abstrato ou uma interface anotada com @FunctionalInterface. Pode haver métodos padrão.
As quatro interfaces funcionais são:

  • Interface de função funcional (Função): uma entrada de interface é a entrada da função e a outra é a saída da função
  • Interface da função do consumidor (Consumidor): a entrada da interface é a entrada da função e o retorno da função é um valor booleano
  • Interface Funcional de Fornecimento (Fornecedor)
  • Interface Funcional de Predicado (Predicado)

função / funcional

Interface funcional: uma entrada de interface é a entrada da função e a outra é a saída da função

@FunctionalInterface
public interface Function<T, R> {
    
    

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
    // ... 两个默认函数和一个静态函数
}

tipo de afirmação

Interface de determinação: a entrada da interface é a função de entrada da função e o retorno da função é um valor booleano

public interface Predicate<T> {
    
    

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
    // ...三个默认函数和一个静态函数
}

Tipo de consumo

Interface do consumidor: apenas entrada, sem saída

@FunctionalInterface
public interface Consumer<T> {
    
    

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
    // ...一个默认方法
}

tipo de fornecimento

Interface de alimentação: somente saída, sem entrada

@FunctionalInterface
public interface Supplier<T> {
    
    

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}
        Consumer consumer = (str) -> {
    
    
            System.out.println(str);
        };
        consumer.accept("Happy");
    }

Caso de uso – programação de streaming

    /**
    有5个用户,筛选
     1、ID 必须是偶数
     2、年龄必须大于23
     3、用户名转大写字母
     4、倒序排序
     5、只需要一个用户
     **/
    public static void main(String[] args) {
    
    
        List<User> list = new ArrayList<>();
        Collections.addAll(list,
                new User(0, 22, "lzy"),
                new User(1, 20, "blzy"),
                new User(2, 25, "azy"),
                new User(3, 24, "czy"),
                new User(4, 24, "dzy"),
                new User(5, 24, "ezy"),
                new User(6, 24, "fzy"),
                new User(7, 24, "gsy"));
        list.stream().filter(e -> {
    
    return e.getId() % 2 == 1;})
                     .filter(e -> {
    
    return e.getAge() > 23;})
                     .map(e -> {
    
    return e.getUsername().toUpperCase();})
                     .sorted(Comparator.reverseOrder())
                     .limit(1)
                     .forEach(System.out::println);

    }

Acho que você gosta

Origin blog.csdn.net/baiduwaimai/article/details/132083149
Recomendado
Clasificación