JAVA自学笔记(高级多线程)

同步和异步概念

  • 同步

    • 形容一次方法调用,同步一旦执行,调用者必须等待该方法返回,才能继续

  • 异步

    • 形容一次方法调用,异步一点开始,像是一次信息传递,调用者告知之后立刻返回,二者竞争时间片,并发执行

线程池概念

  • 现有问题

    • 线程是宝贵的内存资源,单个线程约占1MB空间,过多分配容易造成内存溢出

    • 频繁的创建和销毁线程会增加虚拟机回收频率、资源开销,造成性能下降

  • 线程池

    • 线程容器,可以设定线程分配的数量上限

    • 将预先创建的线程对象存入线程池中,并重用线程池中的对象

    • 避免频繁的创建和销毁

  • 线程池原理

    • 将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程

获取线程池

  • 常用的线程池接口和类(所在包:java.util.concurrent)

    • Executor:线程池的顶级接口

    • ExecutorService:线程池接口,可以通过submit(Runnable task)提交任务代码

    • Executors工厂类:通过此类可以获得一个线程池

    • 通过newFixedThreadPool(int nThread)获取固定数量的线程池。参数:指定线程池中线程的数量

    • 通过newCachedThreadPool()获取动态数量的线程池,如果不够则创建新的线程,无上限

    • Executors.newSingleThreadExecutor();//创建单个线程池

    • Executors.newScheduledThreadPool();//调度线程池,实现延迟、周期执行任务。

    • public static void main(String[] args) {
          var  es = Executors.newScheduledThreadPool(1);  //延迟执行,十秒后执行
          es.schedule(new Runnable() {
              @Override
              public void run() {
                  System.out.println(Thread.currentThread().getName()+"zhixingle........");  //
              }
          },10000, TimeUnit.MILLISECONDS);
          es.shutdown();
      }
      public static void main(String[] args) {
          //创建调度线程池
          ScheduledExecutorService es = Executors.newScheduledThreadPool(1);
          //分配任务
          //2.1固定频率执行  参数:  执行的任务   初始延迟时间   周期时间   时间单位
          es.scheduleAtFixedRate(new Runnable() {
              @Override
              public void run() {
                  Date date = new Date();
                  SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
                  System.out.println(sdf.format(date));
                  count++;
                  num++;
                  if(num ==11){
                      System.out.println("开始休息5秒");  //但是休息的5秒,后面直接执行五次
                      try {
                          Thread.sleep(5000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
                  if(count==20){
                      es.shutdown();  //关闭线程池
                  }
              }
          },0,1000,TimeUnit.MILLISECONDS);
      ​
      ​
          //2.2固定延迟执行
      }

public static void main(String[] args) {
    //1创建固定线程个数的线程池
    ExecutorService es = Executors.newFixedThreadPool(4);
    //创建一个缓存线程池,线程个数有任务个数来决定
    ExecutorService es1 = Executors.newCachedThreadPool();
    //创建单个线程的线程池
    ExecutorService es2 = Executors.newSingleThreadExecutor();
    //创建调度线程池  调度:周期、定期执行
    ExecutorService es3 = Executors.newScheduledThreadPool(4);
    //创建任务
    Runnable ticket =new Runnable() {
        private  int ticket = 100;
        @Override
        public void run() {
            while (true){
                if(ticket<=0){
                    break;
                }
                System.out.println(Thread.currentThread().getName()+ "卖了第"+ticket+"张票");
                ticket--;
            }
        }
    };
    //提交任务
    for(int i=0;i<4;i++){
    es.submit(ticket);
    }
    //关闭线程
    es.shutdown();  //等待任务执行完毕在关闭线程池,执行以前提交的任务,但是不接收新的任务
    es.shutdownNow();//直接关闭线程池,不等待任务结束,暂停处理正在等待的任务,并且返回等待执行的任务列表
}

线程池的七个参数

  • 核心线程数

  • 最大线程数

  • 非核心线程存活时间

  • 时间单位

  • 工作队列

  • 线程工厂

  • 拒绝策略(当进入的线程超过工作队列线程数+最大线程数)

    • 抛弃,并抛出异常AbsortPolicy

    • 直接抛弃,不抛出异常DiacardPolicy

    • 抛弃旧的,Discard OldestPolicy

    • 由线程的创建者执行CallerRunPolicy(比如主线程)

  • 当线程进入线程池时,首先由核心线程执行,当核心线程全部被占用时,将后来进入的线程存入到线程工作队列,当线程工作队列也满的时候,创建非核心线程。如果拒绝原则使用的是抛弃旧的,会将工作队列中的抛弃

  • public static void main(String[] args) {
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2,
                3,
                60L,
                 TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

Callable接口(创建线程)

  • public interface Callable<V>{

    public V call() throws Exception;

    }

  • JDK5加入,与Runnable接口类似,实现之后代表一个线程任务

  • Callable具有泛型返回值、可以声明异常

  • Callable和Runnable接口的区别

    • Callable接口中的call方法有返回值,Runnable接口中run方法没有返回值

    • Callable接口中call方法有声明异常,Runnable接口中run方法没有异常

    • public static void main(String[] args) throws Exception{
          //功能需求:使用Callable实现1-100的和
          //1.创建Callable对象
          Callable<Integer> callable = new Callable<Integer>() {
              @Override
              public Integer call() throws Exception {
                  int sum = 0;
                  for(int i =0;i<=100;i++){
                      sum =sum+i;
                  }
                  return sum;
              }
          };
          //2将Callable对象转换为可执行任务
          FutureTask<Integer>  task = new FutureTask<>(callable);
          //3创建线程
          Thread thread = new Thread(task);
          //4启动线程
          thread.start();
          //5.获取结果
          Integer sum = task.get();
          System.out.println("结果是:"+sum);
      }
    • Future和Callable配合使用

    • public static void main(String[] args) throws ExecutionException, InterruptedException {
          //创建线程池
          ExecutorService es = Executors.newFixedThreadPool(1);
          //提交任务给线程池  Future:表示将要执行完任务的结果
          Future<Integer> future = es.submit(new Callable<Integer>() {
              public Integer call() throws Exception {
                  int sum = 0;
                  for(int i =0;i<=100;i++){
                      sum =sum+i;
                  }
                  return sum;
              }
          });
          //获取任务结果
          System.out.println(future.get());
          //关闭线程池
          es.shutdown();
      }

Future接口

  • 概念:异步接收ExecutorService.submit()返回的状态结果,当中包含了call()的返回值

  • Future:表示将要执行完任务的结果

  • 方法: V get()以阻塞形式等待Future中的异步处理结果(call()的返回值

  • 案例

    • 使用Future 使用两个线程,一个计算1-50,一个计算51-100,然后进行汇总统计

    • public static void main(String[] args) throws ExecutionException, InterruptedException {
              //创建线程池
              ExecutorService es = Executors.newFixedThreadPool(2);
              Future<Integer> future1 = es.submit(new Callable<Integer>() {
                  @Override
                  public Integer call() throws Exception {
                      int sum = 0;
                      for(int i =0;i<=50;i++){
                          sum =sum+i;
                      }
                      System.out.println("计算1-50的和"+sum);
                      return sum;
                  }
              });
      ​
      ​
              Future<Integer> future2 = es.submit(new Callable<Integer>() {
                  @Override
                  public Integer call() throws Exception {
                      int sum = 0;
                      for(int i =51;i<=100;i++){
                          sum =sum+i;
                      }
                      System.out.println("计算51-100的和"+sum);
                      return sum;
                  }
              });
      ​
              //获取结果
              Integer sum = future1.get()+future2.get();
              System.out.println("1-100的和:"+sum);
              //关闭线程池
              es.shutdown();
          }
      }

Lock接口

  • J打开加入,与synchronized比较,显示定义,结构更加灵活

  • 提供更多实用性方法,功能更加强大/性能更加优越

  • 常用方法

    • void lock () // 获取锁,如果锁被占用,则等待

    • Boolean trylock() //尝试获取锁(成功返回true,失败返回false,不阻塞)

    • void unlock() //释放锁

重入锁(Reentrant Lock)

  • ReentrantLock:Lock接口的实现类,与synchronized一样具有互斥锁功能

public class Ticket implements Runnable{
    private int ticket = 100;
    private Lock lock = new ReentrantLock();
​
    @Override
    public void run() {
        while (true){
            lock.lock();
            try{
                if(ticket<=0){
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
                ticket--;
            }finally {
                lock.unlock();
            }
        }
    }
}
public static void main(String[] args) {
    Ticket ticket = new Ticket();
    ExecutorService es = Executors.newFixedThreadPool(4);
    for(int o = 0;o<4;o++){
        es.submit(ticket);
    }
    es.shutdown();
}

读写锁(ReentrantReadWriteLock)

  • 一种支持一写多读的同步锁,读写分离,分别分配读锁和写锁

  • 支持多次分配读锁,使每个读操作可以并发执行

  • 互斥原则

    • 写-写:互斥,阻塞

    • 读-写:互斥,读阻塞写,写堵塞读

    • 读-读:不互斥,不阻塞

    • 在读操作远远高于写操作的环境中,可以在保证线程安全的情况下,提高运行效率

public class ReadWriteDemo {
    //创建读写锁
    private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
    //获取写锁
    ReentrantReadWriteLock.WriteLock writeLock = rw.writeLock();
    //获取读锁
    ReentrantReadWriteLock.ReadLock readLock = rw.readLock();
    //设置访问值
    private String string;

    //获取属性
    public  String  getString(){
        //对属性上读锁
        readLock.lock();
        try {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("读取数据"+this.string);
            return this.string;
        }finally {
           readLock.unlock();
        }
    }

    //修饰属性
    public void setString(String s){
        //对属性上写锁
        writeLock.lock();
        try{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("写入:"+s);
            this.string = s;
        }finally {
            writeLock.unlock();
        }
    }
}
public class TestWriteReadLock {
    public static void main(String[] args) {
        //创建操作对象
        ReadWriteDemo readWriteDemo = new ReadWriteDemo();
        //创建线程池
        ExecutorService es =  Executors.newFixedThreadPool(20);

        //创建写线程
        Runnable write = new Runnable() {
            @Override
            public void run() {
                readWriteDemo.setString("小翠"+(int)(Math.random()*100));
            }
        };
        //创建读线程
        Runnable read = new Runnable() {
            @Override
            public void run() {
                readWriteDemo.getString();
            }
        };

        //添加写线程
        for(int i = 0 ;i<2;i++){
            es.submit(write);
        }
        //添加读线程
        for(int i = 0 ;i<18;i++){
            es.submit(read);
        }

        es.shutdown();
        while (!es.isTerminated()){// 等待线程池里的线程运行结束

        }
    }
}

Condition

  • Condition接口提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式

  • Condition可以通俗的理解位条件队列。当一个线程在调用了wait方法以后,直到线程等待的某个条件为真的时候才会被唤醒

  • 方法名描述
    await()当前线程进入等待状态
    signal()唤醒一个等待线程
    signalAll()唤醒所有等待线程

使用Lock和Condition实现20次输出“ABC”

public class PrintDemo {
    private  int num = 1;//1表示输出A   2表示输出B  3表示输出C
    private Lock lock = new ReentrantLock();  //创建锁
    private Condition conditionA = lock.newCondition(); //创建ConditionA,存放A线程等待
    private Condition conditionB = lock.newCondition(); //创建ConditionB,存放A线程等待
    private Condition conditionC = lock.newCondition(); //创建ConditionA,存放A线程等待

    public void printA(){
        lock.lock();
        try {
            if (num != 1) {
                try {
                    conditionA.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("A");
            num = 2;// x下一个输出B
            conditionB.signal();
        }finally {
            lock.unlock();
        }
    }

    public void printB(){
        lock.lock();
        try {
            if (num != 2) {
                try {
                    conditionB.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("B");
            num = 3;
            conditionC.signal();
        }finally {
            lock.unlock();
        }
    }

    public void printC(){
        lock.lock();
        try {
            if (num != 3) {
                try {
                    conditionC.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("C");
            num = 1;// x下一个输出B
            conditionA.signal();
        }finally {
            lock.unlock();
        }
    }

}
public class TestPrint {
    public static void main(String[] args) {
        PrintDemo pd = new PrintDemo();
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3,3,60L, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(2),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());


        poolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    pd.printA();
                }

            }
        });
        poolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    pd.printB();
                }

            }
        });
        poolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    pd.printC();
                }
            }
        });

        poolExecutor.shutdown();
    }
}

synchronized和Lock的区别

类别synchronizedLock
存在层次Java的关键字,在jvm层面上是一个类或接口
锁释放1、获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁在finally中必须释放锁,不然容易造成线程死锁
锁的获取假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待Lock可以尝试获得锁,线程可以不用一直等待
锁状态无法判断可以判断
锁类型可重入、不可中断、非公平可重入,可中断、可非公平或公平
性能少量同步大量同步

CAS算法

  • CAS:Compare And Swap(比较交换算法)

    • 其实现方式时是基于硬件平台的汇编指令,是靠硬件来实现的,效率高

    • 并且比较和交换时同步的

    • CAS是一种乐观锁

  • CSA比较交换算法,修改的方法包含三个核心参数(V,E,N)

  • V:需要更新的变量、E:预期值 N:新值

  • 只有当V==E时,V=N;否则表示已经被更新过,则取消当前操作

public class TestCAS {
    static  int e = 0;
    public static void main(String[] args) {
        CAS cas = new CAS();
        ExecutorService es = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 100; i++) {
            es.submit(new Runnable() {
                @Override
                public void run() {
                    e++;
                    int n = cas.getNum();
                    boolean flag = cas.compareAndSwap(e,n);
                    System.out.println(Thread.currentThread().getName()+":"+flag+":"+cas.getNum());
                }
            });
        }
        es.shutdown();
    }
}
/**
 * 测试CAS算法
 * V:需要更新的变量、E:预期值  N:新值
 * 只有当V==E时,V=N;否则表示已经被更新过,则取消当前操作
 */
public class CAS {
    private int num ;


    public synchronized  int getNum() {  //获取需要更新的值
        return num;
    }

    public synchronized  boolean compareAndSwap(int e,int n){  //n:表示旧值  e:表示新值
        if(this.num == n){
            this.num = e;
            return true;
        }
        return false;
    }
}

Queue接口(队列)

  • Collection的子接口,表示队列FiFO(First In FirstOut)

  • 常用方法:

    • 抛出异常:

      • Boolean add(E e)//顺序添加一个元素(到达上限后,在添加会抛出异常)

      • E remove()//获得第一个元素并移除(如果没有元素,则抛出异常)

      • E element() //获得第一个元素但不移除(如果队列没有元素,则抛出异常)

    • 返回特殊值(推荐使用)

      • Boolean offer(E e)//顺序添加一个元素(到达上线后,再添加则会返回false)

      • E poll()//获得第一个元素并移除(如果队列没有元素时,返回null)

      • E peek()//获得第一个元素但不移除(如果队列没有元素时,返回null)

Concurrent LinkedQueue

  • 线程安全、可高效读写的队列,高并发下性能最好的队列

  • 无锁、CAS比较交换算法,修改的方法包好三个核心参数(V、E、N)

  • V:需要更新的变量 E:预期值 N:新值

  • 只有当V==E时,v=N;否则表示已被更新过,则取消当前操作

  • public static void main(String[] args) {
        ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
        //入队
        queue.offer("小翠");
        queue.offer("小莲花");
        queue.offer("小蜘蛛");
        //出队
        int size =queue.size();
        for (int i = 0; i <size ; i++) {
            System.out.println(queue.poll());
        }
    }

Blocking Queue接口(阻塞队列)

  • Queue的子结构,阻塞的队列,增加了两个线程状态为无限等待的方法

  • 方法:

    • void put(E e )//将指定元素插入队列中,如果没有可用空间,则等待

    • E take() //获取并移除此队列头部元素,如果没有可用元素,则等待

  • 可用于解决生产者、消费者问题。

/**
 * 使用阻塞队列实现生产者消费者
 * Blocking Queue
 *      ArrayBlockingQueue :数组实现阻塞队列
 *      LinkedBlockingQueue :链表实现阻塞队列
 */
public class Demo2 {
    public static void main(String[] args) {
        //创建队列
        BlockingQueue<String> blockingQueue= new LinkedBlockingDeque<>(6);
        //生产功能
        Runnable produce = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 30; i++) {
                    try {
                        blockingQueue.put("面包:"+i);
                        System.out.println(Thread.currentThread().getName()+"生产了"+i+"号面包");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }
        };
        //消费功能
        Runnable consume = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <30 ; i++) {
                    try {
                        String b = blockingQueue.take();
                        System.out.println(Thread.currentThread().getName()+"消费了"+b);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        ExecutorService es = Executors.newFixedThreadPool(2);
        es.submit(produce);
        es.submit(consume);

        es.shutdown();
        while(!es.isTerminated()){}
    }
}

多线程的三个特性

  • synchronized可以保证原子性和可见性.但不能保证有序性

  • volatile可保证可见性和禁止重排,但不能保证原子性。Lock接口简介借助了volatile关键字间接的实现了可见性和有序性

原子性

  • 一个或多个操作不能被分割,要么全部执行,要么都不执行

可见性

  • 多个线程访问同一个变量,一个线程修改了这个变量,别的线程能立即看到修改的值。

  • volatile关键字保证内存可见性

有序性

  • 程序执行的顺序按照代码的先后顺序执行。但是处理器为了提高程序运行效率,可能会对输入的代码进行优化,他不保证程序每个语句的执行顺序和编写顺序一致,但是最终结果是一致的。使用volatile可以保证代码不被优化

总结

  • ExecutorService线程池接口,Executors工厂

  • Callable线程任务、Future异步返回值

  • Lock、ReentrantLock重入锁。ReentrantRead WriteLock读写锁

  • CopyOnWriteArrayList线程安全的Array L ist

  • CopyOnWriteSet线程安全的Set

  • ConcurrentHashMap线程安全的HashMap

  • ConcurrentLinkedQueue线程安全的Queue

  • ArrayBlockingQueue线程安全的阻塞Queue(生产者、消费者)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值