Open GenweiWu opened 3 years ago
HashMap 非同步,非线程安全,性能好,允许null
HashMap | HashTable | |
---|---|---|
同步 | 非同步 | 同步(sychronized),线程安全 |
线程安全 | 非线程安全,另:java5提供了concurrentHashMap是线程安全的 | 线程安全 |
性能 | 性能好些 | 差一些 |
null | 可以接受null的键值(key)和值(value) | key和value都不能是null |
public class HashTableDemo
{
/**
* HashMap的key和value都可以是null
*/
@Test
public void test()
{
HashMap<String, String> hashmap = new HashMap<>();
hashmap.put(null, "xxx");
hashmap.put("yyy", null);
//{null=xxx, yyy=null}
System.out.println(hashmap);
}
/**
* Hashtable的key和value都不能是null
*/
@Test
public void test2()
{
Hashtable<String, String> hashtable = new Hashtable<>();
//java.lang.NullPointerException
hashtable.put(null, "xxx");
//java.lang.NullPointerException
hashtable.put("yyy", null);
}
}
jdk1.7 数组+链表(头部插入) jdk1.8 数组+链表(尾部插入)+红黑树
平衡的二叉搜索树
iterator
vs listIterator
英[ɪtə'reɪtə]
iterator
listIterator
package com.njust.test.kemu2;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import org.junit.Test;
/**
*
*/
public class IteratorDemo
{
/***
* Arrays.asList对应类型,不能是基本数据类型
*/
@Test
public void iterator00()
{
//此时list的大小为1
int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
List<int[]> list = Arrays.asList(array);
System.out.println(list);
Integer[] array222 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
List<Integer> list222 = Arrays.asList(array222);
//[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
System.out.println(list222);
}
/**
* iterator的主要方法:hasNext(),next(),remove()
*/
@Test
public void iterator01()
{
final String[] array = {"11", "22", "33", "44", "55"};
List<String> stringList = new ArrayList<>(Arrays.asList(array));
Iterator<String> iterator = stringList.iterator();
while (iterator.hasNext())
{
String next = iterator.next();
if (next.equals("44"))
{
iterator.remove();
}
}
//[11, 22, 33, 55]
System.out.println(stringList);
}
/**
* listIterator除了hasNext(),next()和remove()外,还有:
* - set方法用于修改元素
*/
@Test
public void listIterator01()
{
final String[] array = {"11", "22", "33", "44", "55"};
List<String> stringList = new ArrayList<>(Arrays.asList(array));
ListIterator<String> listIterator = stringList.listIterator();
while (listIterator.hasNext())
{
String next = listIterator.next();
if (next.equals("33"))
{
listIterator.set("33New");
}
}
//[11, 22, 33New, 44, 55]
System.out.println(stringList);
}
/**
* 接着上面:
* - set方法用于修改元素
* - 可以反向遍历
*/
@Test
public void listIterator02()
{
final String[] array = {"11", "22", "33", "44", "55"};
List<String> stringList = new ArrayList<>(Arrays.asList(array));
//获取指定位置的迭代器
ListIterator<String> listIterator = stringList.listIterator(stringList.size() - 1);
while (listIterator.hasPrevious())
{
//迭代到前一个元素
String previous = listIterator.previous();
if (previous.equals("33"))
{
//可以修改元素
listIterator.set("33_new");
}
}
//[11, 22, 33_new, 44, 55]
System.out.println(stringList);
}
/**
* - 可以add添加元素
*/
@Test
public void listIterator03_a()
{
final String[] array = {"11", "22", "33", "44", "55"};
List<String> stringList = new ArrayList<>(Arrays.asList(array));
//获取指定位置的迭代器
ListIterator<String> listIterator = stringList.listIterator(stringList.size() - 1);
while (listIterator.hasPrevious())
{
//迭代到前一个元素
String previous = listIterator.previous();
if (previous.equals("33"))
{
//可以修改元素
listIterator.add("33_prev");
}
}
//[11, 22, 33_prev, 33, 44, 55]
System.out.println(stringList);
}
/**
* - 可以add添加元素
*/
@Test
public void listIterator03_b()
{
final String[] array = {"11", "22", "33", "44", "55"};
List<String> stringList = new ArrayList<>(Arrays.asList(array));
//获取指定位置的迭代器
ListIterator<String> listIterator = stringList.listIterator();
while (listIterator.hasNext())
{
//迭代到前一个元素
String next = listIterator.next();
if (next.equals("33"))
{
//可以修改元素
listIterator.add("33_next");
}
}
//[11, 22, 33, 33_next, 44, 55]
System.out.println(stringList);
}
}
/**
* set只能用Iterator不能用ListIterator
*/
@Test
public void test04()
{
Set<String> set = new HashSet<>();
set.add("111");
set.add("222");
set.add("333");
//set使用iterator迭代
Iterator<String> iterator = set.iterator();
while (iterator.hasNext())
{
String next = iterator.next();
System.out.println("-->" + next);
}
List<String> list = new ArrayList<>(set);
//list使用listIterator迭代
ListIterator<String> listIterator = list.listIterator();
while (listIterator.hasNext())
{
String previous = listIterator.next();
System.out.println("next :" + previous);
}
//list使用listIterator倒序迭代
ListIterator<String> listIterator222 = list.listIterator(list.size());
while (listIterator222.hasPrevious())
{
String previous = listIterator222.previous();
System.out.println("previous :" + previous);
}
}
吃饭吃一半,来电话了,你吃完饭再去接电话,是既不并行也不并发
并行:你吃饭吃一半,来电话了,一边打电话一边吃饭,同时做多件事,这叫做并行
并发:你吃饭吃一半,来电话了,去接电话,再继续吃饭,就是并发
并行是同时做,并发是一段时间内一起做(不是同时做,一般是交替进行)
总线程数<= CPU数量:并行运行
总线程数> CPU数量:并发运行
https://blog.csdn.net/mxsgoden/article/details/8821936
进程有独立的内存单元,而进程下的多个线程共享内存
- 一个进程挂掉不会影响其他的进程,而一个线程挂掉会导致对应的整个进程挂掉,所以多进程更稳定
- 进程间切换代价大,线程的切换代价小
java中有两种线程,用户线程(User Thread)和守护线程(Daemon Thread)
守护线程是为其他线程服务的线程
所有非守护线程退出后,jvm
就会退出
由于jvm
退出的时候不会等待守护线程退出,所以守护线程不要去持有文件或资源,因为无法释放
package com.njust.test.thread;
public class DaemonThreadDemo
{
public static void main(String[] args)
{
//test01();
test02();
}
/**
* 守护线程会直接退出,jvm不会等待守护线程执行完
*/
private static void test02()
{
Thread userThread = new Thread(() -> {
for (int i = 0; i < 5; i++)
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("UserThread:" + i);
}
});
Thread daemonThread = new Thread(() -> {
for (int i = 0; i < 50; i++)
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("DaemonThread:" + i);
}
});
daemonThread.setDaemon(true);
//启动测试
daemonThread.start();
userThread.start();
// console:
// DaemonThread:0
// UserThread:0
// DaemonThread:1
// UserThread:1
// UserThread:2
// DaemonThread:2
// DaemonThread:3
// UserThread:3
// UserThread:4
// DaemonThread:4
}
/**
* - 通过setDaemon(true)来设置守护线程
* - 需要在start之前设置
* - jvm不会等待守护线程执行完,就直接退出,所以没打印daemon run
*/
private static void test01()
{
Thread thread = new Thread()
{
@Override
public void run()
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("daemon run");
}
};
thread.setDaemon(true);
thread.start();
System.out.println(thread.isDaemon());
System.out.println("main run");
// true
// main run
}
}
/*
* 创建线程的几种方法
*
* */
public class ThreadDemo
{
/**
* 方法1:继承thread类,复写run方法
*/
@Test
public void test01()
{
Thread thread = new Thread()
{
@Override
public void run()
{
System.out.println("thread:" + Thread.currentThread().getName());
}
};
thread.start();
//thread:Thread-0
}
/**
* 方法2:实现Runnable接口的run方法,然后new Thread(Runnable)创建线程
*/
@Test
public void test02()
{
Thread thread = new Thread(new Runnable()
{
@Override
public void run()
{
System.out.println("Runnable:" + Thread.currentThread().getName());
}
});
thread.start();
//Runnable:Thread-0
}
@Test
public void test03()
{
Callable<String> callable = new Callable<String>()
{
@Override
public String call()
throws InterruptedException
{
System.out.println("--->");
Thread.sleep(1000);
System.out.println("<---");
return new Date().toString();
}
};
FutureTask<String> futureTask = new FutureTask<>(callable);
System.out.println("ready:");
new Thread(futureTask).start();
System.out.println("start:");
try
{
String result = futureTask.get();
System.out.println("result:" + result);
}
catch (InterruptedException | ExecutionException e)
{
e.printStackTrace();
}
System.out.println("finish");
// ready:
// start:
// --->
// <---
// result:Thu Mar 18 14:15:09 CST 2021
// finish
}
}
package com.njust.test.learn;
/**
* 实现线程有两种方法
* 1、方法1是继承Thread类覆盖run方法
* 2、方法2是new Thread(Runnable)的方式
*/
public class ThreadDemo
{
public static void main(String[] args)
{
//3、这个线程的实际输出对应的是thread,原因可以去看下Thread的run方法的实现
//
// @Override
// public void run() {
// if (target != null) {
// target.run();
// }
// }
new Thread(new Runnable()
{
@Override
public void run()
{
System.out.println("runnable: " + Thread.currentThread().getName());
}
})
{
@Override
public void run()
{
System.out.println("thread: " + Thread.currentThread().getName());
}
}.start();
}
}
调用wait的线程会进入等待区,释放锁
调用notify会唤醒等待区的线程,notify会唤醒一个,notifyAll会唤醒所有
WaitNotifyDemo.java
WaitNotifyDemo222.java
WaitNotifyDemo333.java
synchronize
是java关键字,而lock
是类synchronize
是自动获取锁和释放锁lock.lock()
lock.unlock()
tryLock
来知道是否能获取到锁tryLock()
如果可以获取到锁,则返回true
如果无法获取到锁,则直接返回false,而不会阻塞住
lock
可以用tryLock(超时时间)
来实现获取不到锁就放弃,而synchronize
则会一直阻塞下去// boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
lock.tryLock(3, TimeUnit.SECONDS);
try
{
lock.lockInterruptibly();
//在等待获取锁的过程中可被中断
try
{
//TODO
}
finally
{
lock.unlock();
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
ReentrantLock
是可重入锁,非公平锁(默认非公平锁,可自定义),Synchronize也是可重入锁,非公平锁(无法修改)ReentrantLock
可以有多个condition例如为了实现三个线程按顺序打印12345的效果,就可以使用3个condition
//例如看看当前是否是公平锁
public final boolean isFair() {
java的反射是指在运行期可以获得一个对象所属信息(包括对象对应的类,对象的构造函数,对象的方法,对象的属性)
将内存中对象的状态(属性,不包括方法)保存起来,而且可以反向将保存的对象还原到内存;
代理对象和目标对象要实现一样的接口
缺点:
使用jdk api动态创建代理对象,又称为jdk代理
或接口代理
特点:
要求: 目标类要实现接口
cglib是一个第三方代码生成类库 动态生成一个子类来继承目标类,从而实现对目标类的功能扩展
要求:
动态创建一个代理类,给实现了某个接口的目标类,加上一些额外操作,比如日志和事务管理
想对一个对象进行修改,又想保留之前对象的数据,就要用到克隆
实现克隆需要:
* 1. 实现cloneable接口
* 2. override方法clone
public class CloneDemo
{
/**
* 实现克隆需要:
* 1. 实现cloneable接口
* 2. override方法clone
* @throws CloneNotSupportedException
*/
@Test
public void test()
throws CloneNotSupportedException
{
Book book = new Book(1);
Book book2 = (Book)book.clone();
System.out.println(book.equals(book2));
}
static class Book implements Cloneable
{
private int price;
public Book(int price)
{
this.price = price;
}
@Override
protected Object clone()
throws CloneNotSupportedException
{
return super.clone();
}
@Override
public boolean equals(Object o)
{
if (this == o)
{
return true;
}
if (o == null || getClass() != o.getClass())
{
return false;
}
Book book = (Book)o;
return price == book.price;
}
@Override
public int hashCode()
{
return Objects.hash(price);
}
}
}
利用 JSON.parse
+ JSON.stringify
可以实现深拷贝(不包括方法)
利用java序列化
+反序列化
也可以实现深拷贝
session
和 cookie
有什么区别服务器端保存的散列文件,类似了一个map,键就是sessionId
底层实现
性能方面
用户输入恶意的sql,来执行自己的sql语句,进而达到控制、破坏系统的目的
即跨站脚本攻击
用户向有漏洞的网站输入html代码,导致浏览的时候页面上会执行恶意html代码
比如姓名写成
即跨站请求伪造
用户访问A网站的时候,构造一个请求B网站的请求+用户刚好登陆了B网站,就可能导致B请求成功 比如用来盗号和转账等
public class TryTest
{
public static void main(String[] args)
{
//1. try catch
try
{
System.out.println("try catch");
}
catch (Exception e)
{
e.printStackTrace();
}
//2. try finally
try
{
System.out.println("try finally");
}
finally
{
System.out.println("close resource...");
}
//3.try catch finally
try
{
System.out.println("try catch finally");
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
System.out.println("finally");
}
}
}
public class FinallyTest
{
@Test
public void test01()
{
int expect = 30;
int actual = doTest01();
Assert.assertEquals(expect, actual);
}
private int doTest01()
{
int num = 10;
try
{
System.out.println(num / 0);
//这里执行不到
num = 20;
}
catch (Exception e)
{
num = 30;
return num;
//这里实际的语句是return 30,但是要去执行finally中的代码
}
finally
{
num = 40;
}
return num;
}
@Test
public void test02()
{
int expect = 40;
int actual = doTest02();
Assert.assertEquals(expect, actual);
}
private int doTest02()
{
int num = 10;
try
{
System.out.println(num / 0);
//这里执行不到
num = 20;
}
catch (Exception e)
{
num = 30;
return num;
//这里实际的语句是return 30,但是要去执行finally中的代码
}
finally
{
num = 40;
return num;
//这里重新生成返回路径,由于只能有一个return,所以这里直接返回40了
}
}
}
OSI七层模型 | TCP/IP 概念5层模型 | 常见协议 |
---|---|---|
应用层 | 1.应用层 | HTTP,FTP,DNS,telnet |
表示层 | 1.应用层 | --- |
会话层 | 1.应用层 | --- |
传输层 | 2.传输层 | TCP ,UDP |
网络层 | 3.网络层 | IP,ICMP等 |
数据链路层 | 4.链路层 | ARP,RARP等 |
物理层 | 4.链路层 | 等 |
UDP | TCP | |
---|---|---|
面向连接 | 无连接 | 面向连接 (建立连接3次握手,断开连接4次握手) |
是否可靠 | 不可靠传输 | 可靠传输,有流量控制和拥塞控制 |
连接数 | 多对多 | 一对一 |
传输方式 | 面向报文,来一个发一个 | 面向字节流(可以攒够长度再发送) |
首部开销 | 开销小,8字节 | 开销大,20~60字节 |
适用场景 | 视频直播 | 可靠的文件传输 |
3次握手而不是两次握手的原因是:1和2让客户端A知道,他可以发送到服务端B,2和3是让B知道B可以发送到A,如果只有1和2,那B是不清楚自己发送给A的消息是否成功了的;
基于3次握手的基础上继续理解,由于1和2是A告诉B自己不再发送了,所以达到目的:B不再继续接受数据
但是此时B还可以向A发送数据,而且还可能正在传输,所以等B传输完成,要发送3到A表示自己发送完了;A再发送4表示收到,于是B收到后就关闭了自己
GET请求 | POST请求 | |
---|---|---|
1.多次请求 | 反复请求是无害的 | 导致二次提交,可能改变系统状态 |
2.浏览器书签 | get请求的url可以被浏览器保存成书签 | post不可以 |
3.缓存 | get请求会被浏览器默认缓存cache | post默认不缓存,除非手动修改 |
4.参数传递 | get请求的参数通过url传递 | post可以通过requestBody |
4.参数传递 | get请求的参数暴露在url上不安全 | |
5.编码方式 | get请求只能url编码 | post请求有多种编码方式 |
6.长度 | get请求参数长度有限制 | post没有长度限制 |
封装: 可以把类的属性设置为私有,并get和set方法进行访问控制
继承: 子类继承父类,可以共用父类方法,也可以选择复写方法,还能再加入新的子类方法
多态: 父类的引用变量可以指向子类的对象,调用方法或者访问变量的时候,访问的实际上是子类的属性;就是说父类的变量,可以在运行时动态指向父类或者子类对象
BIO
、NIO
、AIO
BIO
(Blocking IO) 同步阻塞IOsocket.accept()、socket.read()、socket.write()三个主要函数都是同步阻塞的
NIO
(Non-blocking IO) 同步非阻塞IOinterface ChannelHandler{
void channelReadable(java.nio.channels.Channel channel);
void channelWritable(java.nio.channels.Channel channel);
}
class Channel{
Socket socket;
Event event;//读,写或者连接
}
//IO线程主循环:
class IoThread extends Thread{
public void run(){
Channel channel;
while(channel= Selector.select()){//选择就绪的事件和对应的连接
if(channel.event==accept){
registerNewChannelHandler(channel);//如果是新连接,则注册一个新的读写处理器
}
if(channel.event==write){
getChannelHandler(channel).channelWritable(channel);//如果可以写,则执行写事件
}
if(channel.event==read){
getChannelHandler(channel).channelReadable(channel);//如果可以读,则执行读事件
}
}
}
Map<Channel,ChannelHandler> handlerMap;//所有channel的对应事件处理器
}
netty框架使用NIO
AIO
异步非阻塞IOStringBuilder
性能好,非线程安全StringBuffer
性能差,线程安全 //1.反转使用StringBuilder、StringBuffer的reverse方法
System.out.println(new StringBuilder("123").reverse());
System.out.println(new StringBuffer("abc").reverse());
//2.一个是length()方法,一个是length属性
System.out.println("123".length());
int[] arr = {1, 2, 3};
System.out.println(arr.length);
String str1 = "hello"; //str1指向静态区
String str2 = new String("hello"); //str2指向堆上的对象
String str3 = "hello";
String str4 = new String("hello");
System.out.println(str1.equals(str2)); //true
System.out.println(str2.equals(str4)); //true
System.out.println(str1 == str3); //true
System.out.println(str1 == str2); //false
System.out.println(str2 == str4); //false
System.out.println(str2 == "hello"); //false
str2 = str1;
System.out.println(str2 == "hello"); //true
大部分基本数据类型,包括 byte, short, int, long, char, boolean
(不包括float, double
)
比如常见的int常量池,范围是-128~127
public static void main(String[] args) {
Integer a = new Integer(3);
Integer b = 3; // 将3自动装箱成Integer类型
int c = 3;
System.out.println(a == b); // false 两个引用没有引用同一对象
System.out.println(a == c); // true a自动拆箱成int类型再和c比较
System.out.println(b == c); // true
Integer a1 = 128;
Integer b1 = 128;
System.out.println(a1 == b1); // false
Integer a2 = 127;
Integer b2 = 127;
System.out.println(a2 == b2); // true
}
java容器分为 Collection和Map两大类
Collection分为 Set,List,Queue接口
可以用`Collections.synchronizedList`代替
1. vector每次扩容为*2,而arrayList扩容是*1.5,比较浪费空间
2. vector的同步是用synchronized实现的,性能较差
synchronized实现的线程安全类
`class Stack<E> extends Vector<E>`
代替
Deque<Integer> deque = new LinkedList<>(); deque.push(1); deque.poll(); deque.pop();
推荐用ConcurrentHashMap来代替
ArrayList
、LinkedList
、Vector
谁速度较快ArrayList
和Vector
底层都是数组结构,可以随机访问,插入和删除要复制数组所以速度慢
LinkedList
底层是链表,无法随机访问,插入和删除快
但是Vector
是线程同步的,比ArrayList
慢
所以 LinkedList
> ArrayList
> Vector
Collections.synchronizedList(list)
ArrayList<String> list = new ArrayList<>(); list.add("aaa"); list.add("bbb"); list.add("ccc");
List
> 使用线程安全的 CopyOnWriteArrayList 代替线程不安全的 ArrayList。
```java
List<Object> list1 = new CopyOnWriteArrayList<Object>();
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
底层是HashMap,value是固定的PRESENT
底层调用HashMap#put方法,先检查hash是否一致,再检查equals方法是否相等
1)equals相等,则覆盖
2)不相等,就是hash冲突要用链表/红黑树了
重复的时候就相当于put覆盖key了
没有元素时,poll会返回null,而remove会抛异常NoSuchElementException
抛出异常 | 特殊值 | 阻塞 | 超时 | |
---|---|---|---|---|
插入 | add(e) |
offer(e) |
put(e) |
offer(e, time, unit) |
移除 | remove() |
**poll()** |
take() |
poll(time, unit) |
检查 | element() |
peek() |
不可用 | 不可用 |
1.7中 数组+链表
1.8中 数组+链表/红黑树(链表长度大于8时转换为树)
头插法在多线程时可能导致环形链表 https://www.cnblogs.com/youzhibing/p/13915116.html
## 在扩容transfer的时候,本来1->2,遍历的时候先处理1再处理2,头插法会导致链表变成2->1即链表的顺序被改变了
HashMap在jdk1.7中采用头插入法,在扩容时会改变链表中元素原本的顺序,以至于在并发场景下导致链表成环的问题。 而在jdk1.8中采用尾插入法,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。
### 2、为啥HashMap用String或Integer作为key
1. final类型,不可变类型,保证key的hash不变
2. 同时复写了equals和hashcode方法
### 3、ConcurrentHashMap 的同步实现
1.7中 分段锁
1.8中 不再使用segment,使用CAS+synchronized锁头结点
### 4、Hashtable和ConcurrentHashMap的区别
1. HashTable底层是数组+链表,而HashMap的底层是jdk1.7时是 数据+链表,而1.8时是 数据+链表/红黑树
2. 实现同步的方式
Hashtable是方法都加上synchronized,put和get等方法,效率低
ConcurrentHashMap是 jdk1.7分段锁,1.8是CAS+synchronized头结点
package com.njust.test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
class Student implements Comparable<Student>
{
private String name;
private int age;
public int getAge()
{
return age;
}
public Student(String name, int age)
{
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student o)
{
return name.compareTo(o.name);
}
}
public class CompareDemo
{
public static void main(String[] args)
{
Student student1 = new Student("zhangsan", 20);
Student student2 = new Student("lisi", 30);
List<Student> list = new ArrayList<>();
/**
* 1、实现了Comparable接口的CompareTo方法,是对象本身的排序
*/
Collections.sort(list);
/**
* 2.或者使用外带的比较器:Comparator
*/
Collections.sort(list, (s1, s2) -> {
return s1.getAge() - s2.getAge();
});
}
}
参数对象实现Comparable接口,调用CompareTo方法
创建TreeSet或TreeMap时,动态传递Comparator比较器
sort类似的两种方式
package com.njust.test;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.junit.Test;
class Student implements Comparable<Student>
{
private String name;
private int age;
public int getAge()
{
return age;
}
public Student(String name, int age)
{
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student o)
{
return name.compareTo(o.name);
}
}
public class CompareDemo
{
@Test
public void treeMap()
{
Student student1 = new Student("zhangsan", 20);
Student student2 = new Student("lisi", 30);
//1,传递Comparator比较器
Map<Student, Integer> treeMap1 = new TreeMap<>((s1, s2) -> {
return s1.getAge() - s2.getAge();
});
treeMap1.put(student1, 1);
treeMap1.put(student2, 2);
//2.或者对象实现了Comparable接口
Map<Student, Integer> treeMap2 = new TreeMap<>();
treeMap2.put(student1, 1);
treeMap2.put(student2, 2);
}
@Test
public void treeSet()
{
Student student1 = new Student("zhangsan", 20);
Student student2 = new Student("lisi", 30);
//1,传递Comparator比较器
Set<Student> treeSet = new TreeSet<>((s1, s2) -> {
return s1.getAge() - s2.getAge();
});
treeSet.add(student1);
treeSet.add(student2);
//2.或者对象实现了Comparable接口
Set<Student> treeSet2 = new TreeSet<>();
//TreeSet的对象要实现Comparable接口才可以
//Exception in thread "main" java.lang.ClassCastException: com.njust.test.Student cannot be cast to java.lang.Comparable
treeSet2.add(student1);
treeSet2.add(student2);
}
}
常见的synchronized和Lock都是悲观锁
CAS(Compare and swap)
例如 AtomicInteger中大量的cas操作
// ------------------------- JDK 8 -------------------------
// AtomicInteger 自增方法
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
// Unsafe.class
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
// ------------------------- OpenJDK 8 -------------------------
// Unsafe.java
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
//这里一直在循环,也可以说在自旋
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
即有时候占用锁的线程A,占用时间比较短,很快就会释放锁的话,那当前等待的线程B,为了避免线程切换的开销,可以考虑执行while循环,来等待线程A释放锁;自旋就是while循环的过程;
不过,为了避免一直自旋导致浪费CPU时间,会控制自旋次数,一般是10
就是自旋锁根据当前线程之前的表现,如果之前自旋5次就成功,就尝试也自旋5次;如果之前自旋都失败了,那就可能直接不尝试自旋
这四种是指锁的状态,是针对synchronized的
典型的CAS就是无锁 多个线程去修改同一个资源,只有一个能修改成功,其他的会不断重试
什么时候用偏向锁:实际只有1个线程在执行同步代码 大多数情况下,不存在多线程竞争,而是由同一个线程多次获得锁;
行为:减少加锁去锁操作 一段同步代码,一直被一个线程访问,那这个线程自动获取锁,减少CAS操作,降低获取锁的代价
什么时候释放 当有其他线程来竞争锁,则升级为轻量级锁
什么时候使用轻量级锁 当锁是偏向锁时,被另外的线程访问,会升级为轻量级锁
行为:自旋 其他尝试获取锁的线程,会进行自旋操作
什么时候释放 如果线程A占有锁,线程B等待获取锁,则进行自旋操作; 但是如果,B自旋了太多次,或者此时又假如等待线程C(达到3个线程了),则锁会升级为重量级锁
锁 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
偏向锁 | 加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。 | 如果线程间存在锁竞争,会带来额外的锁撤销的消耗。 | 适用于只有一个线程访问同步块场景。 |
轻量级锁 | 竞争的线程不会阻塞,提高了程序的响应速度。 | 如果始终得不到锁竞争的线程使用自旋会消耗CPU。 | 追求响应时间。同步块执行速度非常快。 |
重量级锁 | 线程竞争不使用自旋,不会消耗CPU。 | 线程阻塞,响应时间缓慢。 | 追求吞吐量。同步块执行速度较长。 |
按照申请锁的顺序来获得锁
线程加锁时,直接尝试获取锁,获取失败才去排队;
可能会出现后去获取锁的线程,反而先获得锁
外层方法已经获取锁了,调用内层方法时自动获取锁(前提是锁对象一致)
public class Widget {
public synchronized void doSomething() {
System.out.println("方法1执行...");
doOthers();
}
public synchronized void doOthers() {
System.out.println("方法2执行...");
}
}
读写锁中,读锁就是共享锁、写锁就是互斥锁
读读不冲突、读写冲突+写写冲突
byte 1
short 2
int 4
long 8
float 4
double 8
char 2
boolean
byte:1个字节 8位 -128~127 short :2个字节 16位 int :4个字节 32位 long:8个字节 64位
float:4个字节 32 位 double :8个字节 64位
char:2个字节。
非公平锁时,队列链表中的第一个线程B,以及新来的线程C,会一起竞争机会的 但是,已经在队列中的线程还是挨个获取机会的
公平锁就是在获取锁之前会先判断等待队列是否为空或者自己是否位于队列头部,该条件通过才能继续获取锁。