堆内存是唯一的,栈内存是每个线程独有的,每个线程都有自己的堆内存,共享数据是在堆里面,将共享数据临时存储到线程中,叫做变量副本,每次使用都会优先从变量副本获取.理解:线程获取数据先复制一份,然后修改了然后就会重新覆盖共享数据;
String:
String定义的对象都在常量池里面
String常用的方法
方法名 | 说明 |
---|---|
public boolean equals(Obejct obj) | 比较字符串内容是否相同 |
public int length() | 获取字符串的长度,就是字符个数 |
public boolean contains(String s) | 判断是否包含传入的字符串 |
public String subString(int start) | 从start开始截取字符串 |
public boolean equalsignoreCase(String str) | 忽略大小写比较 |
public String toLowerCase() | 字符串变成小写 |
public String toUpperCase() | 字符串变成大写 |
public String[] split(String str) | 分割字符串 |
public char[] toCharArray() | String变成Char数组 |
public int indexof(String) | 字符串首次出现的位置 |
concat(String) | 字符串连接 |
replace(String,int) | 替换字符串 |
trm() | 去掉多余的空格 |
String和StringBuilder的区别:
String是一个不可改变的字符序列:
String string = "ads";
string = "sda"; //这一步只是记录了一个新的对象地址,就是new了一个String,他的底 层是一个final修饰的字符数组
StringBuilder是一个可以改变的字符序列
StringBuilder stringBuilder = new StringBuilder("告诉你:");
stringBuilder.append("这是一个字符串");
stringBuilder.delete(0, 2);
System.out.println(stringBuilder);
字符串拼接:
StringBuilder和StringBuffer的区别
同一个操作作用于不同的对象,可以有不同的解释,产生不同的执行结果.
优点:可以解耦,灵活应对复杂情况,
方法名 | 说明 |
---|---|
.add | 添加数据 |
.remove | 删除数据 |
.removeif | 根据条件删除(使用lambda的表达式) |
.clear() | 清除数据 |
.contains() | 判断集合中是否有存在的元素 |
.isEmpty() | 是否为空 |
Iterator迭代器
terator iterator():返回i集合中的迭代器对象,该迭代器对象默认指向当前集合0索引处.
Iterator<Object> iterator = objects.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
Iterator常用方法:
特有方法:add remove set(替换) get
创建安全的List:
List<Object> objects = Collections.synchronizedList(new ArrayList<>());
使用for循环遍历删除的话,连续的会只删掉一个 原因:因为删除一个元素之后后面的会往前移动,然后导致指针直接跳过 解决方法:使用 i-- 删除的话可以使用iterator 的remove方法
CopyOnWriteArrayList:原理:(写时复制技术),读的时候可以并发读,写的时候复制一份原来的然后写,只能有一个线程写,独立写,最后合并覆盖
特有方法
方法名 | 说明 |
---|---|
void addLast(E e) | 将指定元素追加到末尾 |
void addFirst(E e) | 将指定元素插入到表头 |
E getFirst() | 返回链表第一个元素 |
E getLast() | 返回最后一个元素 |
E removeFirst() | 删除并返回第一个元素 |
E removeLast() | 删除并返回在最后一个元素 |
遍历方式:
新特性遍历:
//新特性遍历java8遍历
objects.forEach(s -> System.out.println(s));
objects.forEach(System.out::println);
如果没有实现HashCode就不会去除重复的不一样,没有索引,不能通过索引增删改查,可以使用迭代器,增强for循环
HashSet 他的底层是哈希表结构,实际是一个HashMap的实例,线程不安全,(数据+链表),不能保证存取顺序一致,没有索引
**TreeSet *它的底层是红黑树组成的,线程不安全 ,可以将元素按照规则进行排序, Comparable要实现这个接口,不然添加的时候会报错!返回 1 表示存右边,返回-1表示存左边
//创建线程安全的TreeSet
Collections.synchronizedList(new TreeSet<>());
CopyOnWriteSet:线程安全,写时复制技术,线程安全
键值对形式,键只能有一个,不能重复,值可以重复,
方法 | 说明 |
---|---|
String put(String,String) | 向map添加数据,如果键存在会覆盖原先的值,把原先的值返回值返回 |
String remove(Object) | 根据键删除键值对 |
void clear() | 清楚所有元素 |
boolean containsKey(Object ) | 判断集合是否包含指定的键 |
boolean containsValue(Object) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
Set keySet() | 获取所有的键的集合 |
V get(Object key) | 根据键获取值 |
Set |
获取所有键值对的集合 |
K getKey() | 获得键 |
V getValue | 获得值 |
HashMap: 是JDK1.2出现的,允许存储null键和null值,线程不安全,如果键要存储自定义对象,就要重写hashCode和equals方法,防止键重复,Hash Map是支持fail -fast(如果当前线程正在遍历,其他线程对此做了修改,就会报错)
HashTable: 是jdk1.0出现的,线程安全,不允许存储null键和null值,他会锁上整个表
TreeMap : 和TreeSet一样都是红黑树结构,底层用Entrye对象,只根据键来排序,必须要进行排序,不然插入会报错,和值没有关系
**ConcurrentHashMap:**线程安全的,它只会锁上数组所在的链表,第一次添加元素创建hash表;
Map的遍历:
字节流:
类名 | 说明 |
---|---|
OutPutStream | 输出流 |
InputStream | 输入流 |
ZipOutPutStream | ZIp压缩流 |
字符流:
类名 | 说明 |
---|---|
Reader | 字符读取流 |
Writer | 字符输出流 |
转换流:
类名 | 说明 |
---|---|
InputStreamReader | 输入转换流 |
OutputStreamWriter | 输出转换流 |
BIO—同步并阻塞的IO
AIO—处理高并发流,适合连接数较多且连接时间长的应用
NIO—同步不阻塞的IO;一般用于连接数较大,连接时间短的应用,如聊天
NIO三要素:
capacity:容量(长度)
limit:界限(最多能读/写到哪)
posotion:位置(读/写哪个索引)
主要方法:
方法名 | 说明 |
---|---|
flip() | 写变读模式,将postition指针移到最前面,limit移到后面 |
get() | 读取一个字节 |
get(byte[ ]dst) | 读取多个字节 |
rewind() | 将position设置围为0可以重复读 |
clear() | 数据读写完毕(读变成写) |
array() | 将缓冲区的转换成字节数组返回 |
底层是一个Map集合,不可以指定泛型,键不能重复,值可以,相同的话会被覆盖,和map方法一样
Properties和IO流结合的方法
方法名 | 说明 |
---|---|
void load (InputStream inStream) | 从输入字节流读取属性列表(键和元素对) |
void load(Reader reader) | 从输入字符流读取属性列表(键和元素对) |
void store(OutputStream out,String comments) | 将此属性列表(键和元素对)写入Properties表中,以结合于使用load(InputStream)方法的格式写入输出字节流(将集合写到文件) |
void store(Writer writer,String comments) | 将此属性列表(键和元素对)写入此Properties表中,以适合使用load(Reader)方法的格式写入输出字符流(将集合写到文件)) |
示范代码:
public class PropertiesDemo {
public static void main(String[] args) {
Properties properties = new Properties();
try {
FileReader reader = new FileReader("src/main/java/JAVA基础/IO流/test.properties");
properties.load(reader);
reader.close();
System.out.println(properties.getProperty("name"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
==tips:==用反射调创建对象,用反射调用成员变量,用凡是调用成员方法,利用反射的时候调用它类的属性和方法时,无视修饰符
Class文件有2个阶段
源代码阶段就是:java文件编译成Class文件(字节码文件)还在硬盘里,这时候就要通过调用加载器的ClassName()方法加载类,类加载器然后就将此class文件加载进内存,会创建class对象
Class<?> aClass = Class.forName("JAVA基础.反射.StudentFS");
Class<StudentFS> studentFSClass = StudentFS.class;
StudentFS studentFS = new StudentFS();
Class<? extends StudentFS> aClass = studentFS.getClass();
示例代码:
Properties properties = new Properties();
InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("test.properties");
InputStreamReader inputStreamReader = new InputStreamReader(resourceAsStream, "UTF-8");
try {
properties.load(inputStreamReader);
String classname = properties.getProperty("classname");
Class<?> aClass = Class.forName(classname);
//获取构造器
Constructor<?> constructor = aClass.getConstructor();
//获取带参构造器
Constructor<?> constructor1 = aClass.getConstructor(String.class, int.class);
System.out.println("constructor1"+constructor1);
//用构造器获取一个对象
Object o = constructor.newInstance();
//获取所有公共的构造方法
Constructor<?>[] constructors = aClass.getConstructors();
for (int i = 0; i < constructors.length; i++) {
System.out.println(constructors[i]);
}
//获取所有的构造方法,私有的也可以
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
for (Constructor de : declaredConstructors
) {
System.out.println(de);
}
//获取方法
Method method = aClass.getMethod(properties.getProperty("method"));
//调用方法
method.invoke(o);
//获取变量
//aClass.getField(); 这个方法只能获取公共的变量!!
Field name = aClass.getDeclaredField("name");
System.out.println(name);
//设置可访问的
name.setAccessible(true);
name.set(o, "lzy");
System.out.println(o.getClass().getMethod("getName").invoke(o));
} catch (Exception e) {
e.printStackTrace();
}
进程:是程序的一个实例,是系统分配资源的基本单位;
线程:线程是进程任务调度和执行的基本单位
**并发:**同一时刻多个线程访问同一资源,多线程对一个点
**并行:**多项工作一起执行,然后汇总
管程: Monitor监视器,同步的机制,就是锁的意思,JVM同步基于进入和退出,使用管程对象实现
用户线程:用户自定义的线程,没有运行完JVM不会结束
守护线程:守护在后台,比如垃圾回收,如果JVM都是守护线程JVM就会结束
继承Thread类
实现Runnable接口
线程池
概念:线程池就是一个维护线程的池子,里面很多线程;线程过多会带来调度开销,进而影响缓存局部性和整体性能.而线程池可以充分利用内核,防止过分调度,有点降低资源消耗,提高线程可管理性
线程池架构:Java中的线程池是Executor框架实现的
使用的几种方式:
一池N线程:FixedThreadExecutor()特点:如果达到容量上限,就会在队列等待;
一池一线程:SingleThreadExecutor()
根据需求创建线程池,可扩容,遇强则强:CachedThreadExecutor,最大是int的最大值,默认60秒
自定义线程池:
public ThreadPoolExecutor( int corePoolSize, //核心线程数量,常驻线程数量,就是固定的数量
int maximumPoolSize, //最大线程数量
long keepAliveTime, //保持存活时间
TimeUnit unit, //存活单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂
RejectedExecutionHandler handler) //拒绝策略,多种拒绝策略
原理:底层都是new ThreadPoolExecutor(),new 线程池对象的时候不会创建线程,只有在execute()的时候才会创建,常驻线程为2,阻塞队列为3的情况下,第三个-第5个会在队列等候,6-8个会开启新线程执行,并不会队列没满就开启新线程,如果线程满了,队列满了,就会执行拒绝策略;
拒绝策略:
实现Callable接口 会有返回值,使用FutureTask()接口
可以用来创建线程,可以得到返回值,类似于Runnerable, 有一个 **call()**方法,而不是run()方法,没有返回值就会抛出异常;
实现一个Callable的接口,要是使用FutureTask,使用new Thread不太行
FutureTask():
FutureTask的方法: get()方法,获取线程的返回值,还有isDone() 任务是否完成
原理:他就是一个Runnable实现类
==Tips:==start()的执行,可能不会马上创建线程,由操作系统决定
说明:是java的关键字,一种同步锁,可以修饰代码块,一个方法,上锁和解锁是自动完成的
锁对象:对于普通方法,锁是当前实例对象(this),对于静态同步方法.,锁的是当前类的.Class对象,同步代码块就是锁的括号里面对象
同步代码块执行流程;
- 线程获得锁
- 清空自己线程的所有变量副本
- 拷贝共享变量最新的值到变量副本中
- 执行代码
- 将修改的变量副本中的值赋给 堆 里面的共享数据
- 释放锁
Synchronized底层原理:
JDK早期的时候,底层实现是重量级的,重量级到这个synchronized要到操作系统去申请锁,效率非常低,
后来有了锁升级概念: 当只有一个线程的时候,只是记录在这个线程的ID(偏向锁),如果偏向锁有竞争的话**,就会升级为自旋锁**,然后自旋转锁10圈左右变成重量级锁
使用这个可以实现手动解锁,上锁,syn发生异常时会自动释放锁,Lock是可重入锁,与Synchronized比较,Lock可以获得更广泛的锁操作,功能更强大,Lock不是java内置的,Lock是一个类,通过这类可以实现同步访问
private final ReentrantLock lock = new ReentrantLock();
实现卖票:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TicketsLock {
private int number;
private String ticketName;
private final ReentrantLock lock = new ReentrantLock();
public void sale() {
//判断是否还有票
lock.lock();
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + ":卖出" + (number--) + "剩下" + number);
}
} finally {
lock.unlock();
}
}
}
Condition: lock.newCondition()(详情参见线程间的通信)
问题:使用if会出现虚假唤醒(从哪里睡就会从哪里醒来,使用if的话不会进行判断,要使用while)
public class Share {
//初始值
private int number = 0;
public synchronized void incr() throws InterruptedException {
if (number != 0) {
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "::" + number);
this.notifyAll();
}
public synchronized void decr() throws InterruptedException {
if (number != 1) {
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "::" + number);
this.notifyAll();
}
}
Condition的方法:
方法名 | 说明 |
---|---|
void await() | 当前线程等待 |
void signal() | 唤醒一个线程 |
void singalAll() | 唤醒所有线程 |
lock原理:???
底层:是通过调用LockSupport.park()和LockSupport.unpark()来实现阻塞和唤醒,实际上调用的是Unsafe类的底层操作.
使用: 直接使用LockSupport.park()没有加锁的限制,不用在同步代码块里
LockSupport可以唤醒具体线程
Thread t=new Thread({
LockSupport.park()
}).start();
//唤醒指定线程
LockSupport.unpark(t)
强制线程每次在使用的时候,都会看一下共享区域的最新值;他不能保持Volatile的原子性
作用:
一次和多次操作中,要么所有的操作全部得到了执行,并且不会受到任何因素的干扰而中断,要么所有操作都不执行,多个操作是一个不可分割的整体
==tips:==使用synchronized锁解决原子性,但是效率比较慢,可以使用AtomicInteger()(更新一个整数还有其他的类型)
方法名 | 说明 |
---|---|
int get() | 获取值 |
int getAndIncrement() | 以原子方式先获取再增加+1 |
int IncrementAndtget() | 以原子方式先增加1再返回值 |
int addAndGet() | 以原子方式,加上传入的值,再返回结果 |
int GetAndadd() | 以原子方式,返回原来的值,再相加 |
理解:2个线程 读取堆里面的数据,然后复制到变量副本,并且保存读取的值,修改完之后重新赋值给堆内存,然后比较旧值和现在堆的数据是不是一样的,是一样的我就修改,不一样的就重新读取再修改,这一步也称自旋
Volatile中CAS算法源码解析:
实际上也是赋值给了一个Volatile修饰的int类型变量,最底层还是调用了本地方法(就是cpu的方法native修饰),本质上是使用了cpu 的一个叫做 高速缓存 一致性协议(MESI)
悲观锁和乐观锁:synchronized 是一种悲观锁,读写只能自己操作,乐观锁,别人可以看我,改的话要和原来的值比较
注意:并不CAS效率就比系统锁要高,要区分实际情况:
// 没有参数的话默认就是非公平锁,或造成线程被饿死.
ReentrantLock lock =new ReentrantLock()
//公平锁,写上TRUE
ReentrantLock lock =new ReentrantLock(true)
CountDownLatch类:构造方法可以设置一个计数器,然后使用countDown()方法来进行减1的操作,使用await()方法等待,计数器等于0,然后继续执行await方法之后的语句
理解:班长关门,所有同学出去了才能锁门
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "离开了");
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
countDownLatch.await(); //这里
System.out.println("班长锁门了");
}
}
----结果-----
1离开了
4离开了
5离开了
0离开了
3离开了
2离开了
班长锁门了
Process finished with exit code 0
CyclicBarrier类:循环栅栏,就是循环阻塞,阻塞的线程到了一定数量,就会完成,鼓噪方法和await方法
理解方法:集齐 7颗龙珠
代码:
//集齐7颗龙珠召唤神龙
public class CyclicBarrierDemo {
private static final int NUMBER = 7;
public static void main(String[] args) {
//创建CyclicBarrier
CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () -> {
System.out.println("集齐7颗龙珠召唤神龙");
});
//集齐7颗龙珠过程
for (int i = 0; i < 7; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "♥龙珠");
try {
cyclicBarrier.await(); //注意这里
} catch (Exception e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
Semaphore类 :他是一个计数信号量,acquire()获取许可,release()释放一个许可
理解:6辆车停3个停车位
代码:
//6台车3个停车位
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到了车位");
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println(Thread.currentThread().getName() + "--------离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}
Exchanger类:
描述:Exchanger是2个线程之间进行交换数据
创建方法:
Exchanger<String> exchanger = new Exchanger();
使用方法:
new Thread(()->{
String s="t1";
try {
System.out.println(Thread.currentThread().getName()+":S="+s);
s = exchanger.exchange(s);
System.out.println(Thread.currentThread().getName()+":S="+s);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
new Thread(()->{
String s="t2";
try {
System.out.println(Thread.currentThread().getName()+":S="+s);
s = exchanger.exchange(s);
System.out.println(Thread.currentThread().getName()+":S="+s);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t2").start();
使用这个类我们不要关心什么时候阻塞线程,什么时候唤醒线程
常见的队列 | 说明 |
---|---|
ArrayBlockingQueue(常用) | 基于数组实现的,长度固定的 |
LinkedBlockingQueue(常用) | 基于链表的阻塞队列 |
DelayQueue | 使用优先级队列实现的延迟无界队列,只有延迟的时间到了才能获取元素 |
PriorityBlockingQueue | 支持优先级排序的无界阻塞队列 |
SynchronousQueue | 不存储元素的队列,单个元素的队列 |
LinkedTransfeQueue | 由链表组成的无界阻塞队列 |
LinkedBlockingDeque | 由链表组成的双向队列 |
阻塞队列的方法
方法类型 | 无法完成就抛出异常 | 特殊值 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | take() | poll(time,unit) |
检查 | element() | peek() | 不可用 | 不可用 |
实现了Future接口,有返回值 ,mp消息队列都是异步回调
同步与异步回调
public static void main(String[] args) throws ExecutionException, InterruptedException {
//同步调用
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + " CompletableFuture.runAsync");
});
completableFuture.get();
//异步调用
CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " CompletableFuture.supplyAsync");
return 1024;
});
//完成之后 t是返回结果,如果有异常,u就是异常信息
completableFuture1.whenComplete((t, u) -> {
System.out.println(u);
System.out.println(t);
}).get();
}
主机:
网络:
二叉查找树 :(二叉排序树,二叉搜索树) 左边节点小于自己,右边节点大于自己
平衡二叉树: 二叉树左右2个子树的高度不超过1 任意节点的左右俩个子树都是一颗平衡二叉树,左旋就是根节点向左移动,右旋 左侧结点太多就要像右边移动
红黑树:也叫平衡二叉B树,根节点必须位黑色,每一个节点是黑色或者红色,如果一个节点没有子结点或者父节点那必须是黑色,不能出现2个红色节点相连,对每一个节点,从该节点到期所有后代叶节点的简单路径上,均包含相同数目的黑色节点
红黑树规则:
- 节点必须是红色或者黑色
- 根节点必须是黑色
- 叶子节点是黑的空节点
- 不能有2个连续的红色节点
- 从任意节点到每个叶子节点的所有路径都包含相同数目的黑色节点