挂面09
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