挂面09

2019-10-15  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读