导图社区 java基础
java基础的复习脑图,有锁,set/list/map的子类解释和例子,IO流等java的一些知识点和例子
编辑于2020-06-29 21:06:00java基础
java集合框架
Map
HashMap
JDk1.8之前实现方式
数组 + 双向链表 + 头插法 在并发下头插法容易出现死循环
JDK1.8之后实现方式
数组 +双向链表 +红黑树 + 尾插法 尾插法修改了并发死循环问题 当链表长度>8时变为红黑树 <6时变回链表。优化了查询速度
LinkedHashMap
是HashMap的增强,可以对键值对进行排序。有两种排序方式,一种根据插入顺序排序(插入顺序),一种根据最后一次被访问的顺序排序(访问顺序)。LinkedHashMap(int size, float loadFactor, boolean Order),Order为true代表访问顺序,false代表插入顺序
HashTable
线程安全类,使用的是synchronize实现线程安全
ConcurrentHashMap
是线程安全的Map,1.7之前的实现,和1.8之前的实现
JDk1.8之前实现方式
数组+链表 分段锁,每个segment继承ReentrantLock实现并发 多个线程同时竞争获取同一个segment锁,获取成功的线程更新map;失败的线程尝试多次获取锁仍未成功,则挂起线程,等待释放锁
JDK1.8之后实现方式
CAS( 比较并替换)。利用 CAS + synchronized 来保证并发更新的安全 底层使用=数组+链表+红黑树来实现。 put操作过程: 假设table已经初始化完成,put操作采用==CAS+synchronized==实现并发插入或更新操作: - 当前bucket为空时,使用CAS操作,将Node放入对应的bucket中。 - 出现hash冲突,则采用synchronized关键字。倘若当前hash对应的节点是链表的头节点,遍历链表,若找到对应的node节点,则修改node节点的val,否则在链表末尾添加node节点;倘若当前节点是红黑树的根节点,在树结构上遍历元素,更新或增加节点。 - 倘若当前map正在扩容f.hash == MOVED, 则跟其他线程一起进行扩容 数组+链表+红黑树 + Node(键值对实现方式)
Set
HashSet
无序
LinkedHashSet
增强了HashSet,继承了HashMap。可以根据插入的顺序进行排序
TreeSet
TreeSet继承自SortSet可以自定义排序
List
ArrayList
LinkedList
它同时扩展自List接口和Deque接口。实现了添加和删除 表头表尾元素的方法。
I/O流
I/O的分类
字节流
InputStream
FileInputStream
ObjectInputStream
FilterInputStream
BufferInputStream
DataInputStream
ByteArrayInputStream
PipedInputStream
一个字节一个字节的写入到内存,也可以存到字节数组中写入提高速度
OutputStream
FileOutputStream
ObjectOutputStream
FilterOutputStream
BufferOutputStream
DataOutputStream
ByteArrayOutputStream
PipedOutputStream
一个字节一个字节的输出到内存以外的任何设备(屏幕,文件等),也可以存到字节数组中输出提高速度j
字符流
Reader
CharArrayReader
pipedReader
FilterReader
BufferedReader
inputStreamReader
FileReader
按字符(java规范内码编码UTF-16,一般不同编码占的字节数也不同,UTF-8是2字节即16位)进行写入到内存
Writer
CharArrayWriter
PipedWriter
FilterWriter
inputStreamWriter
FileWriter
PrintWriter
按字符进行输出到内存以外的任何设备
I/O的类型
使用非阻塞IO时,如果不能立马读写,Java调用会马上返回,当IO事件分发器通知可读写时在进行读写,不断循环直到读写完成。 1.BIO:同步并阻塞,服务器的实现模式是一个连接一个线程,这样的模式很明显的一个缺陷是:由于客户端连接数与服务器线程数成正比关系,可能造成不必要的线程开销,严重的还将导致服务器内存溢出。当然,这种情况可以通过线程池机制改善,但并不能从本质上消除这个弊端。 2.NIO:在JDK1.4以前,Java的IO模型一直是BIO,但从JDK1.4开始,JDK引入的新的IO模型NIO,它是同步非阻塞的。而服务器的实现模式是多个请求一个线程,即请求会注册到多路复用器Selector上,多路复用器轮询到连接有IO请求时才启动一个线程处理。 3.AIO:JDK1.7发布了NIO2.0,这就是真正意义上的异步非阻塞,服务器的实现模式为多个有效请求一个线程,客户端的IO请求都是由OS先完成再通知服务器应用去启动线程处理(回调)。 应用场景:并发连接数不多时采用BIO,因为它编程和调试都非常简单,但如果涉及到高并发的情况,应选择NIO或AIO,更好的建议是采用成熟的网络通信框架Netty。
BIO
同步阻塞IO
NIO
同步非阻塞IO
AIO
异步非阻塞IO。也可以说是NIO2
异步/多线程
异步编程
并行
多个任务,开多个线程去同时执行。不具有原子性和可见性因为不是同一个线程
java.util.concurrent
有返回值
Callable接口
Future<?> future = executorService.submit(new Callable) 使用future.get()获得响应的值
无返回值
Runnable接口
executorService.submit(new Runnable)
并发
一个线程,异步的执行多个任务,不需要阻塞在一个任务处,等待响应。具有原子性和看见性。
Guave中的Future
import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; /** * Guava中的Future * * @author Administrator * */ public class GuavaFutureDemo { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(1); // 使用guava提供的MoreExecutors工具类包装原始的线程池 ListeningExecutorService listeningExecutor = MoreExecutors.listeningDecorator(executor); //向线程池中提交一个任务后,将会返回一个可监听的Future,该Future由Guava框架提供 ListenableFuture<String> lf = listeningExecutor.submit(new Callable<String>() { @Override public String call() throws Exception { System.out.println("task started!"); //模拟耗时操作 Thread.sleep(3000); System.out.println("task finished!"); return "hello"; } }); //添加回调,回调由executor中的线程触发,但也可以指定一个新的线程 Futures.addCallback(lf, new FutureCallback<String>() { //耗时任务执行失败后回调该方法 @Override public void onFailure(Throwable t) { System.out.println("failure"); } //耗时任务执行成功后回调该方法 @Override public void onSuccess(String s) { System.out.println("success " + s); } }); //主线程可以继续做其他的工作 System.out.println("main thread is running"); } }
netty中的promise
import io.netty.util.concurrent.DefaultEventExecutorGroup; import io.netty.util.concurrent.EventExecutorGroup; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; /** * netty中的promise * * @author Administrator * */ public class NettyPromiseDemo { @SuppressWarnings({ "unchecked", "rawtypes" }) public static void main(String[] args) throws Throwable { //线程池 EventExecutorGroup group = new DefaultEventExecutorGroup(1); //向线程池中提交任务,并返回Future,该Future是netty自己实现的future //位于io.netty.util.concurrent包下,此处运行时的类型为PromiseTask Future<?> f = group.submit(new Runnable() { @Override public void run() { System.out.println("任务正在执行"); //模拟耗时操作,比如IO操作 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("任务执行完毕"); } }); //增加监听 f.addListener( new FutureListener() { @Override public void operationComplete(Future arg0) throws Exception { System.out.println("ok!!!"); } }); System.out.println("main thread is running."); } }
JDk1.8的异步处理
package com.test; import lombok.SneakyThrows; import java.util.concurrent.*; import java.util.function.Consumer; import java.util.function.Supplier; /** * @author wdg * @date 2020/6/29 16:45 * @Description */ public class ThreadDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); //创建线程池 CompletableFuture<Object> future = CompletableFuture.supplyAsync(new Supplier<Object>() { @SneakyThrows @Override public Object get() { Thread.sleep(3000); return Thread.currentThread().getName(); } },executorService);//使用创建的线程池进行一次异步任务 future.thenAccept(new Consumer<Object>() { //接受任务的响应值 @Override public void accept(Object o) { System.out.println(o); } }); } }
java.util.concurrent&java.util.Future
多线程
Thread类
Runable接口
Callable接口
锁
分布式锁
redis实现
zookeeper实现
本地锁
synchronize关键字
在JDK1.5引入的锁升级过程,1.6引入偏向锁和轻量级锁。只能升级不能降级 无锁 | | 原持有偏向锁的线程处于未活动状态(锁撤销) | 偏向锁 | | 原持有偏向锁的线程处于活动状态(锁撤销) | 轻量级锁 | | 自旋失败(锁膨胀) | 重量级锁
Lock接口
ReentrantLock类
1.实现公平锁和非公平锁两种方式。可以在构造函数指定。默认非公平锁 1.可重入锁
ReadWriteLock接口
ReentrantReadWritLock
也实现公平和非公平锁 写是独享锁 读是共享锁 读写锁的性能都会比排他锁要好,因为大多数场景读是多于写的。在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量。Java并发包提供读写锁的实现是ReentrantReadWriteLock。 特性 说明 公平性选择 支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平 重进入 该锁支持重进入,以读写线程为例:读线程在获取了读锁之后,能够再次获取读锁。而写线程在获取 了写锁之后能够再次获取写锁,同时也可以获取读锁 锁降级 遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁
分类
公平锁和非公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁。 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。 对于Java ReetrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。 对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。
乐观锁/悲观锁
乐观锁与悲观锁并不是特指某两种类型的锁,是人们定义出来的概念或思想,主要是指看待并发同步的角度。 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS(Compare and Swap 比较并交换)实现的。 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。比如Java里面的同步原语synchronized关键字的实现就是悲观锁。 悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。 悲观锁在Java中的使用,就是利用各种锁。 乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。
独享锁/共享锁
独享锁是指该锁一次只能被一个线程所持有。 共享锁是指该锁可被多个线程所持有。 对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。 读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。 独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。 对于Synchronized而言,当然是独享锁。
互斥锁/读写锁
上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。 互斥锁在Java中的具体实现就是ReentrantLock。 读写锁在Java中的具体实现就是ReadWriteLock。
可重入锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。 对于Java ReetrantLock而言,从名字就可以看出是一个重入锁,其名字是Re entrant Lock 重新进入锁。 对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。
分段锁
分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。 我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7和JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。 当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在哪一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。 但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。 分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。
自旋锁
在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
偏向锁/轻量级锁/重量级锁
这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。 偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。 轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。 重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让他申请的线程进入阻塞,性能降低。