当前位置:网站首页 > 编程语言 > 正文

阻塞队列有哪些(阻塞队列有哪些实现?各有什么优缺点)



努力不辜,时光不负。继续冲冲冲!

  • JVM:Java虚拟机,Java程序需要运行在虚拟机
  • JRE:Java虚拟机+Java程序所需的核心类库
  • JDK:Java虚拟机+Java程序所需的核心类库(JRE)+Java开发工具包
  • 方法区: 在java的虚拟机中有一块专门用来存放已经加载的类信息、常量、静态变量以及方法代码的内存区域,叫做方法区。
  • 常量池: 常量池是方法区的一部分,主要用来存放常量和类中的符号引用等信息。
  • 堆区: 用于存放类的对象实例。
  • 栈区: 也叫java虚拟机栈,是由一个一个的栈帧组成的后进先出的栈式结构,栈桢中存放方法运行时产生的局部变量、方法出口等信息。当调用一个方法时,虚拟机栈中就会创建一个栈帧存放这些数据,当方法调用完成时,栈帧消失,如果方法中调用了其他方法,则继续在栈顶创建新的栈桢。

面向过程:分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现

面向对象:把构成问题事务分解成各个对象,然后描述对象的行为

举个例子:下五子棋:










  • 面向过程的设计思路:1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。
  • 面向对象的设计思路:1、黑白双方,这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定

面向过程

  • 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
  • 缺点:没有面向对象易维护、易复用、易扩展
  • 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
  • 缺点:没有面向对象易维护、易复用、易扩展


举个例子:


面向过程的程序是一份蛋炒饭,面向对象的程序是一份盖浇饭。
蛋炒饭的好处就是入味均匀,吃起来香。如果恰巧你不爱吃鸡蛋,只爱吃青菜的话,那么唯一的办法就是重新做一份青菜炒饭了。盖浇饭更换一份盖菜就可以了。盖浇饭的缺点是入味不均,可能没有蛋炒饭那么香。
到底是蛋炒饭好还是盖浇饭好呢?其实这类问题都很难回答,非要比个上下高低的话,就必须设定一个场景,否则只能说是各有所长。如果大家都不是美食家,没那么多讲究,那么从饭馆角度来讲的话,做盖浇饭显然比蛋炒饭更有优势,他可以组合出来任意多的组合,而且不会浪费。
盖浇饭的好处就是"菜"“饭"分离,从而提高了制作盖浇饭的灵活性。饭不满意就换饭,菜不满意换菜。用软件工程的专业术语就是"可维护性"比较好,“饭” 和"菜"的耦合度比较低。蛋炒饭将"蛋”“饭"搅和在一起,想换"蛋”"饭"中任何一种都很困难,耦合度很高,以至于"可维护性"比较差。软件工程追求的目标之一就是可维护性,可维护性主要表现在3个方面:可理解性、可测试性和可修改性。面向对象的好处之一就是显著的改善了软件系统的可维护性。










  • 都是面向对象的语言,都支持封装、继承和多态
  • Java不提供指针来直接访问内存,程序内存更加安全
  • Java的类是单继承的,接口可以多继承,C++支持多重继承
  • Java有自动内存管理机制,不需要程序员手动释放无用内存
  • Java可跨平台运行,C++要关注平台差异性

== 对基本类型和引用类型作用效果是不同的,如下所示:

  • 基本类型:比较的是值是否相同;
  • 引用类型:比较的是引用是否相同;

static关键字主要有两种作用:

  • 第一,为某特定数据类型或对象分配单一的存储空间,而与创建对象的个数无关。
  • 第二,实现某个方法或属性与类而不是对象关联在一起
  • 静态方法中不能直接访问非静态成员方法和非静态成员变量,非静态成员方法可直接可以访问所有成员方法/成员变量
  • 同类可直接调用静态方法/类变量,不同类则是类.静态方法/类变量
  • 同类非静态方法调用非静态方法/实例变量时,可直接调用方法名()/实例变量名;其它情况下调用非静态方法均要实例化对象通过对象调用非静态方法,类名 对象名 = new 类名(); 对象名.静态方法名()/实例变量名;
  • 静态方法中,不能使用this关键字, this是相对于某个对象而言的,static修饰的方法是相对于类的,因此不能在静态方法中用this


static修饰类

  • 实例内部类:直接定义在类当中的一个类,在类前面没有任何一个修饰符。
  • 静态内部类:在内部类前面加上一个static。
  • 局部内部类:定义在方法当中的内部类,局部类当中不能使用static变量,不能使用 public、protected、private 修饰。
  • 匿名内部类:属于局部的一种特殊情况。


final修饰方法/变量/类

  • final有不可改变,最终的意思,可以用来修饰非抽象类、成员方法和变量
  • 修饰类:该类不能再派生出新的子类,不能作为父类被继承。因此,一个类不能同时被声明为abstract 和 final,抽象类要被引用,所以不能用final修饰
  • 修饰方法:该方法不能被子类重写。
  • 修饰变量:该变量必须在声明时给定初值,而在以后只能读取,不可修改。 如果变量是对象,则指的是引用不可修改,但是对象的属性还是可以修改的。
  • JDK1.0 - 1.4    数据类型接受 byte short int char
  • JDK1.5     数据类型接受 byte short int char enum(枚举)
  • JDK1.7    数据类型接受 byte short int char enum(枚举), String,对应的包装类型

Lambda的前提条件

  • Lambda只能用于替换有且仅有一个抽象方法的接口和匿名内部类对象,这种接口称为函数式接口
  • Lambda具有上下文推断的功能,所以我们才会出现Lambda的省略格式
  • 列表迭代:输出列表的每个元素
  • 事件监听
  • Predicate 接口
  • Map 映射
  • Reduce 聚合:对多个对象进行过滤并执行相同的处理逻辑
  • 代替 Runnable:创建线程
  • 数据有效性验证:用户注册模块是应用正则表达式最集中的地方,主要是用于验证用户帐号、密码、EMAIL、电话号码、号码、身份证号码、家庭地址等信息。如果填写的内容与正则表达式不匹配,可以断定填写的内容是不合乎要求或虚假的信息;
  • 模糊查询,批量替换:可以在文档中使用一个正则表达式来查找匹配的特定文字,然后可以全部将其删除,或者替换为别的文字。
  • 核心类:

       :正则表达式的编译后的对象形式,即正则模式: 将一个字符串转成正则匹配模式对象

       是正则模式匹配给定字符串的匹配器,Pattern对象调用匹配器matcher()方法,查找符合匹配要求的匹配项










拆箱:包装类型转换为基本类型

装箱:基本类型转换为包装类型

进行比较时,被装箱为。在Java中,基本类型比较的是值,而封装类型比较的是对象的地址。但是这两个包装类对象是同一个对象。因为Integer类,里面涉及到缓存机制,如果给定的基本类型int值在-128到127之间的话,就会直接去cache数组里取,如果不在这个范围的话,那么就会创新的对象。










当切换系统语言时,引用到一些的string的写法会有不同,一般有以下三种方式选择

.字符串的反转

  • charAt():通过String类的charAt()的方法来获取字符串中的每一个字符,然后将其拼接为一个新的字符串
  • toCharArray():通过String的toCharArray()方法可以获得字符串中的每一个字符并转换为字符数组,然后用一个空的字符串从后向前一个个的拼接成新的字符串。
  • reverse():通过StringBuiler或StringBuffer的reverse()的方法
  • replace():替换字符串中所有指定的字符,然后生成一个新的字符串,原来的字符串不发生改变
  • replaceAll():字符串中某个指定的字符串替换为其它字符串
    replaceFirst():替换第一个出现的指定字符串

  • String类是不可变类,任何对String的改变都会引发新的String对象的生成;
  • StringBuffer是可变类,任何对它所指代的字符串的改变都不会产生新的对象,线程安全的。
  • StringBuilder是可变类,线性不安全的,不支持并发操作,不适合多线程中使用,但其在单线程中的性能比StringBuffer高。

指代上的区别

  • super:是对当前对象中父对象的引用。
  • This:指当前对象的参考。
  • super:直接父类中引用当前对象的成员(当基本成员和派生类具有相同成员时,用于访问直接父类中隐藏父类中的成员数据或函数定义)。
  • This:表示当前对象的名称(程序中容易出现歧义的地方,应该用来表示当前对象;如果函数的成员数据与该类中成员数据的名称相同,应用于表示成员变量名称)。
  • super:在基类中调用构造函数(是构造函数中的第一条语句)。
  • This:在此类中调用另一个结构化的构造函数(是构造函数中的第一条语句)。

相似点

  • 两者都可包含抽象方法。实现抽象类和接口的非抽象类必须实现这些抽象方法
  • 两者都不能用来实例化对象。可以声明抽象类和接口的变量,对抽象类来说,要用抽象类的非抽象子类来实例化该变量;对接口来说,要用实现接口的非抽象子类来实例化该变量
  • 两者的子类如果都没有实现抽象类(接口)中声明的所有抽象方法,则该子类就是抽象类
  • 两者都可以实现程序的多态性

不同点

  • 一个类只能继承一个直接父类,但是可以实现多个接口
  • 抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
  • 抽象类可以有构造函数;接口不能有。
  • 抽象类可以有 main 方法,并且我们能运行它;接口不能有 main 方法。
  • 接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。
  • 抽象类不能在Java 8 的 lambda 表达式中使用
  • 接口体现的是一种规范(打印机和相机都有打印的功能),与实现接口的子类中不存在父与子的关系;抽象类与其子类存在父与子的关系(圆形和方形都是一种形状)

Error(错误):通常是灾难性的致命错误,不是程序(程序猿)可以控制的,如内存耗尽、JVM系统错误、堆栈溢出等。应用程序不应该去处理此类错误,且程序员不应该实现任何Error类的子类。

Exception(异常):用户可能捕获的异常情况,可以使用针对性的代码进行处理,如:空指针异常、网络连接中断、数组下标越界等。

RuntimeException类及其子类称为非检查型异常,Java编译器会自动按照异常产生的原因引发相应类型的异常,程序中可以选择捕获处理也可以不处理,虽然Java编译器不会检查运行时异常,但是也可以去进行捕获和抛出处理。RuntimeException类和子类以及Error类都是非受检异常。










Java 的所有异常可以分为受检异常(checked exception)和非受检异常(unchecked exception)。

受检异常
编译器要求必须处理的异常。Exception 中除 RuntimeException 及其子类之外的异常都属于受检异常。编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用方法签名中用 throws 关键字抛出,否则编译不通过。

非受检异常
编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException及其子类)和错误(Error)。

运行时异常
定义:RuntimeException 类及其子类。
特点:RuntimeException为Java虚拟机在运行时自动生成的异常,如被零除和非法索引、操作数超过数组范围、打开文件不存在等。此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。
RuntimeException类及其子类称为非检查型异常,Java编译器会自动按照异常产生的原因引发相应类型的异常,程序中可以选择捕获处理也可以不处理,虽然Java编译器不会检查运行时异常,但是也可以去进行捕获和抛出处理。RuntimeException类和子类以及Error类都是非受检异常。

编译时异常
特点: Exception中除RuntimeException及其子类之外的异常,该异常必须手动在代码中添加捕获语句来处理该异常。编译时异常也称为受检异常,一般不进行自定义检查异常。。


































  • throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常。
  • throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。调用该方法的方法必须包含可处理异常的代码,否则也要在方法声明中用 throws 关键字声明相应的异常。
  • finally语句块中发生了异常
  • 前面的代码中执行了System.exit()退出程序
  • 程序中所在的线程死亡
  • 关闭CPU

try中有return
无论在什么位置添加return,finally子句都会被执行

catch和try中都有return
当try中抛出异常且catch中有return语句,finally中没有return语句,java先执行catch中非return语句,再执行finally语句,最后执行return语句。若try中没有抛出异常,则程序不会执行catch体里面的语句,java先执行try中非return语句,再执行finally语句,最后再执行try中的return语句。

finally 中有return
finally中有return时,会覆盖掉try和catch中的return。

finally中没有return语句,但是改变了返回值
如果finally中定义的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块中之前保存的值;如果finally中定义的数据是是引用类型,则finally中的语句会起作用,try中return语句的值就是在finally中改变后该属性的值。




























Java IO流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java IO流的40多个类大部分都是从如下4个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
  • 按照流的流向分,可以分为输入流和输出流;
  • 按照操作单元划分,可以分为字节流和字符流;
  • 按照流的角色划分,可以分为节点流和处理流。

同步与异步

  • 同步: 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。
  • 异步: 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。
  • 同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。
  • 阻塞: 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。
  • 非阻塞: 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。
  • 阻塞和非阻塞的区别最大在于有没有一直在干其他的活动。
  • Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList,LinkedList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好
    在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能,而访问链表中的某个元素时,就必须从链表的一端开始沿着连接方向一个一个元素地去查找,直到找到所需的元素为止,所以,当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList

  • Hashtable:基于哈希表实现的,同样每个元素是一个key-value对,其内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。
    Hashtable也是JDK1.0引入的类,是线程安全的,能用于多线程环境中。

  • HashMap,HashSet不是线程安全的
  • HashMap: 实现了Map接口,Map接口对键值对进行映射。Map中不允许重复的键。Map接口有两个基本的实现,HashMap和TreeMap。TreeMap保存了对象的排列次序,而HashMap则不能。HashMap。HashMap是非synchronized的,但是HashMap可以通过进行同步:  , 这样多个线程同时访问HashMap时,能保证只有一个线程更改Map。public Object put(Object Key,Object value)方法用来将元素添加到map中
  • HashSet: 实现了Set接口,不允许集合中有重复的值,对象存储在HashSet之前,要先确保对象重写equals()和hashCode()方法,这样才能比较对象的值是否相等,以确保set中没有储存相等的对象。如果我们没有重写这两个方法,将会使用这个方法的默认实现。public boolean add(Object o)方法用来在Set中添加元素,当元素值重复时则会立即返回false,如果成功添加的话会返回true。

Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度。

  1. HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
  2. HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
  3. 另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器,不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
  4. 由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
  5. HashMap不能保证随着时间的推移Map中的元素次序是不变的。
  • HashMap实现了Map接口, HashSet实现了Set接口
  • HashMap储存键值对, HashSet仅仅存储对象
  • HashMap使用put()方法将元素放入map中, HashSet使用add()方法将元素放入set中
  • HashMap中使用键对象来计算hashcode值, HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false
  • HashMap比较快,因为是使用唯一的键来获取对象 HashSet较HashMap来说比较慢.

相同点:

  • TreeMap和TreeSet都是有序的集合(非线程安全的),也就是说他们存储的值都是排好序
    的。

  • TreeMap和TreeSet都是非同步集合,因此他们不能在多线程之间共享,不过可以使用方法Collections.synchroinzedMap()来实现同步
  • 运行速度都要比Hash集合慢,他们内部对元素的操作时间复杂度为O(logN),而
    HashMap/HashSet则为O(1)。

  • 要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。


不同点:

  • 最主要的区别就是TreeSet和TreeMap分别实现Set和Map接口
  • TreeSet只存储一个对象,而TreeMap存储两个对象Key和Value(仅仅key对象有序)
  • TreeSet中不能有重复对象,而TreeMap中可以存在
  • TreeMap的底层采用红黑树的实现,完成数据有序的插入,排序。因此它要求一定要有Key比较的方法,要么传入Comparator实现,要么key对象实现Comparable接口。

HashMap:
内部是使用一个默认容量为16的数组来存储数据的,而数组中每一个元素却又是一个链表的头结点,所以,更准确的来说,HashMap内部存储结构是使用哈希表的拉链结构(数组+链表),HashMap获取数据是通过遍历Entry[]数组来得到对应的元素,在数据量很大时候会比较慢,所以在Android中,HashMap是比较费内存的。

ArrayMap:
是一个<key,value>映射的数据结构,它设计上更多的是考虑内存的优化,内部是使用两个数组进行数据存储,一个数组记录key的hash值,另外一个数组记录Value值,它和SparseArray一样,也会对key使用二分法进行从小到大排序,在添加、删除、查找数据的时候都是先使用二分查找法得到相应的index,然后通过index来进行添加、查找、删除等操作,所以,应用场景和SparseArray的一样,如果在数据量比较大的情况下,那么它的性能将退化至少50%。

HashMap和ArrayMap各自的优势
















  1. 查找效率
    HashMap因为其根据hashcode的值直接算出index,所以其查找效率是随着数组长度增大而增加的。ArrayMap使用的是二分法查找,所以当数组长度每增加一倍时,就需要多进行一次判断,效率下降。所以对于数量比较大的情况下,推荐使用HashMap

  2. 扩容数量
    HashMap初始值16个长度,每次扩容的时候,直接申请双倍的数组空间。
    ArrayMap每次扩容的时候,如果size长度大于8时申请size*1.5个长度,大于4小于8时申请8个,小于4时申请4个。这样比较ArrayMap其实是申请了更少的内存空间,但是扩容的频率会更高。因此,如果当数据量比较大的时候,还是使用HashMap更合适,因为其扩容的次数要比ArrayMap少很多。




  3. 扩容效率
    HashMap每次扩容的时候时重新计算每个数组成员的位置,然后放到新的位置。
    ArrayMap则是直接使用System.arraycopy。所以效率上肯定是ArrayMap更占优势。这里需要说明一下,网上有一种说因为ArrayMap使用System.arraycopy更省内存空间,这一点我真的没有看出来。arraycopy也是把老的数组的对象一个一个的赋给新的数组。当然效率上肯定arraycopy更高,因为是直接调用的c层的代码。




  4. 内存耗费
    以ArrayMap采用了一种独特的方式,能够重复的利用因为数据扩容而遗留下来的数组空间,方便下一个ArrayMap的使用。而HashMap没有这种设计。由于ArrayMap只缓存了长度是4和8的时候,所以如果频繁的使用到Map,而且数据量都比较小的时候,ArrayMap无疑是相当的节省内存的。

Collection:
是集合类的上层接口。本身是一个Interface,里面包含了一些集合的基本操作。Collection接口时Set接口和List接口的父接口

Collections
Collections是一个集合框架的帮助类,里面包含一些对集合的排序,搜索以及序列化的操作。最根本的是Collections是一个类,Collections 是一个包装类,Collection 表示一组对象,这些对象也称为 collection 的元素。一些collection 允许有重复的元素, 而另一些则不允许,一些 collection 是有序的,而另一些则是无序的。










进程 :进程是并发执行程序在执行过程中资源分配和管理的基本单位(资源分配的最小单位)。进程可以理解为一个应用程序的执行过程,应用程序一旦执行,就是一个进程。每个进程都有自己独立的地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段。通常来说,应用中的 Activity、Service 等四大组件默认都位于一个进程里面,并且这个进程名称的默认值就是我们给应用定义的包名。系统为每个进程分配的内存是有限的,比如在以前的低端手机上常见是 16M,现在的机器内存更大一些,32M、48M,甚至更高。但是,总是有限的,毕竟一个手机出厂之后RAM 的大小就定了,总是无法满足所有应用的需求。所以,一个明智的选择就是使用多进程,将一些看不见的服务、比较独立而又相当占用内存的功能运行在另外一个进程当中,主动分担主进程的内存消耗。常见如,应用中的推送服务,音乐类App 的后台播放器等等,单独运行在一个进程中。

线程: 程序执行的最小单位。每个进程都有自己的地址空间,即进程空间,在网络或多用户换机下,一个服务器通常需要接收大量不确定数量用户的并发请求,为每一个请求都创建一个进程显然行不通(系统开销大响应用户请求效率低),因此操作系统中线程概念被引进。引入目的是为了减少程序在并发执行过程中的开销,使OS的并发效率更高。

进程与线程的区别










  • 调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位;
  • 地址空间: 同一进程的所有线程共享本进程的地址空间,而不同的进程之间的地址空间是独立的。
  • 资源拥有: 同一进程的所有线程共享本进程的资源,如内存,CPU,IO等。进程之间的资源是独立的,无法共享。
  • 执行过程:每一个进程可以说就是一个可执行的应用程序,每一个独立的进程都有一个程序执行的入口,顺序执行序列。但是线程不能够独立执行,必须依存在应用程序中,由程序的多线程控制机制进行控制。
  • 健壮性: 因为同一进程的所以线程共享此线程的资源,因此当一个线程发生崩溃时,此进程也会发生崩溃。 但是各个进程之间的资源是独立的,因此当一个进程崩溃时,不会影响其他进程。因此进程比线程健壮。线程执行开销小,但不利于资源的管理与保护。进程的执行开销大,但可以进行资源的管理与保护。进程可以跨机器前移。
  • 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程;
  • 资源分配给进程,同一进程的所有线程共享该进程的所有资源;
    -处理机分给线程,即真正在处理机上运行的是线程;
    -线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。




  • 在程序中,如果需要频繁创建和销毁的,使用线程。因为进程创建和销毁开销很大(需要不停的分配资源),但是线程频繁的调用只是改变CPU的执行,开销小
  • 如果需要程序更加的稳定安全时,可以选择进程。如果追求速度,就选择线程。

1.继承Thread类创建线程类

  • 定义Thread类的子类, 并重写该类的run方法, 该run方法的方法体就代表了
    线程要完成的任务。 因此把run()方法称为执行体。

  • 创建Thread子类的实例, 即创建了线程对象。
  • 调用线程对象的start()方法来启动该线程
  • 定义runnable接口的实现类, 并重写该接口的run()方法, 该run()方法的方法体同样是该线程的线程执行体。
  • 创建 Runnable实现类的实例, 并依此实例作为Thread的target来创建Thread对象, 该Thread对象才是真正的线程对象。
  • 调用线程对象的start()方法来启动该线程。
  • 创建Callable接口的实现类, 并实现call()方法, 该call()方法将作为线程执行体, 并且有返回值
  • 创建Callable实现类的实例, 使用FutureTask类来包装Callable对象, 该FutureTask对象封装了该Callable对象的call()方法的返回值。
  • 使用FutureTask对象作为Thread对象的target创建并启动新线程。
  • 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值, 调用get()方法会阻塞线程
  • 采用实现Runnable、Callable接口的方式创建多线程时:
  • 优势
    线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

  • 劣势
    编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

  • 使用继承Thread类的方式创建多线程时
  • 优势
    编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

  • 劣势
    线程类已经继承了Thread类,所以不能再继承其他父类。

  • Callcble是可以有返回值的,具体的返回值就是在Callable的接口方法call返回的,并且这个返回值具体是通过实现Future接口的对象的get方法获取的,这个方法是会造成线程阻塞的;而Runnable是没有返回值的,因为Runnable接口中的run方法是没有返回值的;
  • Callable里面的call方法是可以抛出异常的,我们可以捕获异常进行处理;但是Runnable里面的run方法是不可以抛出异常的,异常要在run方法内部必须得到处理,不能向外界抛出;
  • callable和runnable都可以应用于executors。而thread类只支持runnable

定义: 指在程序运行时 在后台提供一种通用服务的线程,这种线程并不属于程序中不可或缺的部分。通俗点讲,任何一个守护线程都是整个JVM中所有非守护线程的"保姆"。

特点: 守护线程拥有自动结束自己生命周期的特性,而非守护线程不具备这个特点。当 JVM 中不存在任何一个正在运行的非守护线程时,JVM 进程即会退出,也就是说JVM 中的垃圾回收线程就是典型的守护线程。

使用方法: 在Java语言中,守护线程一般具有较低的优先级,它并非只由JVM内部提供,用户在编写程序时也可以自己设置守护线程,例如:将一个线程设置为守护线程的方法就是在调用start()启动线程之前调用对象的setDaemon(true)方法,若将以上参数设置为false,则表示的是用户进程模式,需要注意的是,当在一个守护线程中产生了其他的线程,那么这些新产生的线程默认还是守护线程,用户线程也是如此。

应用场景:通常来说,守护线程经常被用来执行一些后台任务,但是呢,你又希望在程序退出时,或者说 JVM 退出时,线程能够自动关闭,此时,首选守护线程。
















请添加图片描述

总体来说线程安全在三个方面体现:
原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,
(atomic,synchronized)。
可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile)。
有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。










  • 缓存导致的可见性问题
  • 线程切换带来的原子性问题
  • 编译优化带来的有序性问题
  • JDK Atomic开头的原子类(AtomicInteger,AtomicLong,AtomicBoolean等等)、synchronized、LOCK,可以解决原子性问题
  • synchronized、volatile、LOCK,可以解决可见性问题
  • Happens-Before 规则可以解决有序性问题,synchronized和Lock来保证有序性
  • 使用java提供的安全类:java.util.concurrent包下的类自身就是线程安全的,在保证安全的同时还能保证性能;
  • 程序次序规则:在一个线程内,按照程序控制流顺序,书写在前面的操作先行发生于书写在后面的操作
  • 管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作
  • volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作
  • 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作
  • 线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测
  • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
  • 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始
  • 修饰实例方法,对当前实例对象this加锁
  • 修饰静态方法,对当前类的Class对象加锁
  • 修饰代码块,指定加锁对象,对给定对象加锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。是操作系统层面的一个错误,是进程死锁的简称,系统发生死锁现象不仅浪费大量的系统资源,甚至导致整个系统崩溃,带来灾难性后果。

一般来说,要出现死锁问题需要满足以下4个条件:

  • 互斥条件:一个资源每次只能被一个线程使用。
  • 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:线程已获得的资源,在未使用完之前,不能强行剥夺。
  • 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
  • 静态的锁顺序死锁:a和b两个方法都需要获得A锁和B锁。一个线程执行a方法且已经获得了A锁,在等待B锁;另一个线程执行了b方法且已经获得了B锁,在等待A锁。解决方法是将所有需要多个锁的线程,都以相同的对象顺序来获得锁。
  • 动态的锁顺序死锁:指两个线程调用同一个方法时,传入的参数颠倒造成的死锁,解决方案是使用System.identifyHashCode来定义锁的顺序,确保所有的线程都以相同的顺序获得锁。
  • 协作对象之间发生的死锁:一个线程调用了A对象的a方法,另一个线程调用了B对象的b方法。此时可能会发生,第一个线程持有A对象锁并等待B对象锁,另一个线程持有B对象锁并等待A对象锁。解决方案是需要调用某个外部方法时不需要持有锁,即避免在持有锁的情况下调用外部的方法。

ThreadLocal是什么:

JDK1.2的版本中就提供java.lang.ThreadLocal类,每一个ThreadLocal能够放一个线程级别的变量, 它本身能够被多个线程共享使用,并且又能够达到线程安全的目的,且绝对线程安全。ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

相似点:
这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的(操作系统需要在用户态与内核态之间来回切换,代价很高,不过可以通过对锁优化进行改善)。

不同点:







  • 语法区别:
  • 是java语言的,是原生语法层面的互斥,需要jvm实现,是JDK 1.5之后提供的的互斥锁,
  • 可以修饰实例方法,静态方法,代码块。自动释放锁。一般需要try catch finally语句,在try中获取锁,在finally释放锁。需要手动释放锁。
  • 的范围是整个方法或synchronized块部分,因为是方法调用,可以跨方法,灵活性更大
  • 实现方式的区别:
  • 是重量级锁。重量级锁需要将线程从内核态和用户态来回切换。如:A线程切换到B线程,A线程需要保存当前现场,B线程切换也需要保存现场。这样做的缺点是耗费系统资源。这是一种被动阻塞悲观锁,状态是block
  • 是轻量级锁。采用cas+volatile管理线程,不需要线程切换切换获取锁线程,这是一种乐观的思想(可能失败),这是一种主动的阻塞乐观锁,状态是wait
  • 公平和非公平
    公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,

  • 只有非公平锁。
  • 默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。公平锁通过构造函数传递true表示。
  • 可中断的
    持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待

  • 是不可中断的。
  • 提供可中断和不可中断两种方式。其中方法表示可中断,方法表示不可中断。这相当于Synchronized来说可以避免出现死锁的情况。
  • 条件队列
    同步队列:多线程同时竞争一把锁失败被挂起的线程。
    条件队列:正在执行的线程调用await/wait,从同步队列加入的线程会进入条件队列。正在执行线程调用signal/signalAll/notify/notifyAll,会将条件队列一个线程或多个线程加入到同步队列。
    等待队列:和条件队列一个概念。







  • 只有一个等待队列,要么随机唤醒一个线程要么唤醒全部线程。
  • 中一把锁可以对应多个条件队列。通过newCondition表示。一个ReentrantLock对象可以同时绑定对个对象。ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程
  1. 创建(NEW):新创建了一个线程对象。
  2. 就绪(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
  3. 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
  4. 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
  • 等待阻塞:运行(running)的线程执行方法,JVM会把该线程放入等待队列(waitting queue)中。
  • 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
  • 其他阻塞:运行(running)的线程执行或方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
  1. 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

基本区别

  • sleep是Thread类的方法,wait是Object类中定义的方法
  • sleep方法可以在任何地方调用
  • wait方法只能在synchronized方法或synchronized块中使用
  • Thread.sleep是static静态方法,不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。只会让出CPU,不会导致锁行为的改变
  • Object.wait不仅让出CPU,还会释放已经占有的同步资源锁
  • 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
  • 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
  • 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
  • 如果wait被调用之前notify的唤醒通知就来了,那么这个线程并不能保证被唤醒,有可能会导致死锁问题。

创建一个线程 Thread t1 = new Thread()

  • :run是用主线程执行,只是调用了一个普通方法,并没有启动另一个线程,程序还是会按照顺序执行相应的代码,必须是这个方法执行完了代码才能往下走。
  • :start是new了一个线程执行的,表示重新开启一个线程,不必等待其他线程运行完,只要得到cup就可以运行该线程。有多个线程使用start方法开启时,并不需要等待其中一个完成,所以他们的执行顺序应该是并行的。

Executors类创建线程池,创建出来的线程池都实现了ExecutorService接口

  • 方法可缓存的线程池,如果线程池的容量超过了任务数,自动回收空闲线程,任务增加时可以自动添加新线程,线程池的容量不限制。比较适合处理执行时间比较小的任务。
  • 创建固定数目线程的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程数量不再变化,当线程发生错误结束时,线程池会补充一个新的线程。可以用于已知并发压力的情况下,对线程数做限制。
  • 创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。适用于需要多个后台线程执行周期任务的场景。
  • 创建一个单线程化的Executor,线程异常结束,会创建一个新的线程,能确保任务按指定顺序(FIFO,LIFO,优先级)执行。可以用于需要保证顺序执行的场景,并且只有一个线程在执行

在这里插入图片描述

  • 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
  • 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!
  • 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
  • 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。
  • 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会正在处理的任务。
  • 状态切换:调用线程池的()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
  • 状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
  • 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
    当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

  • 状态说明:线程池彻底终止,就变成TERMINATED状态。
  • 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
  • 机制: 首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
  • 缺点
  • 效率问题:标记和清除两个过程的效率都不高
  • 空间问题:标记清除之后产生大量不连续的内存碎片,可能会导致程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
  • 机制:将可用内存按容量大小划分为大小相等的两块,每次只使用其中的一块。当一块内存使用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况。现代的商业虚拟机都采用这种收集算法来回收新生代。
  • 缺点: 将内存缩小为了原来的一半,在对象存活率较高时,就要进行较多的复制操作,效率就会变低。。
  • 机制: 首先标记出所有需要回收的对象, 在标记完成后让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。
  • 机制:把Java堆分为新生代和老年代,根据年代特点采用适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法
    在老年代中,因为对象存活率高、没有额外空间对它进行分配担保,就必须采用“标记-清除”或“标记-整理”算法来进行回收。

  • 判断对象是否存活与“引用”有关
    我们希望的垃圾回收器对它的回收时机的不同。对于一些比较重要的对象,我们希望垃圾回收器永远不去回收它,即使此时内存空间已经不足了,因为一旦它被回收,将导致严重的后果。而对于一些不那么重要的对象,比如在做图片缓存的时候生成的大量图片的缓存对象,我们希望垃圾回收器只在内存不足的情况下去对它进行回收以提升用户体验。
    Java中实际上有四种强度不同的引用,从强到弱它们分别是,强引用(Strong Reference),软引用(Soft Reference),弱引用(Weak Reference)和虚引用(Phantom Reference)。

    : 就是指在程序代码之中普遍存在的,类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

    : 对象留在内存的能力不是那么强的引用。使用WeakReference,在系统,将会把这些对象列进回收范围之中进行第二次回收。软引用非常适合于创建缓存。当系统内存不足的时候,缓存中的内容是可以被释放的。

    : 描述非必须对象的。被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,,都会回收掉只被弱引用关联的对象。弱引用最常见的用处在哈希表中。当一个键值对被放入到哈希表中之后,哈希表对象本身就有了对这些键和值对象的引用。如果这种引用是强引用的话,那么只要哈希表对象本身还存活,其中所包含的键和值对象是不会被回收的。如果某个存活时间很长的哈希表中包含的键值对很多,最终就有可能消耗掉JVM中全部的内存。

    : 一个对象无法通过虚引用来取得一个对象实例,就是形同虚设,与其他几种引用不同的是,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用。虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
    ReferenceQueue queue = new ReferenceQueue ();
    //虚引用对象
    PhantomReference pr = new PhantomReference (object, queue);
    程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。








































  • 引用计数法判断对象
    给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用
    失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能被再使用的。
    主流的JVM里面没有选用引用计数算法来管理内存,其中最主要的原因是它很难解
    决对象间的互循环引用的问题。










  • 可达性分析算法
  • HTTP 是明文传输协议,HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。
  • HTTPS比HTTP更加安全,对搜索引擎更友好,利于SEO,谷歌、百度优先索引HTTPS网页;
  • HTTPS需要用到SSL证书,而HTTP不用;
  • HTTPS标准端口443,HTTP标准端口80;
  • HTTPS基于传输层,HTTP基于应用层;
  • HTTPS在浏览器显示绿色安全锁,HTTP没有显示;
  • 第一次握手:客户端发送连接请求,服务端接收,所以服务端可以确认客户端的发送功能,服务端的接收功能正常;
  • 第二次握手:服务端收到连接请求报文段后,同意连接发送应答,客户端接收到第二次握手报文后,客户端能够确认服务端发送、服务端接收,客户端的接收功能和发送功能是正常的;但是此时服务端并不知道自己的发送功能,客户端的接收功能是否正常,于是需要第三次握手
  • 第三次握手: 客户端收到连接同意的应答后,还要向服务端发送一个确认报文段,表示:服务端发来的连接同意应答已经成功收到。此时,服务端就可以确认自己的发送功能,客户端的接收功能是正常的。
  • 为什么要三次握手?
    client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。

  • 第一次分手:主机1(可以使客户端,也可以是服务器端),设置SequenceNumber,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
  • 第二次分手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我“同意”你的关闭请求;
  • 第三次分手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态;
  • 第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。
  • 为什么要四次分手?
    TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式,这就意味着,当主机1发出FIN报文段时,只是表示主机1已经没有数据要发送了,主机1告诉主机2,它的数据已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的数据;当主机2返回ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的;当主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接。

顾名思义就是只有一个实例,并且她自己负责创建自己的对象,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。核心代码:构造方法私有化,private。

  • 懒汉式:顾名思义就是实例在用到的时候才去创建,“比较懒”,用的时候才去检查有没有实例,如果有则返回,没有则新建。有线程安全和线程不安全两种写法,区别就是synchronized关键字。
  • 饿汉式: 从名字上也很好理解,就是“比较勤”,实例在初始化的时候就已经建好了,不管你有没有用到,都先建好了再说。好处是没有线程安全的问题,坏处是浪费内存空间。
  • 双检锁,又叫双重校验锁,综合了懒汉式和饿汉式两者的优缺点整合而成。特点是在synchronized关键字内外都加了一层 if 条件判断,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。
  • 静态内部类:静态内部类的方式效果类似双检锁,但实现更简单。但这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
  • 枚举的方式是比较少见的一种实现方式,但是看上面的代码实现,却更简洁清晰。并且她还自动支持序列化机制,绝对防止多次实例化。
    在这里插入图片描述

创建型模式
对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。

  • 简单工厂模式(Simple Factory)
  • 工厂方法模式(Factory Method)
  • 抽象工厂模式(Abstract Factory)
  • 建造者模式(Builder)
  • 原型模式(Prototype)
  • 单例模式(Singleton)

结构型模式
描述如何将类或者对 象结合在一起形成更大的结构,就像搭积木,可以通过 简单积木的组合形成复杂的、功能更为强大的结构。

  • 适配器模式(Adapter)
  • 桥接模式(Bridge)
  • 组合模式(Composite)
  • 装饰模式(Decorator)
  • 外观模式(Facade)
  • 享元模式(Flyweight)
  • 代理模式(Proxy)

行为型模式
是对在不同的对象之间划分责任和算法的抽象化。行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象 之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。

  • 职责链模式(Chain of Responsibility)
  • 命令模式(Command)
  • 解释器模式(Interpreter)
  • 迭代器模式(Iterator)
  • 中介者模式(Mediator)
  • 备忘录模式(Memento)
  • 观察者模式(Observer)
  • 状态模式(State)
  • 策略模式(Strategy)
  • 模板方法模式(Template Method)
  • 访问者模式(Visitor)

原文链接:https://blog.csdn.net/Pandafz1997/article/details/

到此这篇阻塞队列有哪些(阻塞队列有哪些实现?各有什么优缺点)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • 学籍认证码在哪里找(学籍认证码如何查询)2025-07-05 22:00:06
  • tpnd全称(tpi全称)2025-07-05 22:00:06
  • pass平台是怎么实现的啊(pass平台是怎么实现的啊知乎)2025-07-05 22:00:06
  • 圈一圈写一写图解一年级圆圈(小学一年级 圈一圈 填一填)2025-07-05 22:00:06
  • 蓝牙地址是什么(蓝牙地址是不是唯一的)2025-07-05 22:00:06
  • 虚拟机识别不了u盘(虚拟机识别不了u盘怎么解决)2025-07-05 22:00:06
  • 圈一怎么用键盘打出来(圈一符号怎么打出来)2025-07-05 22:00:06
  • flash打包exe(flash打包apk)2025-07-05 22:00:06
  • 阻塞队列和非阻塞队列(阻塞队列和非阻塞队列的关系)2025-07-05 22:00:06
  • 圈一圈算一算怎么圈图(除法圈一圈算一算怎么圈图)2025-07-05 22:00:06
  • 全屏图片