挂面09

作者: 盼旺 | 来源:发表于2019-10-15 22:30 被阅读0次

    1.使用list队列/wait()/notify()实现生产者消费者

    其中一种是通过阻塞队列(BlockingQueue)来进行,直接可以创建多个线程去进行操作即可,不需要对方法额外进行同步,
    还有一种就是本文的通过wait()与notify()来进行操作。

    wait():进入临界区的线程在运行到一部分后,发现进行后面的任务所需的资源还没有准备充分,所以调用wait()方法,让线程阻塞,等待资源,同时释放临界区的锁,此时线程的状态也从运行状态变为等待状态;

    notify():准备资源的线程在准备好资源后,调用notify()方法通知需要使用资源的线程,同时释放临界区的锁,将临界区的锁交给使用资源的线程。

    wait()、notify()这两个方法,都必须要在临界区中调用,即是在synchronized同步块中调用,不然会抛出IllegalMonitorStateException的异常。
    临界区指的是一个访问共用资源的程序片段,而这些共用资源又无法同时被多个线程访问的特性。
    实现

    通过一个List对对象进行储存,消费者从List中取对象进行消费。在List为空时,消费者线程执行wait操作,让生产者获取到对象,在生产好对象后,再通过notify唤醒消费者线程进行消费

    import java.util.ArrayList;
    import java.util.List;
    
    public class ThreadTest {
        private static List list = new ArrayList();
        private static int len = 2;
        int id;
        public static void main(String[] args) {
            ThreadTest threadTest = new ThreadTest();
            Thread prodecer1 = new Thread(threadTest.new Producer(), "生产者1");
            Thread consumer1= new Thread(threadTest.new Custemer(), "消费者1");
            Thread consumer2 = new Thread(threadTest.new Custemer(), "消费者2");
            prodecer1.start();
            consumer1.start();
            consumer2.start();
            return;
        }
        //生产者
        private class Producer implements Runnable{
            @Override
            public void run() {
                while (true){
                    synchronized (list){
                        if(list.size()>=len){
                            try {
                                list.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        list.add(id++);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+" 生产了");
                        System.out.println(list.size());
                        list.notifyAll();
                    }
                }
            }
        }
        //消费者
        private class Custemer implements Runnable{
    
            @Override
            public void run() {
                while (true){
                    synchronized (list){
                        while (list.size()==0){//注意这里不能为if因为下面说明
                            try {
                                list.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        list.remove(0);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+" 消费了");
                        System.out.println(list.size());
                        list.notify();
                    }
                }
            }
        }
    }
    

    为if的话判断一次
    一号消费者尝试进行消费,发现数组为空,所以通过wait释放了资源,然后生产者进行生产。在生产者生产完成后,通过了notifyAll通知所有线程资源准备好了,这时二号消费者首先抢到了资源,顺利的进行消费,数组这时就为空,之后唤醒一号消费者,这时一号消费者并不知道数组中没有产品,所以进行操作,就会抛出数组越界的错误

    2.HTTP 和 HTTPS

    端口
    HTTP 的 URL 由 http:// 起始,且默认端口为 80;
    而 HTTPS 的 URL 由 https:// 起始,默认使用端口 443;
    安全性和资源消
    HTTP 协议直接运行在 TCP 之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。
    HTTPS 是运行在 SSL/TLS 之上的 HTTP 协议,SSL/TLS 又运行在 TCP 之上,所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥由服务器方的证书进行了非对称加密。
    所以说,HTTP 安全性没有 HTTPS 高,但是 HTTPS 比 HTTP 耗费更多服务器资源。

    对称加密:密钥只有一个,加密解密为同一个密码,且加解密速度快,典型的对称加密算法有 DES、AES 等;
    非对称加密:密钥成对出现(且根据公钥无法推知私钥,根据私钥也无法推知公钥),加密解密使用不同密钥(公钥加密需要私钥解密,私钥加密需要公钥解密),对比对称加密,速度较慢,典型的非对称加密算法有 RSA、DSA 等。

    3.HTTP 长连接 && 短连接

    在 HTTP/1.0 中默认使用短连接,也就是说,客户端和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接;

    而从 HTTP/1.1 起,默认使用长连接,用以保持连接特性,使用长连接的 HTTP 协议,会在响应头加入这行代码:Connection: keep-alive

    在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输 HTTP 数据的 TCP 连接不会关闭,客户端再次访问这个服务器时,会继续使用这条已建立的连接,Keep-Alive 不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如 Apache、Nginx)中设定这个时间,实现长连接需要客户端和服务端都配置支持;

    HTTP 协议的长连接和短连接,实质上是 TCP 协议的长连接和短连接

    4.Java中默认的访问权限作用域

    作用域 当前类 同一包(package) 子类 其他包
    public Y Y Y Y
    protected Y Y Y N
    default Y Y N N
    private Y N N N

    普通类的默认访问权限是 **default **
    默认不写的时候 子类不能访问 但为啥我子类还能调用父类的变量 是因为当前子类在本包中给大家测试个例子就清楚了

    关于抽象类
    JDK 1.8以前,抽象类的方法默认访问权限为protected
    JDK 1.8时,抽象类的方法默认访问权限变为default
    关于接口
    JDK 1.8以前,接口中的方法必须是public的
    JDK 1.8时,接口中的方法可以是public的,也可以是default的
    JDK 1.9时,接口中的方法可以是private的

    5.Java中的Lock接口,比起synchronized,优势在哪里?

    如果需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,如何实现?
    Lock接口较大的优势是为读和写分别提供了锁。
    读写锁ReadWriteLock拥有更加强大的功能,它可细分为读锁和解锁。
    读锁可以允许多个进行读操作的线程同时进入,但不允许写进程进入;写锁只允许一个写进程进入,在这期间任何进程都不能再进入。(完全符合题目中允许多个用户读和一个用户写的条件)
    要注意的是每个读写锁都有挂锁和解锁,较好将每一对挂锁和解锁操作都用try、finally来套入中间的代码,这样就会防止因异常的发生而造成死锁得情况。
    下面是一个示例程序:

    import java.util.concurrent.locks.*;
    public class ReadWriteLockTest {
    public static void main(String[] args) {
     final TheData myData=new TheData();  //这是各线程的共享数据
      for(int i=0;i<3;i++){ //开启3个读线程
      new Thread(new Runnable(){
      @Override
      public void run() {
        while(true){
           myData.get();
        }
    }}).start();
    }
    
      for(int i=0;i<3;i++){ //开启3个写线程
      new Thread(new Runnable(){
      @Override
      public void run() {
    
      while(true){
      myData.put(new Random().nextInt(10000));
      }
      }
      }).start();
     }
      }
      }
      class TheData{
      private Object data=null;
      private ReadWriteLock rwl=new ReentrantReadWriteLock();
      public void get(){
      rwl.readLock().lock();  //读锁开启,读线程均可进入
      try { //用try finally来防止因异常而造成的死锁
      System.out.println(Thread.currentThread().getName()+"is ready to read");
      Thread.sleep(new Random().nextInt(100));
      System.out.println(Thread.currentThread().getName()+"have read date"+data);
      } catch (InterruptedException e) {
      e.printStackTrace();
      } finally{
      rwl.readLock().unlock(); //读锁解锁
      }
      }
      public void put(Object data){
      rwl.writeLock().lock();  //写锁开启,这时只有一个写线程进入
      try {
      System.out.println(Thread.currentThread().getName()+"is ready to write");
      Thread.sleep(new Random().nextInt(100));
      this.data=data;
      System.out.println(Thread.currentThread().getName()+"have write date"+data);
      } catch (InterruptedException e) {
      e.printStackTrace();
      } finally{
      rwl.writeLock().unlock(); //写锁解锁
      }
      }
      }
    

    6.现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?

    主要问join方法

    Thread t1 = new Thread(new T1());
    Thread t2 = new Thread(new T2());
    Thread t3 = new Thread(new T3());
    
    t1.start();
    t1.join();
    
    t2.start();
    t2.join();
    
    t3.start();
    t3.join();
    

    7.Thread类中的join()方法原理

    join()方法的作用,t.join()方法阻塞调用此方法的线程,直到线程t完成,此线程再继续;通常用于在main()主线程内,等待其它线程完成再结束main()主线程

    状态转换图

    源码了解一下join()

    public final void join() throws InterruptedException {
        join(0);
    }
    public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();  //获取当前时间
        long now = 0;
    
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (millis == 0) {    //这个分支是无限期等待直到b线程结束
            while (isAlive()) {
                wait(0);//wait操作,那必然有synchronized与之对应
            }
        } else {    //这个分支是等待固定时间,如果b没结束,那么就不等待了。
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    

    成员方法加了synchronized说明是synchronized(this),this是谁啊?this就是threadA子线程对象本身。也就是说,主线程持有了threadA这个对象的锁。
    当子线程threadA执行完毕的时候,jvm会自动唤醒阻塞在threadA对象上的线程,在我们的例子中也就是主线程。至此,threadA线程对象被notifyall了,那么主线程也就能继续跑下去了。
    总结
    首先join() 是一个synchronized方法, 里面调用了wait(),这个过程的目的是让持有这个同步锁的线程进入等待,那么谁持有了这个同步锁呢?答案是主线程,因为主线程调用了threadA.join()方法,相当于在threadA.join()代码这块写了一个同步代码块,谁去执行了这段代码呢,是主线程,所以主线程被wait()了。然后在子线程threadA执行完毕之后,JVM会调用lock.notify_all(thread);唤醒持有threadA这个对象锁的线程,也就是主线程,会继续执行。

    参考资料
    https://www.techbelife.com/post/Implement-producer-consumer-model-with-wait-and-notifyAll.html
    https://blog.csdn.net/u010983881/article/details/80257703

    相关文章

      网友评论

          本文标题:挂面09

          本文链接:https://www.haomeiwen.com/subject/symfpctx.html