标签:toc explain ack prot defer 原理 live parameter 隔离级别
COALESCE是一个函数, (expression_1, expression_2, ...,expression_n)依次参考各参数表达式,遇到非null值即停止并返回该值。如果所有的表达式都是空值,最终将返回一个空值。使用COALESCE在于大部分包含空值的表达式最终将返回空值。
--查询是否锁表了
select oid from pg_class where relname=‘可能锁表了的表‘
select pid from pg_locks where relation=‘上面查出的oid‘
--如果查询到了结果,表示该表被锁 则需要释放锁定
select pg_cancel_backend(上面查到的pid)
HSSFDataUtil类的isCellDateFormatted方法
DVConstraint lydlconstraint = DVConstraint.createExplicitListConstraint(dlarr);//入参就是下拉框的数据,数据类型为数组
CellRangeAddressList lydlregions = new CellRangeAddressList(1, 200, 4, 4);
HSSFDataValidation lydldataValidation = new HSSFDataValidation(lydlregions, lydlconstraint);
lydldataValidation.setSuppressDropDownArrow(false);
// 设置提示信息
lydldataValidation.createPromptBox("操作提示", "请选择下拉选中的值");
// 设置输入错误信息
lydldataValidation.createErrorBox("错误提示", "请从下拉选中选择,不要随便输入");
sheet.addValidationData(lydldataValidation);
getServletContext().getInitParameter(“name”)
获取;Listener标签指定监视器,用于监听web应用中某些对象、信息的创建、销毁、增加、修改、删除等动作的发生,然后做出相应的相应处理。当范围对象的状态发生变化的时候,服务器自动调用监听器对象中的方法。ContextLoaderListener指定的是Spring的配置,而DispatcherServlet配置的是SpringMVC的配置环境,用于初始化九大组件。如果在前者的配置文件中配置了@Controller的注解的bean,实际上SpringMVC是扫描不到的。
当servlet容器接收到浏览器发起的一个url请求后,容器会用url减去当前应用的上下文路径,以剩余的字符串作为servlet映射,假如url是http://localhost:8080/appDemo/index.html,其应用上下文是appDemo,容器会将http://localhost:8080/appDemo去掉,用剩下的/index.html部分拿来做servlet的映射匹配。应用的上下文路径对应server.xml中的Context标签的path。
要使用forward重定向就只需把redirect换成forward即可,特别的ModelAndView默认使用forward重定向方式。
注:r-a.jsp位于webapp目录下;r-a是一个servlet。
a()||b()
当a函数返回true时,执行b函数,并返回b函数结果
a||b
当a为true时,返回a,当a为false时,返回b的结果
用于浅拷贝一个对象的属性或数组。
let bar = {a: 1, b: 2};
let baz = {...bar, ...{a:2, b: 4}}; // {a: 2, b: 4}
Objects.requireNonNull(T obj);
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
向上取整用Math.ceil(double a) 向下取整用Math.floor(double a) // 举例: double a=35; double b=20; double c = a/b; System.out.println("c===>"+c); //1.75 System.out.println("c===>"+Math.ceil(c)); //2.0 System.out.println(Math.floor(c)); //1.0
父类.class.isAssignableFrom(子类.class)
子类实例 instanceof 父类类型
对于嵌套循环来说,一个break只能跳出一层
默认采用操作系统的字符集,可使用getByte(“UTF-8”)来指定字符集
查看tomcat进程
ps -ef |grep tomcat
杀死进程
kill -9 pid
修改字符集编码
系统支持编码的修改如下:1. 使用如下命令查看系统支持的字符集cat /usr/share/i18n/SUPPORTED说明:查看系统支持的字符集,你需要注意的是支持字符集的格式,如对中文会有以下一些显示(我的系统如此,我不知是否普遍) zh_CN.GB18030 GB18030 zh_CN.GBK GBK zh_CN.UTF-8 UTF-8 zh_CN GB2312 2.sudo vim /var/lib/locales/supported.d/local说明:打开系统字符集配置文件,将支持的中文字符集添加进去,格式如1中得到所示。 3.sudo locale-gen说明:更新。如果2中添加正确应该没有问题,如果出问题再次编辑2,后再3直至解决。如果正确此时应该可以使用VIM查看GBK编码的文件了,没有编码。但此时用Gedit还不可以,现在我们添加Gedit的字符集支持。 1.gconf-editor打开Gnome配置编辑器 2.app/gedit/preferences/encodings修改键值auto_detected添加入GBK,GB2312,GB18030如果操作成功现在Gedit也没乱码了。
1、打开
sudo vi /var/lib/locales/supported.d/local
在此文件中,添加一行
zh_CN.GBK GBK
2、 sudo locale-gen
会看到系统下载几个文件。
3、修改/etc/environment
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games"
LANG="zh_CN.UTF-8"
LANGUAGE="zh_CN:zh"
添加
LC_ALL="zh_CN.GBK"
4、接下来重启机器就行了
解压
tar –xvf file.tar //解压 tar包
tar -xzvf file.tar.gz //解压tar.gz
tar -xjvf file.tar.bz2 //解压 tar.bz2
tar –xZvf file.tar.Z //解压tar.Z
unrar e file.rar //解压rar
unzip file.zip //解压zip
压缩
tar –cvf jpg.tar *.jpg //将目录里所有jpg文件打包成tar.jpg
tar –czf jpg.tar.gz *.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用gzip压缩,生成一 个gzip压缩过的包,命名为jpg.tar.gz
tar –cjf jpg.tar.bz2 *.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用bzip2压缩,生成一个bzip2压缩过的包,命名为jpg.tar.bz2
tar –cZf jpg.tar.Z *.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用compress压缩,生成一个umcompress压缩过的包,命名为jpg.tar.Z
rar a jpg.rar *.jpg //rar格式的压缩,需要先下载rar for linux
zip jpg.zip *.jpg //zip格式的压缩,需要先下载zip for linux
?
package com.wss.httpclients.factory;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.micrometer.core.instrument.util.IOUtils;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.rmi.UnknownHostException;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class HttpConnectionPoolUtil {
private static final int CONNECT_TIMEOUT = Config.getHttpConnectTimeout();// 设置连接建立的超时时间为10s
private static final int SOCKET_TIMEOUT = Config.getHttpSocketTimeout();//设置请求数据的超时时间
private static final int MAX_CONN = Config.getHttpMaxPoolSize(); // 最大连接数
private static final int Max_PRE_ROUTE = Config.getHttpMaxPoolSize();
private static final int MAX_ROUTE = Config.getHttpMaxPoolSize();
private static CloseableHttpClient httpClient; // 发送请求的客户端单例
private static PoolingHttpClientConnectionManager manager; //连接池管理类
private static ScheduledExecutorService monitorExecutor;
private final static Object syncLock = new Object(); // 相当于线程锁,用于线程安全
/**
* 对http请求进行基本设置
* @param httpRequestBase http请求
*/
private static void setRequestConfig(HttpRequestBase httpRequestBase){
RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(CONNECT_TIMEOUT)
.setConnectTimeout(CONNECT_TIMEOUT)
.setSocketTimeout(SOCKET_TIMEOUT).build();
httpRequestBase.setConfig(requestConfig);
}
//真正获取httpclient
public static CloseableHttpClient getHttpClient(String url){
String hostName = url.split("/")[2];
System.out.println(hostName);
int port = 80;
if (hostName.contains(":")){
String[] args = hostName.split(":");
hostName = args[0];
port = Integer.parseInt(args[1]);
}
if (httpClient == null){
//多线程下多个线程同时调用getHttpClient容易导致重复创建httpClient对象的问题,所以加上了同步锁
synchronized (syncLock){
if (httpClient == null){
httpClient = createHttpClient(hostName, port);
//开启监控线程,对异常和空闲线程进行关闭
monitorExecutor = Executors.newScheduledThreadPool(1);
monitorExecutor.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
//关闭异常连接
manager.closeExpiredConnections();
//关闭5s空闲的连接
manager.closeIdleConnections(Config.getHttpIdelTimeout(), TimeUnit.MILLISECONDS);
}
}, Config.getHttpMonitorInterval(), Config.getHttpMonitorInterval(), TimeUnit.MILLISECONDS);//初始化延时,两次开始执行最小时间间隔
}
}
}
return httpClient;
}
/**
* 根据host和port构建httpclient实例
* @param host 要访问的域名
* @param port 要访问的端口
* @return
*/
public static CloseableHttpClient createHttpClient(String host, int port){
ConnectionSocketFactory plainSocketFactory = PlainConnectionSocketFactory.getSocketFactory();
LayeredConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactory.getSocketFactory();
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create().register("http", plainSocketFactory)
.register("https", sslSocketFactory).build();
manager = new PoolingHttpClientConnectionManager(registry);
//设置连接参数
manager.setMaxTotal(MAX_CONN); // 最大连接数
manager.setDefaultMaxPerRoute(Max_PRE_ROUTE); // 路由最大连接数
HttpHost httpHost = new HttpHost(host, port);
manager.setMaxPerRoute(new HttpRoute(httpHost), MAX_ROUTE);
//请求失败时,进行请求重试
HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {
@Override
public boolean retryRequest(IOException e, int i, HttpContext httpContext) {
if (i > 3){
//重试超过3次,放弃请求
return false;
}
if (e instanceof NoHttpResponseException){
//服务器没有响应,可能是服务器断开了连接,应该重试
return true;
}
if (e instanceof SSLHandshakeException){
// SSL握手异常
return false;
}
if (e instanceof InterruptedIOException){
//超时
return false;
}
if (e instanceof UnknownHostException){
// 服务器不可达
return false;
}
if (e instanceof ConnectTimeoutException){
// 连接超时
return false;
}
if (e instanceof SSLException){
return false;
}
HttpClientContext context = HttpClientContext.adapt(httpContext);
HttpRequest request = context.getRequest();
if (!(request instanceof HttpEntityEnclosingRequest)){
//如果请求不是关闭连接的请求
return true;
}
return false;
}
};
CloseableHttpClient client = HttpClients.custom().setConnectionManager(manager).setRetryHandler(handler).build();
return client;
}
/**
* 设置post请求的参数
* @param httpPost
* @param params
*/
private static void setPostParams(HttpPost httpPost, Map<String, String> params){
List<NameValuePair> nvps = new ArrayList<NameValuePair>();
Set<String> keys = params.keySet();
for (String key: keys){
nvps.add(new BasicNameValuePair(key, params.get(key)));
}
try {
httpPost.setEntity(new UrlEncodedFormEntity(nvps, "utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
public static JsonObject post(String url, Map<String, String> params){
HttpPost httpPost = new HttpPost(url);
setRequestConfig(httpPost);
setPostParams(httpPost, params);
CloseableHttpResponse response = null;
InputStream in = null;
JsonObject object = null;
try {
response = getHttpClient(url).execute(httpPost, HttpClientContext.create());
HttpEntity entity = response.getEntity();
if (entity != null) {
in = entity.getContent();
String result = IOUtils.toString(in, Charset.forName("utf-8"));
Gson gson = new Gson();
object = gson.fromJson(result, JsonObject.class);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try{
if (in != null) in.close();
if (response != null) response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return object;
}
/**
* 关闭连接池
*/
public static void closeConnectionPool(){
try {
//httpclient4.5之后httpclients4.5.x版本直接调用ClosableHttpResponse.close()就能直接把连接放回连接池,
// 而不是关###闭连接,以前的版本貌似要调用其他方法才能把连接放回连接池
httpClient.close();
manager.close();
monitorExecutor.shutdown();
} catch (IOException e) {
e.printStackTrace();
}
}
}
display: inline-flex
加在父元素上,同时父元素不指定宽高。能够使父元素根据子元素的宽度自适应
transition:all 0.3s;//动画
-webkit-transform: rotate(90deg); //让一个元素旋转90°
css3动画
https://c.runoob.com/codedemo/3391
单位
vw:视口的最大宽度,1vw=视口宽度的百分之一;
vh:视口得最大高度,1vh=视口高度的百分之一;
一个div里面的文字不指定高度直接居中。
<div style="width: 50%;font-size: 14px;line-height: 40px;">1111111</div>
一个父div根据子元素的高度自适应
<div style="padding: 0px 5px 0px 5px;background-color: antiquewhite;">
<div style="height: 60px;background-color: blue"></div>
</div>
父div不指定宽高,只指定padding,让子元素定位
<div style="padding: 10px 10px; background-color: aqua;position: fixed;">1111111111111111111</div>
span加上padding之后可实现垂直居中的效果
JAVA 命令行参数解析,org.apache.commons.cli的使用
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.2</version>
</dependency>
public class t1 {
public static void main(String[] args) throws Exception {
final Options options = new Options();
final Option option = new Option("f", true, "Configuration file path");
options.addOption(option);
final CommandLineParser parser = new PosixParser();
CommandLine cmd = null;
try {
cmd = parser.parse(options, args);
} catch (final ParseException e) {
throw new Exception("parser command line error",e);
}
String configFilePath = null;
if (cmd.hasOption("f")) {
configFilePath = cmd.getOptionValue("f");
System.out.println(configFilePath);
}else{
System.err.println("please input the configuration file path by -f option");
System.exit(1);
}
}
}
注意:
用eclipse导出的时候导出的是runnable jar file
判断一个类有没有加一个指定的注解
通过class对象的isAnnotationPresent方法,注意java注解在编译成字节码的时候忽略注解,所以需要把注解的定义类加上@Retention(RetentionPolicy.runtime)
才能被java虚拟机识别
实现了这个接口的list使用for遍历会优于用迭代器遍历
this.getClass().getClassLoader().getResourceAsStream(path);
this.getClass().getResource("/").getPath();
List
size >> 1 表示size/2 相当于 size/(2的n次方)
size<<1 表示size*2 相当于size* (2的n次方)
public class Service {
public boolean login(String username,String password){
if(username.equals(password)){
return true;
}
return false;
}
public String forgetpaddword(){
return "aaa123";
}
}
public class Reflect {
public static void main(String[] args) throws Exception {
Class<?> serviceClass = Class.forName("col.Service");
Service service = (Service)serviceClass.newInstance();
Method login = serviceClass.getMethod("login", String.class, String.class);
login.setAccessible(true);
boolean invoke = (boolean) login.invoke(service,"zhouyongxi", "zhouyongxi");
System.out.println(invoke);
}
}
public interface Animal {
public void eat();
}
public class Cat implements Animal{
@Override
public void eat() {
System.out.println("吃饱了!");
}
}
public class JDKDynamicProxy implements InvocationHandler {
public Object target;
public JDKDynamicProxy(Object target){
this.target = target;
}
public <T> T getProxy(){
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("主人给食!");
/*此处必须传target,传入proxy会循环报错 */
Object invoke = method.invoke(target,args);
System.out.println("吃饱去散步!");
return invoke;
}
}
public class ProxyExample {
public static void main(String[] args) throws Exception {
Animal animal = new Cat();
JDKDynamicProxy jdkDynamicProxy = new JDKDynamicProxy(animal);
Animal proxy = jdkDynamicProxy.getProxy();
proxy.eat();
}
}
public class ProxyExample2 {
public static void main(String[] args) {
Animal animal = new Cat();
JDKDynamicProxy jdkDynamicProxy = new JDKDynamicProxy(animal);
Animal newProxyInstance = (Animal)Proxy.newProxyInstance(animal.getClass().getClassLoader(), animal.getClass().getInterfaces(), jdkDynamicProxy);
newProxyInstance.eat();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--启用Spring注解,指定使用了注解的包,有多个包时逗号分隔-->
<context:component-scan base-package="AspectJ" />
<!--启用AspectJ的注解-->
<aop:aspectj-autoproxy />
<bean id="animal" class="AspectJ.Animal" />
</beans>
public class Animal {
public void speak(){
//int i = 2 / 0;
System.out.println("aaa123");
}
}
@Aspect
@Component
public class PointCut {
//切点用于给通知传参
@Pointcut("execution(* AspectJ.Animal.*())")
private void myPointCut(){}
//环绕通知,对前后置通知的加强
@Around("execution(* AspectJ.Animal.*(..))")
public Object arount(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("1");
Object proceed = joinPoint.proceed();
System.out.println("2");
return proceed;
}
//前置通知
@Before("myPointCut()")
public void before(){
System.out.println("Before");
}
//最终通知
@After("myPointCut()")
public void after(){
System.out.println("After");
}
//后置通知,与异常抛出通知只能运行于一个
@AfterReturning("myPointCut()")
public void afterReturning(){
System.out.println("afterReturning");
}
//异常抛出通知,与后置通知只能运行于一个
@AfterThrowing("myPointCut()")
public void AfterThrowing(){
System.out.println("AfterThrowing");
}
}
public class Amain {
public static void main(String[] args) {
ApplicationContext acontext = new ClassPathXmlApplicationContext("spring-aspectJ.xml");
Animal animal = (Animal)acontext.getBean("animal");
animal.speak();
}
}
? 在servlet3.0中,我们可以从HttpServletRequest对象中获取一个AsyncContext对象,该对象构成余部请求上下文,request和response对象开源从AsyncContext对象中获取,AsyncContext可以从当前线程传给另一个线程,并在新线程中完成对请求处理并返回结果给客户端。
这2个类是线程安全的,内部使用了ReentrantLock可重入锁来实现线程安全锁。顾名思义,这个类设置值的时候,将原数组的元素拷贝(Array.copyOf浅拷贝)到一个新数组,再给新数组添加值,因此在大量数组增加的时候,会浪费性能。
Unsafe类可以为我们提供高效线程安全来操作变量,直接和内存交互,并且可以直接开辟堆外内存。
可以通过静态方法Unsafe.getUnsafe()就获取Unsafe实例。
//该方法可以获取一个对象的属性相对于该对象在内存当中的偏移量,这样我们就可以根据这个偏移量在对象内存当中找到这个属性。
long objectOffset = unsafe.objectFieldOffset(User.class.getDeclaredField("value"));
Object value = unsafe.getObject(new User(), objectOffset);
//putOrderedInt 设置值 并且马上写入主存,该变量必须是volatile类型
/**
* 设置 volatile 类型到int值
*
* @param obj 需要更新的对象
* @param offset obj中整型field的偏移量
* @param expect 希望field中存在的值
*/
void sun.misc.Unsafe.putOrderedInt(Object obj, long offset, int expect)
//compareAndSwapObject 和上面方法功能一样,只不过是设置Object类型的变量
public native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object update);
如果一个线程获得了锁,那么锁就进入了偏向模式。当线程再次获取锁的时候,无须再次同步操作。节省大量申请锁的时间,提高程序性能。但是对于锁竞争比较激烈的情况下,效果比较差。开启偏向锁的方法:
-XX:+UserBiasedLocking开启
浅克隆:会生成一个新的内存地址给一个引用变量
通过实现CloneAble接口,并重写clone方法,方法内调用super.clone()实现浅克隆。
java的clone方法的克隆只是浅拷贝,它实现的是在堆内存中复制一份目标对象的地址和数据,并将其指向新的引用变量(栈内存),而实际上新产生对象的的引用属性还是和原对象完全一致(hashcode一致),也就是说仅仅是拷贝了堆内存的地址。如果想要利用clone方法实现深拷贝,那么就得将对象的引用属性也一起调用clone实现拷贝。
public class CloneTest implements Cloneable{
private int age;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "CloneTest{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
@Override
protected CloneTest clone() throws CloneNotSupportedException {
return (CloneTest) super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
CloneTest c = new CloneTest();
c.setName("1");
c.setAge(1);
CloneTest cloneTest = c.clone();
//新克隆的对象内的引用变量的地址相同,说明是浅拷贝,如果想改成深拷贝,则需要调用对象内引用属性的clone方法。
System.out.println(c.name == cloneTest.name);//true
//说明调用clone方法生成的引用对象的地址是新的,
System.out.println(c == cloneTest);//false
System.out.println(cloneTest.getName());
System.out.println(cloneTest.getAge());
cloneTest.setName("2");
System.out.println(cloneTest.toString());
System.out.println(c.toString());
}
}
利用Serializable实现深克隆,在其中利用io流的方式将这个对象写道io流中,然后再从io流中读取。
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//将当前这个对象写到一个输出流当中,,因为这个对象的类实现了Serializable这个接口,所以在这个类中
//有一个引用,这个引用如果实现了序列化,那么这个也会写到这个输出流当中
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
BeanUtils.copyProperties(target,obj)将obj的同名属性赋值给target的同名属性。
public class ListInerator {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
ListIterator<String> listIterator = list.listIterator();
while (listIterator.hasNext()){
System.out.println(listIterator.next());
}
ListIterator<String> listIterator1 = list.listIterator(1);
while (listIterator1.hasNext()){
System.out.println(listIterator1.next());
}
}
}
LinkedList内部采用双向链表的原理来进行,而ArrayList内部采用数组来进行维护,LinkedList内部保存了头节点和尾节点。
for (Map.Entry<? extends K, ? extends V> e : t.entrySet())
volatile并不能保证原子性。
volatile声明的变量,在写入该变量时,同时也会将该变量写入到内存,在读取改变量时,也会直接从内存中读取。可见性的保证遵顼一下原则:
可以将volatile的读写理解为一个触发刷新的操作,写入volatile时,线程中所有的变量也都会触发写入内存,而读取volatile变量时,也会触发所有变量从内存中读取。应当尽量把volatile写操作放到最后,而将volatile的读操作写道最前,这样就能连带其他变量进行刷新。
由于指令重排机制的存在,volatile的可见性可能会存在bug:
因此volatile在保证可见性的同时,也会提供happen-before的保证。
happen-before:
若2个线程同时修改某个变量,仅使用volatile是不够的,还需要使用synchronized配合。
在多核cpu中每个处理器都有高速缓存(L1、L2、L3),而主内存只有一个。
那么如何保证数据的一致性呢?
总线的方式,这样会降低cpu的吞吐量
使用缓存一致性,如MESI协议、MSI协议等。
虚拟机栈:每个方法都有自己的一块区域,存储局部变量。
本地方法栈:主要存储native方法。
堆内存:用于存放对象实例,被所有线程共享。
方法区:存储类信息、常量、静态变量
程序计数器:每个线程有自己的程序计数器。
jvm有很多,Hotspot、JRockit、J9等。jdk8对hotspot和jrockit进行了结合。
JVM主要由3个子系统组成。类加载子系统、运行时数据区、执行引擎。
方法的调用就是栈帧入栈出栈的过程。
synchronized的特性有:原子性、可见性、有序性
synchronized的语义底层时通过monitor的对象来完成,起始wait/notify也依赖于monitor对象。
对象锁: 利用monitorenter和monitorexit命令
monitorenter:每个对象都有一个监视器锁(monitor),当monitor被占用时,就会处于锁定状态,线程2执行monitorenter指令时尝试获取monitor的所有权。若monitor的进入数为0时,则线程进入monitor,并且将monitor设为1,该线程就是锁的拥有者;若线程已经占有monitor,需要重新进入,只需对monitor+1;若其他线程已经占有monitor,则进入阻塞状态,直到monitor变为0,再次尝试获取monitor所有权。
monitorexit:执行monitorexit命令的只能是拥有object的monitor的所有者。指令执行时,monitor的数量-1,直到线程减为0,线程退出monitor,不再是这个monitor的拥有者。
方法锁:除了利用monitorenter和monitorexit之外还利用了方法的ACC_SYNCHRONIZED 。
当方法被调用时,调用指令会检查方法的ACC_SYNCHRONIZED 访问标志是否被设置,若设置了,线程会去先获取monitor,获取成功后才回去执行方法体,方法执行完,再释放monitor。在方法执行期间任何线程无法获取同一个monitor对象。
都是使用了OS底层的mutex来实现。
对象在内存中布局有对象头、实例数据、填充数据
synchronized使用的锁就是存储在对象头里面的。对象头有MarkWord和ClassPointer。其中ClassPoint存储了对象对应的类元数据的指针,虚拟机通过这个指针来判断该对象时哪个类的实例。MarkWord主要存储对象自身运行时数据,是实现轻量级锁和偏向锁的关键。
在32位OS中对象头占8字节,64位则是16字节(不考虑压缩指针,开启压缩指针后12字节),
锁状态分为无锁、偏向锁、轻量锁、重量锁
StampedLock有三种锁:乐观读锁、悲观读锁、写锁。
乐观读锁:读锁和写锁不是互斥的,因为在日常的系统中读操作存在很多,而相对写操作会少很多,这就会导致写锁饥饿的现象。StampedLock的乐观锁只是在读操作的过程中发现有写操作,就会在去读一次。
悲观读锁:读写互斥
强引用
平时引用的最多,User user = new User();user便是强引用,若user=null,则堆区中的User对象会被GC回收
软引用
通过SoftReference来定义一个软引用。
若一个对象只有软引用,内粗足够时,gc不回收,若内存空间不足,就会被回收
弱引用
通过WeakReference来定义一个弱引用
若对象只有弱引用,那么GC回收时,不管内存是否足够,都会回收此对象
虚引用
通过PhantomReference来定义虚引用
主要用来跟踪对象被垃圾回收的活动。
加载、验证、准备、解析、初始化、使用、卸载。
加载:将java文件转换成class文件,也可以是网络传输的io流,也可以是zip包。
预加载:java虚拟机启动时,会先加载java_home的lib目录下的rt.class下的class,是java运行时常用的一些类,比如util.和lang.。
运行时加载:首先java虚拟机会先去内存中查找这个class文件,没有被加载会根据这个类的全限定名去加载。
过程:
验证:为了保护JVM
对class的字节流进行验证,确保class文件符合虚拟机的要求,并且不危害虚拟机自身的安全
准备:主要是给static变量分配内存(方法区),并设置初值。同时给常量设置值。注意public static int a = 5
在准备阶段只是给a赋予初值0
解析:引用关系转换成地址
初始化:调用类的构造方法,不是构造函数。生成规则:static变量的赋值操作+static代码块
使用&卸载
https://www.jianshu.com/p/76959115d486
https://blog.csdn.net/lwang_IT/article/details/78650168
我们知道程序计数器、本地方法栈、虚拟机栈随线程而生,随线程而灭。java堆是GC回收的“重点区域”,堆内存放所有对象的实例,gc进行回收前,需要判断哪些对象存活。
JVM提供了两个参数来控制JVM堆的大小:-XX:InitialHeapSize(-Xms)和-XX:MaxHeapSize(-Xmx)。JVM会根据应用程序使用内存的情况,动态扩展堆内存的大小,上图中的Virtual表示的区域,表示的就是可以扩展的内存空间。
当调用System.gc()方法时,会调用Object.finalize()。
https://www.cnblogs.com/duke2016/p/6013519.html
简单效率高,给对象添加一个引用计数器,每当对象被引用1次,引用计数就+1,当被引用0次时,代表对象已经不被引用。
缺点:当出现循环引用时,引用计数失效。
通过一个叫“GC Roots”的对象为起点,搜索所经过的路径为引用链,当一个对象没有任何引用跟他的连接则证明对象是不可用的。
要真正宣告对象死亡需经过两个过程:
当我们真正回收一个对象的时候,对象必须经过2次回收过程。当确认没有与GC Roots相关联的链时,这个对象会被第一次标记,并判断对象的finalize方法是否需要执行(在java的Object对象中,有一个finalize()方法,我们创建的对象可以选择是否需要重写),若对象的类没有重写finalize方法或者finalize已经被执行过了,那么就不需再次执行该方法了。
若这个对象的finalize方法需要被执行,那么这个对象就会被加入F-Queue的队列,这个队列是jvm自动创建的低优先级Finalizer线程去消费,去执行对象的finalize方法(虚拟机只会去触发这个方法,不会等待这个方法调用返回,这样做的目的是如果方法执行过程中出现阻塞,性能可能出现问题或者死循环,Finalizer线程仍然不受影响的去消费队列,不影响整个过程)。
稍后GC会对F-Queue队列中的对象进行第二次标记,若在这次标记发生时,队列中的对象确实没有存活(和GC Roots没有链),那么这个对象就确定下来需要被回收了。当然第二次标记时,突然和GC Roots有了引用链,那么这个对象就相当于救活了自己,在第二次标记时,这个对象就被移除待回收对象的集合了。所以通过2次标记的机制,可以通过finalize方法让Gc Roots和对象重新连接,那么这个对象就可以被救活了。
public class FinalizerTest {
private static Object HOOK_REF;
public static void main(String ...args) throws Exception {
HOOK_REF = new FinalizerTest();
// 将null赋值给HOOK_REF,使得原先创建的对象变成可回收的对象
HOOK_REF = null;
System.gc();
Thread.sleep(1000);
if (HOOK_REF != null) {
System.out.println("first gc, object is alive");
} else {
System.out.println("first gc, object is dead");
}
// 如果对象存活了,再次执行一次上面的代码
HOOK_REF = null;
System.gc();
if (HOOK_REF != null) {
System.out.println("second gc, object is alive");
} else {
System.out.println("second gc, object is dead");
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
// 在这里将this赋值给静态变量,使对象可以重新和GC Roots对象创建引用链
HOOK_REF = this;
System.out.println("execute in finalize()");
}
}
#output:
execute in finalize()
first gc, object is alive
second gc, object is dead
可以看到,第一次执行System.gc()的时候,通过在方法finalize()中将this指针指向HOOK_REF来重建引用链接,使得本应该被回收的对象重新复活了。而对比同样的第二段代码,没有成功拯救的原因是: finalize()方法只会被执行一次 ,所以当第二次将HOOK_REF赋值为null,释放对对象的引用的时候,由于finalize()方法已经被执行过一次了,所以没法再通过finalize()方法中的代码来拯救对象了,导致对象被回收。
https://www.cnblogs.com/duke2016/p/6013519.html
jvm采用“分代收集算法”对不同区域采用不同的回收算法。
新生代采用复制算法,老年代采用标记/清除算法和标记/整理算法
标记清除算法,是最基础的算法,分为2个阶段:标记、清除阶段。在标记阶段判断哪些对象需要被回收,在清除阶段清除这些对象。算法本身存在一些缺陷:首先标记和清除阶段效率并不高,其次直接将标记的对象清除会产生内存碎片,而大量不连续的碎片会影响后续连续内存空间的申请。申请连续的内存空间失败会再次触发垃圾回收机制,影响性能。
复制算法,顾名思义,跟复制操作有关,该算法将内存区域划分为大小相同的2块区域,每次使用都是只是用其中一块,另一块闲置备用。当进行垃圾回收的时候,会将当前用的那块内存上的存活的对象直接复制到另一块闲置的空闲内存上,然后将之前使用的那块内存上的对象全部清除掉。这样处理的好处是可以有效处理标记-清除算法碰到的内存碎片的问题,实现简单、效率高。但是比较耗费内存,实际上另一半内存是在浪费。
标记整理算法,思路是先进行垃圾内存的标记,和标记-清除算法的标记阶段一致,当垃圾对象被回收之后,为了避免内存碎片的问题,标记整理算法会对内存进行整理,将所有存活的对象移到另一端,将一段的空闲内存整理出来,这样就可以得到连续的内存了。这样做可以很方便的申请内存,只要移动内存指针就可以划分区域用以存储新的对象,可以在不浪费内存的情况下高效的分配内存,避免了复制算法中浪费一部分内存的情况
分代收集算法。在现代虚拟机实现中,会将整个内存划分为多个区域,用年龄代表对象的存活时间。并将不同年龄段的对象存放在不同内存区域。这就是老年代和新生代的来源。
年轻代是指刚出生的对象,老年代是指在内存中存活了一段时间的对象。将对象分代的目的是针对不同种类的对象的垃圾回收采用不同的算法。
对于年轻代,其中大部分对象的存活时间较短,很多对象撑不过下一次垃圾回收,所以在年轻代中,一般采用”复制算法“实现垃圾回收器。
在上图中,我们可以看到"Young Generation"标记的这块区域就是"年轻代"。在年轻代中,还细分了三块区域,分别是:"eden"、"S0"和"S1",其中"eden"是新对象出生的地方,而"S0"和"S1"就是我们在复制算法中说到了那两块相等的内存区域,称为存活区(Survivor Space)
年轻代区域中,如果按照复制算法中的1:1的方式平分新生代的话,会浪费内存空间,所以将年轻代划分为eden区、S0、S1,每次只是用eden区和其中一块Servivor区,当垃圾回收时会将eden区和已占用的serviror区复制到另一块空闲的serviror区,然后将eden区清空,现在年轻代就可以使用eden区和另一块serviror区,S0和S1区是交替使用的。
Hotspot区默认Eden区和其中一块servivor区的比例是8:1,通过JVM参数"-XX:SurvivorRatio"控制这个比值。SurvivorRatio的值是一个整数,表示Eden区域是一块Survivor区域的大小的几倍,所以,如果SurvivorRatio的值是8,那么Eden区和其中Survivor区的占比就是8:1,那么总的年轻代的大小就是(Eden + S0 + S1) = (8 + 1 + 1) = 10,所以年轻代每次可以使用的内存空间就是(Eden + S0) = (8 + 1) = 9,占了整个年轻代的 9 / 10 = 90%,而每次只浪费了10%的内存空间用于复制。
并不是留出越少的空间用于复制操作越好,如果在进行垃圾收集的时候,出现大部分对象都存活的情况,那么空闲的那块很小的Survivor区域将不能存放这些存活的对象。当Survivor空间不够用的时候,如果满足条件,可以通过分配担保机制,向老年代申请内存以存放这些存活的对象。
对于老年代的对象,由于在这块区域中的对象和年轻代的对象相比较而言存活时间都很长,在这块区域中,一般通过"标记-清理算法"和"标记-整理算法"来实现垃圾收集机制。上图中的Tenured区域就是老年代所在的区域。而最后那块Permanent区域,称之为永久代,在这块区域中,主要是存放类对象的信息、常量等信息,这个区域也称为方法区。在Java 8中,移除了永久区,使用元空间(metaspace)代替了。
JVM参数NewSize和MaxNewSize分别可以控制年轻代的初始大小和最大的大小。通过设置这两个参数,可以手动控制JVM中年轻代的大小,比如-XX:NewSize=100m将年轻代的大小初始化为100m。除了通过固定值来控制年轻代的大小,还可以通过参数NewRatio来按比例控制年轻代的大小,NewRatio的值表示年轻代和老年代的比值,比如:-XX:NewRatio=6 就表示,年轻代:老年代 = 1:6,所以年轻代占据了堆内存的1/7,而老年代则占据了6/7。
对于年轻代中的Eden区和Survivor区的大小分配,JVM提供了SurvivorRatio这个参数来控制两块区域的大小。和NewRatio一样,这个值也是用于控制Eden区和两个Survivor区的大小比例的,比如:-XX:SurvivorRatio = 8,那么表示Eden : 一个Survivor = 8 : 1,那么Eden区就占据了年轻代中的8 / 10,而两个Survivor区分别占据了 1 / 10。
我们通常将年轻的的GC称为Minor GC。
当对象经历了多次MinorGC后仍存活,当达到一定年龄后(经历一次MinorGC后,加1),仍存活的对象就会被移入到老年代中。
我们通常将老年代的GC成为MajorGC、
https://www.cnblogs.com/duke2016/p/6250766.html
jvm对堆内存进行分代划分,是为了结合不同对象的状态采用合适的垃圾回收算法。
JVM实现了多种GC收集器,
按单线程多线程来划分,分为:
按照新生代和老年代划分,分为:
Serial收集器
是一个年轻代垃圾收集器,使用标记-复制算法,单线程的。只是用一个线程进行垃圾回收工作,进行垃圾回收时,所有工作线程均停止工作,等垃圾回收线程完成之后,其他线程才继续工作。因此系统会出现STW(stop the world)现象,应用程序会出现停顿的现象,若停顿时间过长,会导致系统相应迟钝。
PartNew收集器
PartNew是Serial的多线程版本,使用标记-复制算法,由于使用了多线程其性能要比Serial具有更好的性能,并且它可以和老年代的CMS的老年代收集器一起搭配使用。
作为多线程收集器,它运行在单CPU机器时,不能发挥多核优势,会出现频繁的上下文切换,导致额外的开销,所以在单CPU的机器上PartNEW的性能不一定好于Serial。
PartNew收集器默认开启的垃圾手机线程是和当前机器的CPU数量相同,为了控制GC收集线程的数量,可以通过参数-XX:ParallelGCThreads来控制垃圾收集线程的数量。
Parallel Scavenge收集器
他是一个年轻代的处理器,利用标记-复制算法,与PartNew一样,他也是多线程的收集器,与其他收集器不同的是,像PartNew和CMS关注点主要集中于如何缩短垃圾回收时间,而Parallel Scavenge关注的是系统的吞吐量,这里的吞吐量指的是CPU用于运行应用程序的时间和CPU总时间的比值。
Serial Old收集器
是Serial的老年代版本,是标记-整理算法的实践
Parallel Old收集器
Parallel Old收集器是parallel Scavenge的老年代版本,与Parallel Scavenge搭配使用,可实现对堆内存的吞吐量的提高。
CMS收集器
是老年代回收器中最优秀的垃圾回收器,使用标记-清除算法。CMS是一款获取最短停顿时间为目标的收集器.
cms工作过程分为4个阶段:
由图看一看出初始标记阶段和重新标记阶段是只有GC线程参与,用户线程会被停止,所以会产生STW。
由于并发标记和并发清理是和应用程序一起执行的,而初始标记和重新标记相对耗时较短,因此CMS收集器是一款并发低停顿收集器
CMS不足之处:
markword中涉及锁的字段有该对象是不是偏向锁(0/1)、锁标志位(01/10)。
锁级别的升级:无锁->偏向锁(01)->轻量锁(00)->重量级锁(10)
自旋锁(CAS)
自旋锁让线程等待一段时间,而不是立即挂起。
偏向锁
当线程访问同步代码块兵获取锁时,会在对象头和栈帧的锁记录中存储所偏向的线程id,以后该线程再次进入或退出同步块时不需要CAS操作来加锁或解锁,只需简单测试对象头中的MarkWorld中是否存储指向当前线程的偏向锁。若测试成功,表示线程已经获取了锁。如果测试失败,则需要再测试一下MarkWorld中偏向锁的标识是否设置为1;若没有设置,则使用CAS竞争锁;若设置失败了,则尝试使用CAS将对象头的偏向锁指向当前线程。
当线程执行到临界区时,将线程id插入到Markword中,同时修改锁标志位和是不是偏向锁。
轻量级锁
重量级锁
@ExceptionHandler(Throwable.class)
public ResponseEntity handleException(Throwable e){
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
return buildResponseEntity(ApiError.error(e.getMessage()));
}
需要2个类,一个是读取配置文件,二是根据配置来反射实例化。
DefaultListableBeanFactory是整个Bean加载的核心。
xml:
init-method、destroy-method
非xml:
实现InitializingBean类并实现afterPropertiesSet();实现DisposableBean接口并实现其distroy方法。注意DisposableBean接口的destroy方法是需要调用applicationcontext.registerShutdownHook()来调用的
注解:
@PostConstruct(初始化)
@PreDetroy(销毁)
beanfactorypostprocessor主要是在容器中中的任意一个bean被new出来之前执行。应用场景:ConfgiurationClassPostProcessor的postProcessorBeanFactory方法针对配置类加上cglib代理。
beandefinationregistryposrprocessor。是一个子类,在beanfactorypostprocessor的之前执行。因为源码当中先遍历的是它。应用场景:ConfgiurationClassPostProcessor有一个回调方法,会去扫描包,解析beanmethod
importSelector。通过这个方法selectImports返回一个类名(全名),并将其转换成bd,动态添加bd,这个bd是死的。也可以动态扫描
importBeandefinationregistrar。实现这个接口的方法,会把beandefinationregistry传给我们,因此我们可以动态改变bd。这也是和importSelector的区别:importBeandefinationregistrar接口的方法可以把beandefinationregistry传给我们。
DeferedImportSelector延迟加载。
一个类依赖了另一个类,初始化的时候要先初始化依赖类。
用的时候才去实例化,容器启动的时候并不去实例化。
用于指定扫描执行哪些包下面的注解。
excludeFilters属性用于排除哪些包下面的注解。这个属性接收元素为Filter类的数组,Filter注解里面由各种排除的表达式。例如
@CompnentScan(value="com.wss",excludeFilters={@Filter(type=FilterType.REGEX,pattern="com.wss.test")})
beans标签和@Profile注解都可以指定bean的profile
通过applicationContext.getEnvironment().setActiveProfiles("dev")
来指定生效的profile
接受一个class类,用于注册这个类,在调用方法之后需要执行applicationcontext.refresh()
(5.0版本已不再需要)
在ioc中是单例的
连接点(JoinPoint)
永远是一个方法,目标对象中的一个方法
切点(PointCut)
连接点的集合
目标对象(target)
原始对象
织入(weaving)
将增强的操作附加到连接点
通知(advice)
before前置通知
after后置通知
afterreturning返回通知,在连接点方法正常返回后调用,要求连接点方法没有返回异常
afterthrowing异常通知,当连接点方法异常时返回
around环绕通知,将覆盖原有方法,允许你通过反射调用原有方法。
@DeclareParents 扩展,让目标去实现目标对象
@DclareParents(value="",defaultImpl="A")
public static B b;
这样从ioc容器中获取b时,b可以调用A里面的方法。
JoinPoint
getTarget(获取目标对象)
getArgs(连接点参数)
ProceedingJoinPoint,继承自JoinPoint
切面(Aspect)
连接点+切点+通知
代理对象(AOP proxy)
包含了原有对象和增强的操作的代理对象
Spring的动态代理默认使用JDK动态代理。
开启Spring AspectJ的方法
@AspectJ
Spring Aspectj默认支持的是Aspectj是单例的,可以采用下列方法改成多例的
@Aspectj(perthis(this(XXX)))
perthis:每个切入点表达式匹配的连接点对应的Aop对象都会创建一个新的切面
@PointCut
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
这里问号表示当前项可有可没有
modifiers-pattern-方法的可见性,如public,private
ret-type-pattern-方法的返回值类型,如int
declaring-type-pattern-方法所在类的全路径名。如com.spring.Aspect
name-pattern-方法名
param-pattern-方法参数类型
throws-pattern-方法异常类型
字符串(string)、哈希(hash)、列表(list)、集合(set)、有序集合(zset)
若redis的内存已满,后续的写操作如何处理?
LRU算法:
最近最少使用,是一种缓存置换算法。
LFU算法:
根据key的最近被访问频率进行淘汰,很少被访问的优先淘汰,被访问多的被留下来
时一种基于客户端-服务端模型,以及请求/响应模型的TCP服务。Redis管道技术可以在服务端未响应时,可护短可以继续向服务端发送请求,并最终一次性读取所有服务端响应。
一次性向redis服务提交,一次性读取所有服务端响应。
事务是一次单独的隔离操作,事务中的所有命令都是经过序列化、按顺序执行的,事务在执行过程中不会被其他客户端的命令打断。
开启事务
multi命令由非事务状态>事务状态。
不可嵌套使用。
命令入列
客户端进入事务状态之后,执行的所有redis命令都是入队,命令会FIFO的形式。
执行事务的命令
exec
废弃事务的命令
discard
事务错误及回滚
redis不支持事务回滚。目的是:提高运行速度;如果发生语法错误,在开发过程中就已经检查并修复了。
watch实现乐观锁
是一种CAS操作,watch命令用于监听事务中队列的命令,在exec之前,一旦有命令被修改,那整个事务被终止,exec返回null,提示事务失败。
若watch监听的key在exec执行时未被修改(可以在事务执行过程中修改),那么正常执行事务;否则就不会执行。watch可以接收任意多的key
setNX,设置成功返回1,设置失败返回0
单节点设置分布式锁
集群设置分布式锁
客户端1从Master获取了锁,Master宕机了,存储锁的key还未来得及同步到从节点上,
Slave升级为Master,
客户端2从新的Master获取了对应统一资源的锁
客户端1和客户端2同时持有同一资源的锁,锁不再具有安全性。
RedLock解决集群分布式锁
RDB是对当前redis的数据进行一次快照。定时更新,缺点:耗时、耗性能(fork+io),容易丢失数
AOF会记录redis每一次的写操作。当服务器重启时,会重新执行这些命令来恢复原始数据。缺点是体积大,恢复速度慢。
通常情况下AOF保存的数据要比RDB的方式要完整。
RDB的优点:
在恢复数据量大的数据时,要比AOF要快。
库存可能会修改,修改时既要修改数据库,也要修改缓存中的数据。
问题情况1: 先删除缓存,再修改数据库,读缓存时,读不到就会去数据库读最新数据。如果删除缓存成功,数据库修改失败,数据库里面还是旧值,缓存中数据时空的,出现不一致现象。再去读的时候,缓存中没有就去数据库读旧的数据,然后更新到缓存中。
问题情况2:当修改库存时,先将缓存删除,再修改数据库中的数据,此时还未修改完成,另一个请求过来查询,查询首先先从缓存中获取。
bean的种类:spring内部bean、注解bean、xml
插手bean实例化的过程(AOP):
用一个类去实现BeanPostProcessor接口,并且实现2个方法,一个是postProcessBeforeInitialization,另一个是postProcessAfterInitialization,方法的参数有2个,一个是原始bean,一个是bean的name,要想要达到效果必须将这个实现类添加到ioc容器中。
也可以实现BeanFactoryPostProcessor接口,并实现其postProcessBeanFactory方法。通过这个方法我们可以获取beanfactory,获取beandefinationmaps,可以获取其中的beandefination,从而进行对bean的修改。
也可以实现InitizlizingBean接口的afterproperties方法。
spring中applicationcontext的addbeanfactorypostprocessor方法可以手动将一个beanfactorypostprocessor添加到容器中。
我们自定义的beanfactoryprocessor可以有2种方式:1. 实现beandefinationregistryPostProcessor接口(spring自己的)2. 实现beanfactoryprocessor接口(我们自定义的),当然beandeinationregistrypostprocessor接口继承了beanfactorypostprocessor接口,扩展了beanfacotrypostprocessor的能力,它提供了postProcessBeanDefinitionRegistry方法。
//这个方法将beandefination注册到defalultlistablebeanfactory中的beandefinationmap中去
//beandefiantionholder = beandefination+beanname
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
bean的种类:spring内部bean、注解bean、xml
插手bean实例化的过程(AOP):
用一个类去实现BeanPostProcessor接口,并且实现2个方法,一个是postProcessBeforeInitialization,另一个是postProcessAfterInitialization,方法的参数有2个,一个是原始bean,一个是bean的name,要想要达到效果必须将这个实现类添加到ioc容器中。
spring扩展点:beanpostprocessor、beanfactorypostprocessor、beandefinationregistrypostprocessor
spring中判断bean是否已经被处理过,是通过判断beandefination中的CONFIGURATION_CLASS_ATTRIBUTE值是否为null,不为null,则证明已经被处理过
spring内置多种bd来描述,有annotatedBeandefination(加了注解的)、Rootbeandefination、ScannedGenericBeanDefiantion(被扫描注入的bean)
AnnotationConfigApplicationContext的构造方法中,初始化了一个叫ClassPathBeanDefinitionScanner的对象,该对象有一个scan方法用于扫描包下的bean,但是这个对象并不是spring内部扫描的原理实现。也就是说我们可以通过applicationcontext对象的scan方法可以手动扫描。
spring通过bd的构造方法将class转换成某种类型的bd
doProcessConfigurationClass处理注解。很难。parse方法、processConfigurationClass方法、
想ioc注册bean:
@import注解内属性添加一个实现了importselector接口的类,这个类需要实现selectorImports方法,这个方法的返回值就是需要注入到容器中的class的名字。
BeanfefinationBuilder.genericBeandefination(class)可以生成指定类的beandefinationbuilder,通过生成的builder对象的getBeandefination方法来获取beandefination,然后再通过registry.registryBeanddefination就可以将db注册到map中去。
像mybatis那样,如何做到向容器中注入一个接口呢?首先新建接口的代理对象,然后将代理对象设置到接口对应的bd的beanclass属性(通过setBeanClass方法)。那么如何将代理对象设置到属性中呢,可以通过spring提供的factorybean来实现,新建一个factorybean的实现类,实现其getObject方法,当将此实现类注册到容器中时,spring获取到的bean就是getObject方法的实际返回值。但是此时如果factorybean有一个有参构造函数呢?可以通过beandefination.getConstructorArgumentValues().addGenericArgumentValue(name)方法可以将name对应的对象传给构造函数的入参。
AnnotationMetadata存在2个实现类,StandardAnnotationMetadata和AnnotationMetadataReadingVisitor,StandardAnnotationMetadata主要是利用java反射原理获取元数据;AnnotationMetadataReadingVisitor主要利用ASM技术分析字节码获取元数据
//这个方法将beandefination注册到defalultlistablebeanfactory中的beandefinationmap中去
//beandefiantionholder = beandefination+beanname
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
普通的类(被@Component注解的类)是在扫描包时处理,扫描之后注册。
spring通过asm技术读取class,将其转换成bd,并将其注册
ConfigurationClassPostProcessor的postProcessBeanFactory方法的实现就是扫描注解
方案1:nginx+tomcat,利用tomcat的集群模式,但是会占用tomcat带宽,降低吞吐量。
方案2:端存储,保存到浏览器中,服务器存储所有的session,将session存储到浏览器cookie中,一端只存储一个用户消息
方案3:使用nginx的ip_hash让用户的请求都落到一台机器上。
通过nginx负载均衡算法中的 ip_hash ,也就是ip 绑定方式,让每个客户端的和服务器进行了绑定,A 客户端访问了1号服务器,后面A 客户端发起的请求,都会分发到1号服务器了。
缺点是没有了负载均衡
方案4:spring-session保存到redis
动态生成要代理类的子类,子类重写代理类的非final方法,在子类中采用方法拦截的技术拉杰所有父类方法的调用,他比jdk的动态代理要快。
缺点:对于final方法,无法进行代理
jdk动态代理对InvocationHandler接口方法的调用对代理类内的所以方法都有效。
在CGLib回调时可以设置对不同方法执行不同的回调逻辑,或者根本不执行回调。
说到延迟加载,应该经常接触到,尤其是使用Hibernate的时候,本篇将通过一个实例分析延迟加载的实现方式。 LazyLoader接口继承了Callback,因此也算是CGLib中的一种Callback类型。
另一种延迟加载接口Dispatcher。
Dispatcher接口同样继承于Callback,也是一种回调类型。
但是Dispatcher和LazyLoader的区别在于:LazyLoader只在第一次访问延迟加载属性时触发代理类回调方法,而Dispatcher在每次访问延迟加载属性时都会触发代理类回调方法。
PropertyBean o = (PropertyBean) enhancer.create(PropertyBean.class, new ConcreteClassLazyLoader());
public class TestInterfaceMaker {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
InterfaceMaker interfaceMaker =new InterfaceMaker();
//抽取某个类的方法生成接口方法
interfaceMaker.add(TargetObject.class);
Class<?> targetInterface=interfaceMaker.create();
for(Method method : targetInterface.getMethods()){
System.out.println(method.getName());
}
System.out.println("---------------------------------------");
//接口代理并设置代理接口方法拦截
Object object = Enhancer.create(Object.class, new Class[]{targetInterface}, new MethodInterceptor(){
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
if(method.getName().equals("method1")){
System.out.println("filter method1 ");
return "mmmmmmmmm";
}
if(method.getName().equals("method2")){
System.out.println("filter method2 ");
return 1111111;
}
if(method.getName().equals("method3")){
System.out.println("filter method3 ");
return 3333;
}
return "default";
}});
Method targetMethod1=object.getClass().getMethod("method3",new Class[]{int.class});
int i=(int)targetMethod1.invoke(object, new Object[]{33});
System.out.println(i);
Method targetMethod=object.getClass().getMethod("method1",new Class[]{String.class});
System.out.println(targetMethod.invoke(object, new Object[]{"sdfs"}));
}
}
使用了spring中的Import和ImportBeanDefinationRegistar技术来进行扩展。再对比一下@BeanSqlSessionFactoryBean就会知道spring会先去执行ImportBeanDefinationRegistar的registryBeanDefination方法。
在spring中都是mapperFactoryBean。
//spring结合mybatis之后这里获取的mapper实际上是jdk动态代理生成出来的。mybatis通过初始化一个类(MapperProxy),这个类实现InvocationHandler。
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.query();
mysql对每条记录的占用的最大空间有限制,占用的字节不超过65535个字节,除blob或者text类型的列之外,,其他所有的列(不包含隐藏列和记录头信息)。我们存储varchar(N)类型的列,需占用3部分的存储空间:
InnoDB一页大小为16384字节=16kb
当每行数据大小大于一页能够存储的数据时,就出现了页溢出。这个时候会存储一部分真实数据,再存储下一页的地址-----row format(行格式)为Compact
select时什么都不加,Innodb默认采用主键的值进行排序。聚集索引(主键索引)
InnoDB在插入数据的时候,会对页中的数据进行排序,并且相邻数据存在指针指向的关系。类似链表。因此查询的时候也就会有排序的现象
给定一组数列,如何快速的去查找指定的数字?
首先回去考虑排序
再去采用二分法去排序等等。
链表的结构在查询的时候会特别慢
因此在每一页的结构中会有一个pageRecord(页目录),Innodb会对页中的行进行分组,pagerecord中会记录分组中的数据行的第一条记录的主键id。页目录可以理解为一个数据递增的数组,存储了各个分组第一条记录的主键id。当要进行查找时,只需要利用二分法先去pagerecord中去找,然后再去对应分组中去找。
此时当数据行过多,需要分页,会出现由很多页的情况,现在去查找的时候,需要先确定数据在哪一页。因此同理需要存储每一页的页号和每一页的第一行数据。因此也就出现了和pagerecord一样的数据结构,这个数据结构存储了key为每一页的第一条记录的主键id,value存储了页号(指针)。
推导出的总体数据结构为:
此种数据结构叫做b+树。
当建表的时候,Innodb会初始化一个空页(根页),当插入数据时,会在这个空页(根页)插入数据行,当出现行溢出时,innodb会复制第一页,然后将溢出的数据行插入到另一页,然将之前的第一页(根页)改为目录页
还有一种特殊情况就是页的第一条数据可能相同,那么在排序的时候可能会更加复杂,因此innodb对页目录中还会存储主键即:
优先使用用户自定义的主键作为主键,若没有,则选取一个Unique健作为主键,若连Unique健都没有,Innodb会默认添加一个名为row-id的隐藏列为主键。Innodb会为每条记录都添加transaction-id(事务id)和roll-pointer(回滚id)列,但是row-id是可选的
mysql中的utf8占0-3字节,Java中占4个字节,mysql中的utf8md4才是和java中一样的。
create index indexname on table(column,...);
比较的规则是先比较第一个,再比较第二个,再比较第三个
建立索引后的b+树后的叶子节点结构为:
当查询的时候,如果是select *,innodb首先根据条件会去联合索引的树中找到和索引对应的主键,然后再去主键树中查找所有的元素。
索引的缺点:
create index ti_index on t1(c,d,e);
全值匹配联合索引字段
select * from t1 where c = 1 and d = 2 and e =3
如果我们的搜索条件中的列和索引中的列一致的话,这种情况就是全值匹配。
匹配左边的列
select * from t1 where c = 1
匹配列前缀
select * from t1 where c like ‘%101%‘
x
select * from t1 where c like ‘101%‘
v
需要注意的是若只给出后缀或者中间的某个字符串,mysql无法定位记录位置,因为字符串中间有101的字符串并没有排好序,所以只能全表扫描。有时候我们有一些匹配某些字符串后缀的需求,比如某一列存储了url
匹配范围值
所有记录都是按照索引从小到大排好序的
select * from t1 where c > 1 and d >5
上面查询又分为2部分:
这样对于联合索引来说,只能用到c列的部分,而用不到d列的部分,因为只有c值相同的时候才能用到d列的值进行排序,而这个查询中通过c进行范围查找的记录中可能并不是按照d列进行排序的,所以在搜索条件中继续以d列进行查找时用不到b+树索引
排序
查询时候的排序规则和b+树建立索引的规则是否一致
select * from t1 order by c,d,e
v
select * from t1 order by d,c,e
X
select * from t1 where c = 1 order by d,e
v,因为c=1的记录都是符合d,e的排序来的
select * from t1 order by c asc,d desc,e
X
考虑索引的选择性
索引选择性 = 基数/记录数
基数=记录去除重复值后的数量
记录数= 数据行数
选择性越高,代表索引的价值越大。
考虑前置索引
建立索引时只需要取某列的前几位
对于一个sql语句,查询优化器会先看是否能转换成join,再将join进行优化
优化分为:1.条件优化 2. 计算全表扫描成本 3. 找出所有能用到的索引4. 针对每个索引计算不同访问方式的成本 5.选出成本最小索引以及访问方式
开启
set optimizer_trace=‘enabled=on‘
执行sql
查看日志信息
select * from information_schema.OPTIMIZER_TRACE
mysql中真正核心的是join
select * from t1,t2
select * from t1 (inner) join t2
select * from t2 (inner) join t1
这3种情况相同
io成本
innodb存储引擎都是将数据和索引存储到磁盘上,当我们想要查询表的记录时,需要先将数据或者索引加载到内存后然后再操作。这个从磁盘到内存的加载过程消耗的时间叫做IO成本
cpu成本
读取以及检测记录是否满足对应的搜索条件、对结果集进行排序等操作消耗的时间称之为CPU成本
mysql会为每个表维护一系列统计信息,show Table STATUS
语句来查看表的统计信息,
统计信息是异步更新的
ANALYZE TABLE single_table
可以手动更新统计数据
SHOW INDEX FROM 表名
可以查看某个表中索引的统计信息
统计信息中rows列在myisam引擎中时真实的数据行的数量;而Innodb在是一个估值
datalength标识占用空间大小,对于myisam引擎来说,该值是数据文件大小,而对于Innodb存储引擎来说,该值,就相当于聚簇索引占用的存储空间。data_length = 聚簇索引(主键索引)页面数量*每个页面大小。而在innodb中,每个页面的大小默认为16kb。其单位是byte。
全表扫描成本:聚簇索引页数 乘 1+行数 乘 0.2
索引的统计信息中Cardinality属性表示索引列中不重复值的个数。对于Innodb来说这个值是估计值。
当where条件中in中的值少于200个,那么就会精确计算估计行数
当where条件中in中的值多于200个,那么计算成本如何计算?
首先根据数据行数/不重复值个数得到平均单个值的重复个数,用这个值去*in语句中的参数就可以得到成本
对于null,其实有三种理解:
innodb提供了一个系统变量:
show global variables like ‘%innodb_stats_method%‘;
这个变量有三个值:
最好不要在索引列中存放null值才是正解
null值在innodb中比任何值都要小,在索引树里面排在前面
mysql提供join-buffer内存空间,来缓存被驱动表,可以适当调整该内存空间大小,并且适当给驱动表增加索引就可以提高查询速度
将sql语句尽量优化成内连接,外连接可以通过增加条件转换成内连接,内连接能够提高查询速度。
1.物化表,当in语句的子查询不牵涉外表的时候,可以使用物化表,也就是将子查询的结果建立一个临时表
半连接(semi join)
直接将in语句转换成join语句
利用主键去重
逻辑上的去重
利用索引去重
什么情况下不能使用semi join进行优化
对于不能转为semi join优化的查询来说,可以使用其他方式进行优化
原子性、一致性、隔离性、持久性
show variables like ‘autocommit‘
是否自动提交set session transaction isolation level read uncommitted
设置隔离级别
查看隔离级别
select @@tx_isolation
一个事务可以读到另一个事务未提交的数据,容易出现脏读
脏读,一个事务读到另一个事务未提交已经修改过的数据。
一个事务读到另一个已经提交的事务修改过的数据,并且其他事务对该数据进行一次修改并提交后,该事务都能查到该值,会出现不可重复读、幻读
幻读,如果一个事务现根据某些条件查出了一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次查询时,能把另一个事务插入的记录也查出来。
一个事务第一次读过某条记录后,即使其他事务修改了该记录的值并且提交,该事务之后再读该条记录时,读到
的仍是第一次读到的值,而不是每次都读到不同的数据,这就是可重复读,这种隔离级别解决了不可重复,但是
还是会出现幻读。
mysql在这种隔离级别在底层解决了幻读的问题
不允许并发操作,读写加锁
readview中的数组(m_ids)是在select之后生成的,会保存没有提交或回滚的事务。(read commit的隔离级别)
普通的读操作不存在加读锁
select ... lock in share mode
加读锁
select ... for update
加写锁
使用普通索引查询加锁时,实际上会有间隙锁的限制,所以不会出现幻读,因为插入操作根本就插不进去。间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。
没有用到索引的情况。对所有的间隙和记录都加锁
select默认加一个version字段
标签:toc explain ack prot defer 原理 live parameter 隔离级别
原文地址:https://www.cnblogs.com/mwss/p/12238527.html