JUC01

JUC01

java 线程的高级篇

1.什么是JUC?

java.util工具包,包,分类

2.线程与进程

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

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

Java默认有几个线程?2个,main GC

线程:开了一个进程Typora, 里面有写字,自动保存等是线程负责的

Java真的可以开线程吗?不能,开不了

public class Test1 {
    public static void main(String[] args) {
        new Thread().start();
    }
}

看源码!只能通过本地方法去调

public synchronized void start() {

    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
          
        }
    }
}

private native void start0();//只能通过本地方法去调
并行   并发

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

  • CPU一核,模拟出来多条线程,唯快不破,快速交替

并行(多个人一起行走)

  • CPU多核,多个线程可以同时执行 线程池
package com.mjh;

public class Test1 {
    public static void main(String[] args) {
        //获取CPU的核数
        //cpu密集型  IO密集型
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}

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

线程有几个状态
public enum State {
   //新生
    NEW,

  //运行
    RUNNABLE,
    
  //阻塞
    BLOCKED,

   //等待   死死的等
    WAITING,

  //超时等待
    TIMED_WAITING,

   //终止
    TERMINATED;
}

3.Lock锁(重要)

为了解决多线程的并发问题

传统的 synchronized 本质就是队列 锁

package com.mjh;

public class Test1 {
    public static void main(String[] args) {
     //并发  多线程操作同一个资源类,把资源类丢入线程
        Ticket ticket = new Ticket();

        //@FunctionalInterface  函数式接口, JDK1.8  lambda表达式(参数)——>{代码}
       /* new Thread(new Runnable() {
            @Override
            public void run() {

            }
        }).start();*/

        new Thread(()->{
            for (int i = 0; i <40 ; i++) {
                ticket.sale();
            }
            },"A").start();
        new Thread(()->{
            for (int i = 0; i <40 ; i++) {
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{for (int i = 0; i <40 ; i++) {
            ticket.sale();
        }
        },"C").start();


    }
}
class Ticket{
    //属性,方法
    private int number = 30;

    //卖票的方式
    // synchronized  同步  解决多个人同时抢而造成票数混杂问题  等一个线程执行完了再执行下一个线程
    public synchronized void  sale(){
        if(number>0){
            System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余:"+number);
        }
    }
}

lock 接口

公平锁:十分公平 先来后到

非公平锁:十分不公平 可以插队

package com.mjh;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test2 {

    public static void main(String[] args) {
        //并发  多线程操作同一个资源类,把资源类丢入线程
      Ticket2 ticket = new Ticket2();

        //@FunctionalInterface  函数式接口, JDK1.8  lambda表达式(参数)——>{代码}
        new Thread(()->{ for (int i = 0; i <40 ; i++) ticket.sale(); },"A").start();
        new Thread(()->{ for (int i = 0; i <40 ; i++) ticket.sale(); },"B").start();
        new Thread(()->{ for (int i = 0; i <40 ; i++) ticket.sale(); },"C").start();
    }
    }

    //1.new ReentrantLock();
    //2. lock.lock();//加锁
    // finally   ---->   lock.unlock();//解锁

    class Ticket2 {
        //属性,方法
        private int number = 30;
        Lock lock = new ReentrantLock();

        //卖票的方式
        public void sale() {

            try {
                lock.lock();//加锁
                if (number > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖出了" + (number--) + "票,剩余:" + number);
                }
            } finally {
                lock.unlock();//解锁

            }
        }
}

synchronized 和lock的区别

1.synchronized 是内置的java关键字,lock是一个类

2.synchronize无法判断获取锁的状态,lock 可以判断是否获取到了锁

3.synchronize会自动释放锁,lock必须要手动释放锁!如果不释放锁,就变成了死锁

4.synchronize 线程1(获得锁,阻塞)、线程2(等待,傻傻的等);lock 就不一定会等待下去,它可以等不了就下了

5.synchronize 可重入锁,不可以中断,非公平 ;lock 可重入锁,可以判断锁,非公平(可以自设置);

6.synchronize 适合少量的代码同步问题,lock 适合大量的同步代码

锁是什么,如何判断锁的是谁

4.生产者和消费者

传统的synchronized
package com.mjh;

public class pc {
    public static void main(String[] args)  {
        Date date = new Date();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    date.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    date.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }
}

class  Date{
    private int number= 0;
    public  synchronized void increment() throws InterruptedException {
        if(number!=0){
            //等待其他线程使用为0为止
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程,我+1结束了
        this.notifyAll();
    }
    public synchronized void decrement() throws InterruptedException {
        if(number==0){
            //等待其他线程生产在使用
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程,我-1结束了
        this.notifyAll();
    }
}

如果有多个线程呢?如果用以上代码,运行出来就会出现如下情况,if就判断了一次,这样多个线程同时去访问同一个资源他也不知道,导致同时都加了1,导致了虚假唤醒

按照官网所说如下图。使用while循环会让一个线程进去,而其他线程等待,因此稍改变代码如下

JUC版的生产者和消费者

任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,一定会有优势和补充

Condition实现精准通知和唤醒线程

使用多个监视器就可以实现顺序执行线程

package com.mjh.pc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class PC3 {
    public static void main(String[] args) {
        Date3 date=new Date3();
        new Thread(()->{
            for (int i = 0; i <10; i++) {
                date.printA();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i <10; i++) {
                date.printB();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i <10; i++) {
                date.printC();
            }
        },"C").start();

    }

}

class  Date3{
    private Lock lock=new ReentrantLock();
    private Condition condition=lock.newCondition();
    private Condition condition2=lock.newCondition();
    private Condition condition3=lock.newCondition();

    private int number=1;//1A  2B 3C

    public void printA(){
        lock.lock();
        try {
            //业务代码
            while(number!=1){
                //等待
                condition.await();
            }
            number=2;
            System.out.println(Thread.currentThread().getName()+"=>AAAAAAAAA");
            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()+"=>BBBBBBBBBBB");
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC(){
        lock.lock();
        try {
            //业务代码
            while(number!=3){
                //等待
                condition3.await();
            }
            number=1;
            System.out.println(Thread.currentThread().getName()+"=>CCCCCCCCCCC");
            condition.signal();//唤醒当前线程
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

5.8锁现象彻底理解锁

我们回到“锁是什么,如何判断锁是谁”-------->永远知道什么是锁,锁谁

对象,class

package com.mjh.lock8;

import java.util.concurrent.TimeUnit;

/**
 *8锁,就是关于锁的8个问题
 * 1.标准情况下,两个线程先打印发短信还是打电话? 1/发短信  2/打电话
 * 2.sendSms延迟4秒,两个线程先打印发短信还是打电话? 1/发短信  2/打电话
 */
public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();

        // phone.sendSms();这不是先调用的问题,是锁的存在
        new Thread(()->{
            phone.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        },"B").start();
    }
}

class Phone{
    //synchronized 锁的对象是方法的调用者
    //两个方法用的是同一个锁,谁先拿到谁执行

    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");

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

    }
}
package com.mjh.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 3.增加了一个普通方法下,两个线程先打印你好还是发短信? 1/你好  2/发短信
 * 4.两个线程两个对象,一个线程调用一个对象先打印打电话还是发短信? 1/打电话 2/发短信
 */
public class Test2 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        Phone2 phone2 = new Phone2();


        // phone.sendSms();这不是先调用的问题,是锁的存在
        new Thread(()->{
            phone.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}

class Phone2{
    //synchronized 锁的对象是方法的调用者
    //两个方法用的是同一个锁,谁先拿到谁执行

    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");

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

    //这里没有锁
    public void hello(){
        System.out.println("你好");
    }
}
package com.mjh.lock8;

import java.util.concurrent.TimeUnit;

/**
 *  5.两个静态方法,一个对象,先打印打电话还是发短信? 1/打电话 2/发短信
 * 6.两个静态方法,两个线程两个对象,一个线程调用一个对象先打印打电话还是发短信? 1/打电话 2/发短信
 *
 */
public class Test3 {
    public static void main(String[] args) {
        Phone3 phone = new Phone3();
        Phone3 phone3 = new Phone3();


        // phone.sendSms();这不是先调用的问题,是锁的存在
        new Thread(()->{
            phone.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone3.call();
        },"B").start();
    }
}
//Phone3唯一的一个Class对象
class Phone3{
    //synchronized 锁的对象是方法的调用者
    //static 静态方法
    //类一加载就有了,锁的是class
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");

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

}
package com.mjh.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 7.1个静态同步方法,1个普通同步方法,一个对象,先打印发短信?还是打电话 1/打电话 2/发短信
 * 8.1个静态同步方法,1个普通同步方法,两个对象,先打印发短信?还是打电话 1/打电话 2/发短信
 */
public class Test4 {
    public static void main(String[] args) {
        Phone4 phone = new Phone4();
        Phone4 phone2 = new Phone4();


        // phone.sendSms();这不是先调用的问题,是锁的存在
        new Thread(()->{
            phone.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
//Phone3唯一的一个Class对象
class Phone4{

    //静态同步方法,锁的是class
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");

    }

    //普通同步方法,锁的是调用者
    public  synchronized void call(){
        System.out.println("打电话");
    }

}

6.集合类不安全

list不安全

package com.mjh.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

//java.util.ConcurrentModificationException  并发修改异常
public class ListTest {
    public static void main(String[] args) {
        /*
        并发下ArrayList不安全
        解决方法
        1.  List<String> list = new Vector<>();
        2.  List<String> list = Collections.synchronizedList(new ArrayList<>());
        3.   List<String> list = new CopyOnWriteArrayList<>();//JUC的解决方案

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

        Vector 和  CopyOnWriteArrayList<>
        Vector 的底层源码的add()用的是synchronized 效率相对较低
        CopyOnWriteArrayList<>底层源码的add()用的是Lock锁 效率高
         */
        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();
        }

    }
}

set 不安全

package com.mjh.unsafe;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

//同理可证  java.util.ConcurrentModificationException
public class SetTest {
    public static void main(String[] args) {
        /*多线程下的 HashSet 不安全
        1.工具类
            Set<String> set= Collections.synchronizedSet(new HashSet<>());
        2.  Set<String> set= new CopyOnWriteArraySet<>();
         */
        Set<String> set= new CopyOnWriteArraySet<>();
        for (int i = 0; i <10 ; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,4));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

map不安全

package com.mjh.unsafe;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class MapTest {
// 同理可证:java.util.ConcurrentModificationException
    public static void main(String[] args) {
       // Map<String,String> map = new HashMap<>();
        Map<String,String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
            map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }

    }
}

7.常用的辅助类(必会)

CountDownLatch

package com.mjh.add;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    //减法计数器
    public static void main(String[] args) throws InterruptedException {
        //总数是6,使用场景:必须要执行任务的时候,再使用
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <=6 ; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" go out");
                countDownLatch.countDown();//数量-1
            },String.valueOf(i)).start();
        }

        countDownLatch.await();//等待计数器归零,然后再向下执行

        System.out.println("close  door");
    }
}

原理:

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

countDownLatch.await();//等待计数器归零,然后再向下执行

每次有线程调用countDown()数量-1,假设计数器变为0, countDownLatch.await();就会被唤醒,继续执行!

CyclicBarrier

package com.mjh.add;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    //加法计数器
    public static void main(String[] args) {
        /*
        集齐7颗龙珠召唤神龙
         */
        //召唤龙珠的线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("神龙召唤成功!");
        });

        for (int i = 1; i <=7 ; i++) {
            final int temp=i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"收集"+temp+"个龙珠");

                try {
                    cyclicBarrier.await();//等待召集7颗龙珠
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

Semaphore

package com.mjh.add;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {
    public static void main(String[] args) {
        //线程数量:停车位!这个Semaphore一般用于限流
        Semaphore semaphore = new Semaphore(3);//信号量就只有三个,超过三个线程进来其余就只有等待

        for (int i = 1; i <=6 ; i++) {
            new Thread(()->{
                //acquire()  得到
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+" 找到停车位");
                    TimeUnit.SECONDS.sleep(3);//找到之后让它停一会儿
                    System.out.println(Thread.currentThread().getName()+" 离开停车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //release()  释放
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }

}

原理:

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

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

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

posted @ 2020-09-21 20:31  林森001  阅读(92)  评论(0编辑  收藏  举报