JUC深入浅出,第一篇!!!!!

1、什么是JUC

2、线程和进程

3、并发和并行

4、Lock锁(重点)

5、生产者消费者问题

6、8锁现象

7、集合类不安全(list、set、map)

8、Callable

9、常用的辅助类(CountDownLatch、CyclicBarrier、Semaphore)


JUC第二篇




1、 什么是JUC

juc 就是java.util.concurrent
java.uti工具包、包、分类

业务:普通的线程代码 Thread

Runable 没有返回值、效率相比 Callable 相对较低!
在这里插入图片描述
在这里插入图片描述




2、进程和线程:


线程、进程、解释;

进程:—个程序,QQ.exe Music.exe程序的集合;

一个进程往往可以包含多个线程,至少包含—个!

Java默认有几个线程?2个mian、GC

那么我们说说线程是什么?例子说明;

线程:开了—个进程Typora ,写字,自动保存(线程负责的)

对于Java而言: Thread、Runnable、Callable

java真的可以开启线程吗?

开不了的;java没有权开启线程的;

为什么开启不了?

虽然我们 new一个start但是我们开启线程的是本地方法start0( )这个方法开启的;native;直接调用本地的c++程序;



  • 就是在start里面的start0()方法的源码;
public class Demo01 {
    public static void main(String[] args) {
        new Thread().start();
    }



public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}
//我们调用start方法其实里面直接调用的是start0方法java无法直接操作
  //硬件,因为java运行在JVM上的;
private native void start0();

3、 并发和并行

并发、并行

并发(多个线程操作同一个资源)

  • CPU一核的,模拟出来多个线程,也就是交替执行

并行(多个人一起行走);提高线程,我们可以使用线程池的技术

  • CPU是多核的,多个线程共同执行;
public class Demo01 {
    public static void main(String[] args) {
        /*new Thread().start();*/
        //快速查看能运行多少个线程;也就是获取CPU的核数
        //CPU 密集型、IO密集型
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}

结果是12;
  这个结果说,我可以运行12个线程;

并发编程的本质:充分利用CPU的资源

线程有几个状态 :

直接看源码:

public enum State {
 //线程新生
    NEW,

  //运行
    RUNNABLE,

   //阻塞
    BLOCKED,
   //等待,这种等待会死死地等
    WAITING,
   //超时等待,这种等待等到一定时间就会不等你了
    TIMED_WAITING,
   //终止;terminated
    TERMINATED;
}

里面有6中状态;

wait/sleep区别 :

1、来自不同的类 :

wait=>Object

sleep =>thread

(其实企业中很少用sleep而是使用juc中的TimeUnit.DAYS.sleep(1); 引用的是import java.util.concurrent.TimeUnit;)

2、关于锁的释放:

wait会释放锁,

sleep睡觉了,抱着锁睡觉,不会释放!

3、使用的范围

同步代码块指在代码块前加上 synchronized关键字的代码块。

同步代码块的格式:

synchronized(同步对象){

需要同步的代码;

}

wait:必须是在同步代码块中

sleep:可以在任何地方睡觉

4、是否需要捕获异常

wait不需要捕获异常

sleep必须要捕获异常


注意 :线程都需要有异常就是中断异常


5、锁

1、synchronized、传统锁

/**
 * 一个真正的多线程开发,公司中
 * 线程就是一个单独的资源类,没有任何复数的操作!
 * 1、在资源类中就是有  属性和方法
 */
public class SaleTicketDemo01 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        //并发就是把资源类丢入线程
        //但是在线程里面有一个Runable接口,里面
        // @FunctionalInterface 函数是接口
        //里面new一个函数接口,然后实行里面的方法,这个就是
        //匿名内部类;
        /**
         * 但是我们写匿名内部类太复杂了;我们可以是使用
         * lambda 简化一下;(写参数的)->{ 代码}这个就行;直接可以简化内部匿名的格式
         */
        new Thread(()->{
            for (int i = 1; i <60 ; i++) {
                ticket.sale();
            } },"A").start();
        new Thread(()->{ for (int i = 1; i <60 ; i++) {
            ticket.sale();
        } },"B").start();
        new Thread(()->{  for (int i = 1; i <60 ; i++) {
            ticket.sale();
        }},"C").start();
    }
}

//资源类oop
class Ticket {
    //属性和方法
    private int number = 60;

    public void sale()
    {
        if (number>0){
            System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number);
        }
    }
}

结果:
没有加锁的情况下:

A卖出了7票,剩余6
A卖出了6票,剩余5
C卖出了18票,剩余17
C卖出了4票,剩余3
C卖出了3票,剩余2
B卖出了19票,剩余18
C卖出了2票,剩余1
A卖出了5票,剩余4
B卖出了1票,剩余0

添加一个synchronized

就是在资源的方法上添加一个锁;

synchronize锁本质就是:队列、锁就机制

//资源类oop
class Ticket {
    //属性和方法
    private int number = 60;

    public synchronized void sale()
    {
        if (number>0){
            System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number);
        }
    }
}

A卖出了13票,剩余12
A卖出了12票,剩余11
A卖出了11票,剩余10
A卖出了10票,剩余9
A卖出了9票,剩余8
A卖出了8票,剩余7
A卖出了7票,剩余6
A卖出了6票,剩余5
A卖出了5票,剩余4
A卖出了4票,剩余3
A卖出了3票,剩余2
A卖出了2票,剩余1
A卖出了1票,剩余0

结果很正规;



2、lock的接口

如果我们是用的话肯定是使用里面的实现类;
在这里插入图片描述
在这里插入图片描述
1、第一个我们使用的是重入锁:
在这里插入图片描述
公平锁∶十分公平∶可以先来后到

非公平锁:十分不公平:可以插队(默认)

使用的步骤:

  • 1\首先需要new 一个锁
  • 2、然后加锁 对象.lock
  • 3\使用try catch fially{解锁}语句进行
public class SaleTicketDemo02 {
    public static void main(String[] args) {
        Ticket2 ticket = new Ticket2();
        //并发就是把资源类丢入线程
        //但是在线程里面有一个Runable接口,里面
        // @FunctionalInterface 函数是接口
        //里面new一个函数接口,然后实行里面的方法,这个就是
        //匿名内部类;
        /**
         * 但是我们写匿名内部类太复杂了;我们可以是使用
         * lambda 简化一下;(写参数的)->{ 代码}这个就行;直接可以简化内部匿名的格式
         */
        new Thread(()->{
            for (int i = 1; i <60 ; i++) ticket.sale();},"A").start();
        new Thread(()->{ for (int i = 1; i <60 ; i++) {
            ticket.sale();
        } },"B").start();
        new Thread(()->{  for (int i = 1; i <60 ; i++) {
            ticket.sale();
        }},"C").start();
    }
}

/**
 * 1\首先需要new 一个锁
 * 2、然后加锁 对象.lock
 * 3\使用try catch fially{解锁}语句进行
 */
class Ticket2 {
    //属性和方法
    private int number = 60;
   Lock lock= new ReentrantLock();
    public void sale()
    {
        //加锁操作:
        lock.lock();
        try {

            //业务代码
            if (number>0){
                System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //解锁操作
            lock.unlock();
        }
    }
}

总结:其实和前面的都差不多就是在资源里面的锁的机制改变了;



3、synchronized 和lock的区别:

1、Synchronized内置的Java关键字,Lock是一个Java类(java接口)

2、Synchronized无法判断获取锁的状态,Lock 可以判断是否获取到了锁

//尝试获取所;返回值是boolean类型的
System.out.println(lock.tryLock());

3.Synchronized会自动释放锁,lock必须要手动释放锁!如果不释放锁,死锁

4、synchronized线程1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去;lock.tryLock()我们可以在这个里面放入时间;等待到一定时间就会放弃了

5、Synchronized可重入锁,不可以中断的,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置);

6.Synchronized适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!



5、生产者和消费者

面试的:单例模式、排序算法、生产者和消费者、死锁

public class PC {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
//资源类
//步骤就是传统的synchronized
// 1判断,但是两个线程还好,如果线程比较多的话if就不行了,因为判断一次就出来了。用while
// 2如果判断成功的话等待
// 3,不成功的话+1操作然后通知其他线程
class Data{
    //属性和方法
    private int number=0;
    //+1操作
    public synchronized void increment() throws InterruptedException {
        if (number!=0){
         this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"剩余的"+number);
        //通知其他线程
        this.notifyAll();
    }
    //减一操作
    public synchronized void decrement() throws InterruptedException {
        if (number==0){
          this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"剩余的"+number);
        //通知其他线程
        this.notifyAll();
    }
    }


结果:

A剩余的1
B剩余的0
C剩余的1
D剩余的0
B剩余的-1
B剩余的-2
B剩余的-3

很明显出现了虚假唤醒的问题:
在这里插入图片描述

就是在多个线程中可能会出现虚假唤醒的问题;我们需要把if改成while;

在这里插入图片描述
线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒

就是线程其实一多在使用if就不行了,因为if只能判断一次就出来了;那么我们需要使用能进行多次判断的,使用while就解决这个问题了;

解决之后:
结果:
D剩余的0
C剩余的1
D剩余的0
C剩余的1
D剩余的0
C剩余的1
D剩余的0



2、JUC版的生产者消费者问题

通过lock找到condition ( ) 这个方法


根据和synchronized和lock的比较

在这里插入图片描述
代码实现:

public class PC01 {
    public static void main(String[] args) {
        Data1 data = new Data1();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
//资源类
//步骤就是lock
// 1判断,但是两个线程还好,如果线程比较多的话if就不行了,因为判断一次就出来了。用while
// 2如果判断成功的话等待这个地方就是需要一个condition进行的等待还有通知所有
// 3,不成功的话+1操作然后通知其他线程
class Data1{
    //属性和方法
    private int number=0;
    //new一个锁
    Lock lock= new ReentrantLock();
    Condition condition = lock.newCondition();
    //+1操作
    public void increment() throws InterruptedException {

          lock.lock();
        try {
            while (number!=0){
                //等待的
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+"剩余的"+number);
            //通知其他线程
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //解除锁
            lock.unlock();
        }

    }
    //减一操作
    public void decrement() throws InterruptedException {
       lock.lock();
        try {
            while (number==0){
           condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"剩余的"+number);
            //通知其他线程
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

结果还是一样的,都可以成功的;

还有个地方就是lock的使用的比起传统的代码需要try包裹起来;我认为安全性会更高一些;

一个新的技术绝对不是,覆盖原来的技术,优势和补充的

condition 精准的通知和唤醒线程 :

在这里插入图片描述
没有顺序可言的;我们想让他依次执行可以吗?

答案是;可以的完全可以的;

代码:

注意:

新版的监视器;一个监视器可以监视一个类;

监视器也就是condition

还有个细节就是使用的是同一把锁;所有我们每次只能进来一个;

线程condition调用的问题就是;你想使用那个资源里的方法,你就使用condition监控那个方法的监视器,给那个线程一个通知就行了;但是里面的等待还是那个监控器控制的线程;

try {
    //代码
    while (number!=2){
        condition2.await();
    }
    number=3;
    System.out.println(Thread.currentThread().getName() + "=>bbbbbbb");
    condition3.signal();
  
  
   try {
            //代码
            while (number!=3){
             condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "CCCC");
            number=1;
            condition1.signal();
/**
 * 线程可以让它根据我们的顺序进行
 */
public class PC02 {
    public static void main(String[] args) {
        Data3 data = new Data3();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                data.printA();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                data.printB();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                data.printC();
            }
        },"C").start();

    }

}


class Data3{
    Lock lock=new ReentrantLock();
    private int number=1;
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
    public void printA()
    {
        lock.lock();
        try {
            //代码
            while (number!=1){
                condition1.await();
            }
            number=2;
            System.out.println(Thread.currentThread().getName()+"=>AAAAA");
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printB()
    {
        lock.lock();
        try {
            //代码
            while (number!=2){
                condition2.await();
            }
            number=3;
            System.out.println(Thread.currentThread().getName() + "=>bbbbbbb");
            condition3.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printC()
    {
        lock.lock();
        try {
            //代码
            while (number!=3){
             condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "CCCC");
            number=1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}


6、 8锁现象

如何判断锁的是谁;永远知道什么是锁,锁到底锁的是谁;

1、现象;两个都有锁的方法;线程先调用那个?

/**
 * 8种锁的现象,就是关于所的8个现象
 * 1、标准情况下,两个线程先打印 发短信 还是打电话? 1/发短信 2、打电话
 *发短信
 */
public class Demo02 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendSms();
        },"A").start();
//        延迟一秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        },"A").start();
    }
}
class Phone{
    public synchronized void sendSms()
    {
        System.out.println("发短信");

    }
    public synchronized void call()
    {
        System.out.println("打电话");
    }
}

总结 :我们使用我的是锁的对象是方法的调用者!

但是资源里面的两个方法用的是同一个锁都是使用new Phone()这个对象,这个对象谁先拿到对象调用的方法就是谁先执行;

2、现象;一个是带锁;另一个不带锁(也就是说没有锁的方法),那么线程现调用哪一个呢?

public class Demo03 {
    public static void main(String[] args) throws InterruptedException {
        Phone3 phone = new Phone3();
        new Thread(()->{
            try {
                phone.sendSms();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
//        延迟一秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.hello();
        },"B").start();
    }
}
class Phone3{
    public synchronized void sendSms() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("发短信");

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

结果:这个东西不受锁的影响;同样是一个对象调用;虽然谁先调用。还是先运行谁的方法,但是里面的hello是不受锁的影响的;一旦锁的进行sleep了,那么hello方法就会执行;


3、问题两个对象调用不同的方法,方法都是加锁的,那么会执行那个;一个方法中使用睡眠4秒,另一个没有睡眠

public class Demo03 {
    public static void main(String[] args) throws InterruptedException {
        Phone3 phone = new Phone3();
        Phone3 phone1=new Phone3();
        new Thread(()->{
            try {
                phone.sendSms();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
//        延迟一秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone1.hello();
        },"B").start();
    }
}
class Phone3{
    public synchronized void sendSms() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("发短信");

    }
    public synchronized void hello()
    {
        System.out.println("hello");
    }
}

结果:肯定先执行没有睡觉的那个方法;因为是两个不同的对象嘛;谁先枪住谁执行的;没有延迟没有办法玩;


4、在锁的前面添加一个static 那个那么一个先执行;

public class Demo04 {
    public static void main(String[] args) throws InterruptedException {
        Phone4 phone = new Phone4();
    
        new Thread(()->{
            try {
                phone.sendSms();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
//        延迟一秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.hello();
        },"B").start();
    }
}
class Phone4{
    public static synchronized void sendSms() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("发短信");

    }
    public static synchronized void hello()
    {
        System.out.println("hello");
    }
}

发短信
hello

结果 :static 标注这个方法肯定是一个静态方法;又因为这个资源类是一个全局唯一,这个地方锁的是class对象;因为这个用的是同一把锁,所以只能谁先拿到,然后执行谁;

5、两个对象还是两个同步方法,还是静态的;那么我们先打印那个?

public class Demo04 {
    public static void main(String[] args) throws InterruptedException {
        Phone4 phone = new Phone4();
        Phone4 phone1 = new Phone4();
        new Thread(()->{
            try {
                phone.sendSms();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
//        延迟一秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone1.hello();
        },"B").start();
    }
}
class Phone4{
    public static synchronized void sendSms() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("发短信");

    }
    public static synchronized void hello()
    {
        System.out.println("hello");
    }
}

结果 : 虽然我们使用的是两个对象,如果没有static的话,肯定是没有睡觉的先执行;但是我们添加的是static,这个时候我们锁的是一个Class,那么先执行的肯定是那个对象先调,就先执行那个;


6、一个是静态加锁另一个是直接加锁,这个是一个对象的,先执行哪一个

public class Demo04 {
    public static void main(String[] args) throws InterruptedException {
        Phone4 phone = new Phone4();
    
        new Thread(()->{
            try {
                phone.sendSms();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
//        延迟一秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.hello();
        },"B").start();
    }
}
class Phone4{
    public static synchronized void sendSms() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("发短信");

    }
    public  synchronized void hello()
    {
        System.out.println("hello");
    }
}

结果 : 先分析就是静态加锁 锁的是class类,而另一个锁,并不是锁的Class,所以两个是不相同的锁,所以互不干扰;谁快谁就会先输出;


7、尽管是两个对象,一个是静态加锁另一个是直接加锁,先执行哪一个

public class Demo04 {
    public static void main(String[] args) throws InterruptedException {
        Phone4 phone = new Phone4();
        Phone4 phone1 = new Phone4();
        new Thread(()->{
            try {
                phone.sendSms();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
//        延迟一秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone1.hello();
        },"B").start();
    }
}
class Phone4{
    public static synchronized void sendSms() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("发短信");

    }
    public  synchronized void hello()
    {
        System.out.println("hello");
    }
}

结果 : 先分析就是静态加锁 锁的是class类,而另一个锁,并不是锁的Class,所以两个是不相同的锁,所以互不干扰;谁快谁就会先输出;


7、集合类不安全

因为我们使用的Juc是并发执行的;所以我们以前写的很多list…都不是很安全的;但是单线程确实很安全,但是我们只是使用于我们自己罢了;人一多就完蛋;

在这里插入图片描述
这个三个是同级的;

1、 List不安全

如果我们使用一般发的线程使用list集合的时候,会出错!

只要在并发集合下都会出现这个错误

java.util.ConcurrentModificationException 并发修改异常

1、在并发下我们看出List list = new ArrayList<>( );是不安全的会出现异常的现象;我们现在的问题该怎么解决这个问题?

有三个方法:

1、List list = new Vector<>(); 我们可以使用vector 默认就是安全的;面试不要回答这个;因为这个在jdk1.0就用了;有点老土了;

2、他不安全我们可以让他变的安全就行了吧!List list = Collections.synchronizedList(new ArrayList<>());

3、List list = new CopyOnWriteArrayList<>();

CopyOnWrite写入时复制;简称cow计算机程序设计领域的优化策略;就是在多个线程调用的时候,list,读取固定,写入(覆盖)用了这个写入的时候避免覆盖,造成了数据问题。

//说到这个时候还会谈到读写分离的机制;mycat 这个是数据库界别的;

4、注意:为什么我们不用vector而用copyonwriteArrayList()有点在哪里呢?

因为vector 在add的时候里面用到了synchronized ;只要用到这个效率就会下降;而copyonwriteArrayList里面用到的是lock锁;里面复制一份然后在写入就行了;

代码:

1、

public class ListTest {
    public static void main(String[] args) {
        //并发下arraylist 不安全
        /**
         * 解决方案
         */
           List<String> list = new ArrayList<>();
        for (int i = 0; i <=10 ; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

2、

public class ListTest {
    public static void main(String[] args) {
        //并发下arraylist 不安全
        /**
         * 解决方案
         */
        /*   List<String> list = new ArrayList<>();*/
        /*List<String> list = new Vector<>();*/
       /* List<String> list = Collections.synchronizedList(new ArrayList<>());*/
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i <=10 ; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

2、set不安全

1、出现的问题还是并发性异常;

java.util.ConcurrentModificationException并发性修改异常

解决问题:

1、第一种就是使用我们的

Set set= Collections.synchronizedSet(new HashSet<>());

这个可以完整的解决我们的异常问题!collections一个工具类;

2、就是使用:Set set= new CopyOnWriteArraySet<>();

代码:

class HashTet {
    public static void main(String[] args) {
      /* Set<String> set=new HashSet<>();*/
       /* Set<String> set= Collections.synchronizedSet(new HashSet<>());*/
        Set<String> set= new CopyOnWriteArraySet<>();
        for (int i = 0; i <=10 ; i++) {
            new Thread(()->{
           set.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }

    }
}

3、hashset的底层是什么:

1、hashset的底层就是hashmap

用的就是hashmap里面的一个key值;因为key值无法重复的!你说底层是hashmap 里面的key就是你set的值,但是你说hashmap里面的value是什么?其实就是一个常量一个不会改变的 private static final Object PRESENT = new Object();

public HashSet() {
    map = new HashMap<>();
}

add方法就是直接put一个值:

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

4、Map 的不安全

在这里插入图片描述
1、有几个问题:

map 是这样使用我的吗?答案不是,工作中并不是这样使用的new HashMap<>();

第一个初始化容量;第二个加载因子

默认的 new HashMap<>(16,0.75);

2、使用的问题还是会出现java.util.ConcurrentModificationException

并发修改异常错误;

解决问题:

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

2、使用工具类的

Map<String, Object> map= Collections.synchronizedMap(new HashMap<>());

代码:

public class MapTest {
    public static void main(String[] args) {
        /**
         * map 是这样使用我的吗?答案不是,工作中并不是这样使用的new HashMap<>();
         * 第一个初始化容量;第二个加载因子
         * 默认的   new HashMap<>(16,0.75);
         */
       /* Map<String, Object> map = new HashMap<>();*/
       /* Map<String, Object> map=new ConcurrentHashMap<>();*/
        Map<String, Object> map= Collections.synchronizedMap(new HashMap<>());
        for (int i = 0; i <=10 ; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
                },String.valueOf(i)).start();

        }
    }
}


8、Callable

在这里插入图片描述
1、callable可以有返回值,

2、可以抛出异常

3、方法不同runnable 是run方法,而callable是 call()

在这里插入图片描述
在这里插入图片描述
Class FutureTask里面有一个这个类,这个类的改造方法可以和callable挂上关系;我们可以使用;
在这里插入图片描述
代码:

public class callableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        MyThread thread = new MyThread();
        FutureTask<Integer> futureTask = new FutureTask<>(thread);
        new Thread(futureTask,"A").start();
        //这个能抛出异常还有能打印返回的结果了;通过get方法
        Integer integer = futureTask.get();
        System.out.println(integer);
    }
}
class MyThread implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        System.out.println("call()");
        return 123;
    }
}

还有可能的是结果会产生缓存;还有get方法可能会产生阻塞;就是在返回前面添加时间,那么我们可能会产生阻塞,会一直等待,所以get方法一般都会写到最后面;

public class callableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        MyThread thread = new MyThread();
        FutureTask<Integer> futureTask = new FutureTask<>(thread);
        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start();
        //这个能抛出异常还有能打印返回的结果了;通过get方法
        Integer integer = futureTask.get();
        System.out.println(integer);
    }
}
class MyThread implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        System.out.println("call()");
        return 123;
    }
}

结果:
  两个线程为什么只有一个结果就是产生的了缓存的效果;
call()
123

总结 :就是根据这个进行的一层调用一层调用一层进行的;还有就是:1、有缓存 2、结果可能需要等待,会阻塞;



常用的辅助类

1、 CountDownLatch
       这是一个减法计数器;
在这里插入图片描述
代码:

public class CountDown {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {

                System.out.println(Thread.currentThread().getName() + "go");
                countDownLatch.countDown();//-1操作

            }, String.valueOf(i)).start();
        }
        //等待计数器归零操作之后然后在执行下面的代码
        countDownLatch.await();
        System.out.println("操作结束了");
    }

}

原理:

countDownLatch.countDown();//-1操作

//等待计数器归零操作之后然后在执行下面的代码
countDownLatch.await();

每次有线程调用countDown(); 数量减一操作;,假设计数器变为0,countDownLatch.await();就会别唤醒,继续执行;

2、CyclicBarrier

      加法计数器
在这里插入图片描述

class CyclicBarrier1 {

    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(7,()->{
            System.out.println("召唤成功了");
        });
        for (int i = 1; i <=7 ; i++) {
            new Thread(()->{
                        System.out.println("已经收集"+Thread.currentThread().getName());
                        try {
                            barrier.await( );//等待结束
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } catch (BrokenBarrierException e) {
                            e.printStackTrace();
                        }
                    }
            ).start();
        }
    }
}

其实还有一个问题就循环一个线程的时候我们有什么办法拿到 i 值吗?
其实有的:但是问题的是我们不能直接拿到的,我们需要一个中间的变量;使用的还是final的性的变量;那么问题来了,我们为什么要使用final呢?其实就是我们所说的内部匿名的类比方法的周期要长,所以我们为了保持一致性,我们需要前面添加一个final关键字;

总结 : 就是线程的问题;我们开始new cyclicBarrir之后我们进行for循环,然后我们在进行里面需要使用wait等待,每次循环 ,在计数里面都会加1操作;知道加到固有的参数之后才可以运行;

3、Semaphore

计数的信号量
在这里插入图片描述
代码:

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore= new Semaphore(3);
        for (int i = 1; i <=6 ; i++) {
        new Thread(()->{
            try {
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName()+"抢到车位");
                TimeUnit.SECONDS.sleep(2);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                //进行释放操作
                semaphore.release();
                System.out.println(Thread.currentThread().getName()+"离开车位");
            }
        }).start();
        }
    }
}

原理:

semaphore.acquire(); 获得,假设如果已经满了,等待,等待被释放为止!

semaphore.release(); 释放,会将当前的的信号量释放+1操作,然后唤醒等待的线程!

作用 : 多个共享资源互斥的使用!并发限流,控制最大线程数!

总结 : 就是每一个许可证都是可用的,通过acquire进行进行阻塞操作,然后进行,然后就是使用release进行释放,然后在一次类推;知道循环完结束本次操作;


版权声明:本文为weixin_46455069原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。