fundação juc (2)

Índice

1. Segurança do fio de coleta

1. Coleção de listas

2. Conjunto de hash

3. mapa hash

Dois bloqueios multithread

3. Interface chamável e futura  

1. Interface chamável

2. Interface futura

3. Tarefa Futura

4. Três turmas auxiliares da JUC

1. Reduza a contagem CountDownLatch

2. Barreira Cíclica

3. Semáforo de luz de sinalização


1. Segurança do fio de coleta

1. Coleção de listas

List não é thread-safe e o exemplo a seguir reportará um erro ao executar

/**
 * 集合线程安全案例
 */
public class NotSafeDemo {
    /**
     * 多个线程同时对集合进行修改
     *
     * @param args
     */
    public static void main(String[] args) {
        List list = new ArrayList();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString());
                System.out.println(list);
            }, "线程" + i).start();
        }
    }
}
Conteúdo anormal
java.util.ConcurrentModificationException
Pergunta: Por que ocorrem exceções de modificação simultânea?
Verifique o código-fonte do método add de ArrayList e descubra que nenhum bloqueio é usado

três soluções

① vetor

List<String> list = new Vector<>();

② Coleções

List<String> list = Collections.synchronizedList(new ArrayList<>());

③ CopyOnWriteArrayList

List<String> list = new CopyOnWriteArrayList<>();

2. Conjunto de hash

        //演示Hashset
        Set<String> set = new HashSet<>();

        for (int i = 0; i <30; i++) {
            new Thread(()->{
                //向集合添加内容
                set.add(UUID.randomUUID().toString().substring(0,8));
                //从集合获取内容
                System.out.println(set);
            },String.valueOf(i)).start();
        }

 solução

Set<String> set = new CopyOnWriteArraySet<>();

3. mapa hash

        //演示HashMap
        Map<String,String> map = new HashMap<>();

        for (int i = 0; i <30; i++) {
            String key = String.valueOf(i);
            new Thread(()->{
                //向集合添加内容
                map.put(key,UUID.randomUUID().toString().substring(0,8));
                //从集合获取内容
                System.out.println(map);
            },String.valueOf(i)).start();
        }

 

solução

Map<String,String> map = new ConcurrentHashMap<>();

 

Resumo (ênfase)
1. Coleções thread-safe e thread-insafe
Existem dois tipos de tipos de coleção thread-safe e thread-insafe, exemplos comuns são:
ArrayList ----- Vetor
HashMap-----HashTable
No entanto, todos os itens acima são implementados por meio da palavra-chave sincronizada, que é menos eficiente
2. A coleção thread-safe construída por Collections 3. java.util.concurrent no pacote simultâneo
CopyOnWriteArrayList Tipo CopyOnWriteArraySet, por meio de array dinâmico e segurança de thread
Segurança de rosca garantida em todos os aspectos

Dois bloqueios multithread

Demonstração de oito perguntas de fechaduras

class Phone {

    public static synchronized void sendSMS() throws Exception {
        //停留4秒
        TimeUnit.SECONDS.sleep(4);
        System.out.println("------sendSMS");
    }

    public synchronized void sendEmail() throws Exception {
        System.out.println("------sendEmail");
    }

    public void getHello() {
        System.out.println("------getHello");
    }
}

/**
 * @Description: 8锁
 *
1 标准访问,先打印短信还是邮件
------sendSMS
------sendEmail

2 停4秒在短信方法内,先打印短信还是邮件
------sendSMS
------sendEmail   --前两个锁的是对象

3 新增普通的hello方法,是先打短信还是hello
------getHello
------sendSMS

4 现在有两部手机,先打印短信还是邮件
------sendEmail
------sendSMS     --不同对象锁自己

5 两个静态同步方法,1部手机,先打印短信还是邮件
------sendSMS
------sendEmail

6 两个静态同步方法,2部手机,先打印短信还是邮件
------sendSMS
------sendEmail   -- 5和6锁的是当前对象的Class

7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
------sendEmail
------sendSMS

8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
------sendEmail
------sendSMS

 */

public class Lock_8 {
    public static void main(String[] args) throws Exception {

        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "AA").start();

        Thread.sleep(100);

        new Thread(() -> {
            try {
               // phone.sendEmail();
               // phone.getHello();
                phone2.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "BB").start();
    }
}

 Conclusão :

(1) Se houver vários métodos sincronizados em um objeto, em um determinado momento, apenas um thread chama um deles
Um método sincronizado , outros threads só podem esperar, ou seja, em um determinado momento, apenas um thread pode acessar esses métodos sincronizados
O que está bloqueado é o objeto atual this , após ser bloqueado, outros threads não podem entrar em outros métodos sincronizados do objeto atual
(2) Depois de adicionar um método comum, descobre-se que ele não tem nada a ver com o bloqueio de sincronização
(3) Depois de mudar para dois objetos, o bloqueio não é o mesmo e a situação muda imediatamente.
A base da implementação sincronizada da sincronização: cada objeto em Java pode ser usado como um bloqueio.
Especificamente, manifesta-se nas três formas seguintes.
Para métodos sincronizados normais, o bloqueio é o objeto da instância atual.
Para métodos sincronizados estáticos, o bloqueio é o objeto Class da classe atual.
Para um bloco de método sincronizado, o bloqueio é o objeto configurado entre colchetes Synchonized
Quando um thread tenta acessar um bloco de código sincronizado, ele deve primeiro adquirir o bloqueio e liberá-lo ao sair ou lançar uma exceção.
Ou seja, se o método de sincronização não estático de um objeto de instância adquirir o bloqueio, outros métodos de sincronização não estáticos do objeto de instância deverão aguardar que o método de aquisição de bloqueio libere o bloqueio antes de adquiri-lo.
No entanto, os métodos de sincronização não estáticos de outros objetos de instância usam bloqueios diferentes dos métodos de sincronização não estáticos do objeto de instância, para que possam adquirir seus próprios bloqueios sem esperar pelos métodos de sincronização não estáticos do objeto de instância que adquiriram a trava para liberar a trava.
Todos os métodos de sincronização estática também usam o mesmo bloqueio - o próprio objeto de classe. Esses dois bloqueios são dois objetos diferentes, portanto, não haverá condições de corrida entre métodos de sincronização estáticos e métodos de sincronização não estáticos.
Mas uma vez que um método de sincronização estática adquire o bloqueio, outros métodos de sincronização estática devem esperar que o método libere o bloqueio antes de adquiri-lo, independentemente de ser entre métodos de sincronização estática do mesmo objeto de instância ou métodos de sincronização estática de objetos de instância diferentes Entre, desde que sejam objetos de instância da mesma classe!

3. Interface chamável e futura 

1. Interface chamável

As características da interface Callable são as seguintes (ênfase)
  • Para implementar um Runnable, você precisa implementar um método run() que não retorne nada, e para um Callable, você precisa implementar um método call() que retorne um resultado na conclusão.
  • O método call() pode lançar exceções, enquanto run() não.
  • O método call deve ser substituído para implementar Callable
  • Não é possível substituir diretamente o executável, porque o construtor da classe Thread não possui nenhum Callable
创建新类 MyThread 实现 runnable 接口
class MyThread implements Runnable{
    @Override
    public void run() {
    }
}

新类 MyThread2 实现 callable 接口
class MyThread2 implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        return 200;
    }
}

 2. Interface futura

Quando o método call() for concluído, o resultado deve ser armazenado em um objeto conhecido pelo thread principal para que o thread principal possa saber qual resultado foi retornado por aquele thread. Para fazer isso, um objeto Future pode ser usado. Pense em um Future como um objeto que contém o resultado - ele pode não reter o resultado por um tempo, mas o manterá no futuro (assim que Callable retornar) . Futuro é basicamente algo que o thread principal pode acompanhar o progresso e os resultados de outros threads
um método. Para implementar esta interface, 5 métodos devem ser reescritos. Os métodos importantes estão listados aqui, como segue:
  • cancelamento booleano público (boolean mayInterrupt): usado para interromper a tarefa.
== Ele interromperá a tarefa se ainda não tiver sido iniciada. Se iniciada, a tarefa só será interrompida se mayInterrupt for verdadeiro. ==
  • public Object get() lança InterruptedException, ExecutionException: usado para obter o resultado da tarefa.
== Se a tarefa for concluída, ele retornará o resultado imediatamente, caso contrário, aguardará a conclusão da tarefa e retornará o resultado. ==
  • public boolean isDone(): retorna verdadeiro se a tarefa for concluída, caso contrário retorna falso
Você pode ver que Callable e Future fazem duas coisas - Callable é semelhante a Runnable, pois encapsula uma tarefa a ser executada em outro thread, e Future é usado para armazenar resultados obtidos de outro thread. Na verdade, os futuros também podem ser usados ​​com Runnables.
Para criar um thread, é necessário um Runnable. Para obter um resultado é necessário um futuro.

3. Tarefa Futura

A biblioteca Java possui um tipo FutureTask concreto, que implementa Runnable e Future e combina convenientemente as duas funções. Uma FutureTask pode ser criada fornecendo um Callable ao seu construtor. Em seguida, forneça o objeto FutureTask ao construtor do Thread para criar o objeto Thread. Então, indiretamente, usando Callable para criar threads.
Princípios fundamentais: (ênfase)
Quando você precisar realizar operações demoradas no thread principal, mas não quiser bloquear o thread principal, você pode entregar esses trabalhos ao objeto Future para serem concluídos em segundo plano
Quando o thread principal precisar no futuro, o resultado do cálculo ou o status de execução do trabalho em segundo plano poderá ser obtido por meio do objeto Future
Geralmente, FutureTask é usado principalmente para cálculos demorados, e o thread principal pode obter os resultados após concluir suas próprias tarefas.
O resultado só pode ser recuperado quando o cálculo for concluído; se o cálculo ainda não tiver sido concluído, o método get será bloqueado
Depois que um cálculo for concluído, ele não poderá ser reiniciado ou cancelado
método get e o resultado só pode ser obtido quando o cálculo for concluído, caso contrário ele será bloqueado até que a tarefa entre no estado de conclusão e então o resultado será retornado ou uma exceção será lançada
get é calculado apenas uma vez, então o método get é colocado por último

demonstração

//比较两个接口
//实现Runnable接口
class MyThread1 implements Runnable {
    @Override
    public void run() {

    }
}

//实现Callable接口
class MyThread2 implements Callable {

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+" come in callable");
        return 200;
    }
}

public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //Runnable接口创建线程
        new Thread(new MyThread1(),"AA").start();

        //Callable接口,报错
       // new Thread(new MyThread2(),"BB").start();

        // Thread没有传递参数为callable的方法,那么如何使用callable?
        // 找一个中间人,既认识Runnable又认识Callable的就可以了————FutureTask
        //FutureTask除了实现了Future接口以外,FutureTask还实现了Runnable接口
        FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());

        //lam表达式 callable接口是函数式接口,可以简化
        FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
            System.out.println(Thread.currentThread().getName()+" come in callable");
            return 1024;
        });

        //创建一个线程
        new Thread(futureTask2,"lucy").start();
        new Thread(futureTask1,"mary").start();

//        while(!futureTask2.isDone()) {
//            System.out.println("wait.....");
//        }
        //调用FutureTask的get方法
        System.out.println(futureTask2.get());

        System.out.println(futureTask1.get());

        System.out.println(Thread.currentThread().getName()+" come over");
        //FutureTask原理  未来任务
        /**
         * 1、老师上课,口渴了,去买票不合适,讲课线程继续。
         *   单开启线程找班上班长帮我买水,把水买回来,需要时候直接get
         *
         * 2、4个同学, 1同学 1+2...5   ,  2同学 10+11+12....50, 3同学 60+61+62,  4同学 100+200
         *      第2个同学计算量比较大,
         *     FutureTask单开启线程给2同学计算,先汇总 1 3 4 ,最后等2同学计算位完成,统一汇总
         *
         * 3、考试,做会做的题目,最后看不会做的题目
         *
         * 汇总一次
         *
         */

    }
}
resumo
Quando você precisar realizar operações demoradas no thread principal, mas não quiser bloquear o thread principal, você pode entregar essas tarefas ao objeto Future para serem concluídas em segundo plano. Quando o thread principal precisar disso no futuro , você pode obter o cálculo do trabalho em segundo plano por meio do resultado do objeto Future ou do status de execução. Geralmente, FutureTask é usado principalmente para cálculos demorados. O thread principal pode obter os resultados após concluir suas próprias tarefas.
O resultado só pode ser recuperado quando o cálculo for concluído; se o cálculo não for concluído, o método get será bloqueado. Depois que um cálculo for concluído, ele não poderá ser reiniciado ou cancelado. get e o resultado só podem ser obtidos quando o cálculo for concluído, caso contrário ele será bloqueado até que a tarefa entre no estado de conclusão, e então o resultado será retornado ou uma exceção será lançada.
Calculado apenas uma vez

4. Três turmas auxiliares da JUC

JUC fornece três classes auxiliares comumente usadas, através das quais o número de threads pode ser resolvido muito bem.
Operações freqüentes de bloqueios multi-time. As três classes auxiliares são:
  • CountDownLatch: Diminui a contagem
  • CyclicBarrier: Barreira Cíclica
  • Semáforo: Semáforo

1. Reduza a contagem CountDownLatch

CountDownLatch possui dois métodos principais. Quando um ou mais threads chamam o método await, esses threads serão bloqueados
Outros threads que chamam o método countDown diminuirão o contador em 1 (threads que chamam o método countDown não serão bloqueados)
Quando o valor do contador se tornar 0, o thread bloqueado pelo método await será acordado e continuará em execução
Cena: Os alunos de plantão só poderão fechar a porta após 6 alunos saírem da sala um após o outro.

//演示 CountDownLatch
public class CountDownLatchDemo {
    //6个同学陆续离开教室之后,班长锁门
    public static void main(String[] args) throws InterruptedException {

        //创建CountDownLatch对象,设置初始值
        CountDownLatch countDownLatch = new CountDownLatch(6);

        //6个同学陆续离开教室之后
        for (int i = 1; i <=6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");

                //计数  -1
                countDownLatch.countDown();

            },String.valueOf(i)).start();
        }

        //等待
        countDownLatch.await();

        System.out.println(Thread.currentThread().getName()+" 班长锁门走人了");
    }
}

2.  Barreira Cíclica

O primeiro parâmetro do método de construção de CyclicBarrier é o número de barreiras alvo. O número de barreiras será aumentado em um cada vez que CyclicBarrier for executado. Se o número alvo de barreiras for atingido, a instrução após cyclicBarrier.await() será executado. CyclicBarrier pode ser entendido como a adição de 1 operação
Cena: Colete todas as 7 Dragon Balls para invocar Shenlong
/*
 * CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。
 * 它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,
 * 屏障才会开门,所有被屏障拦截的线程才会继续干活。
 * CyclicBarrier 默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,
 * 每个线程调用 await 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
 */
//集齐7颗龙珠就可以召唤神龙
public class CyclicBarrierDemo {

    //创建固定值
    private static final int NUMBER = 7;

    public static void main(String[] args) {
        //创建CyclicBarrier
        CyclicBarrier cyclicBarrier =
                new CyclicBarrier(NUMBER,()->{
                    System.out.println("*****集齐7颗龙珠就可以召唤神龙");
                });

        //集齐七颗龙珠过程
        for (int i = 1; i <=7; i++) {
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+" 星龙被收集到了");
                    //等待
                    cyclicBarrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

3. Semáforo de luz de sinalização

O primeiro parâmetro passado no método de construção do Semáforo é o semáforo máximo (que pode ser considerado o maior pool de threads), e cada semáforo é inicializado com no máximo uma licença. Use o método de aquisição para obter uma licença e o método de liberação para liberar uma licença
Cenário: Captura de carros, 6 carros e 3 vagas de garagem
//6辆汽车,停3个车位
public class SemaphoreDemo {
    public static void main(String[] args) {
        //创建Semaphore,设置许可数量
        Semaphore semaphore = new Semaphore(3);

        //模拟6辆汽车
        for (int i = 1; i <=6; i++) {
            new Thread(()->{
                try {
                    //抢占
                    semaphore.acquire();

                    System.out.println(Thread.currentThread().getName()+" 抢到了车位");

                    //设置随机停车时间
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));

                    System.out.println(Thread.currentThread().getName()+" ------离开了车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //释放
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

Acho que você gosta

Origin blog.csdn.net/m0_62946761/article/details/132434579
Recomendado
Clasificación