与之前写的Java随笔一样,此篇博客主要是用来记录我之前存放在本地的Word文档中的一些Java的自身理解,由于水平有限所以可能只适合自己加深理解与记忆。
目录
控制台使用java/javac直接运行/编译依赖其他jar包的类
ReentrantLock
ReentrantLock是可重入锁,可以使用它来实现公平锁和非公平锁。
公平锁 类似队列一样,当一个线程要获取锁的时候 发现有别的线程持有或者在等待这个锁,那么他就在队列排队,也就是如果是公平锁,那么锁的获取顺序应该符合请求的绝对的时间顺序,满足FIFO,new ReentrantLock(true) 构建公平锁。
非公平锁,获取锁的顺序是不固定的,有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。默认无参构造器,或者传入参数false都能构造非公平锁。
在ReentrantLock中有一个方法newCondition,可以创建条件变量,条件变量是为了将wait、notify、notifyAll等复杂而晦涩的同步操作转为直观可控的对象行为而设计的。具体使用方法与应用我们可以从ArrayBlockingQueue类的源码中找到。如下:
ArrayBlockingQueue的构造函数以及两个条件变量的声明
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
可以看到,构造函数中我们初始化了一个可重入锁,并且使用newCondition初始化了两个条件变量。
然后我们看take()函数的源码
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
可以看到首先我们调用lock.lockInterruptibly() 表示此线程可以被中断,然后会在判断当前队列元素是0的时候,调用条件变量notEmpty的await()方法,类似Object的wait(),表示等待唤醒,这样我们调用take()如果队列为空,那么就一直阻塞,直到notEmpty条件触发为止。那么是如何唤醒的呢,我们看enqueue方法源码就能明白。
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
可以看到再有元素入栈之后,调用了notEmpty.signal(),这样就会将之前阻塞的take()方法的线程唤醒。
一定要注意Condition的await()和signal()的配套使用。不然假设只有 await 动作,线程就会一直等待直到被打断(interrupt)。
ReentrantLock和Synchronized的比较
用法
ReentrantLock使用比较灵活,但是必须手动获取/释放锁,Synchronized不需要手动获取/释放锁,ReentrantLock只适用于代码块锁,而Synchronized可以用于修饰方法、代码块等。
特性比较
ReentrantLock的优势在于具备尝试非阻塞地获取锁的特性:当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁。与synchronized不同,获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。可以设置超时获取锁,在指定的时间范围内获取锁;如果截止时间到了仍然无法获取锁,则返回(使用tryLock传递参数即可)。
使用ReentrantLock的注意事项
在finally释放锁,这是为了保证获取锁之后,最终能释放锁
不要将获取锁的过程写在try块内,因为如果在获取锁时发生了异常,异常抛出的同时,也会导致锁无故被释放。
ReentrantLock提供了newCondition方法,便于用户在同一锁的情况下,可以根据不同的情况执行等待或唤醒动作,condition要注意await和signal配套使用。
transient关键字
表示瞬态,是类型修饰符,只能用来修饰字段,在对象序列化的过程中,标记为transient的变量不会被序列化。
volatile关键字
易变的,只能修饰变量,volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
但是 不能因此就认为使用volatile关键字修饰的变量就线程安全了,比如多个线程都一直操作一个变量i,进行i++ 还是会出现值与预期不一样的情况,因为它只是保证线程看到的是最新的值,但是对于i++ 实际上是 两步操作 计算 i+1 然后 赋值给 i,所以当两个线程交叉执行i+1 此时看到的是最新的值 然后赋值给i 那么就会丢失其中的一次 i+1操作值,相当于少加一次。
通过Java内存机制解读volatile关键字
Java使用一个主内存来保存变量当前值,而每个线程则有其独立的工作内存。线程访问变量的时候会将变量的值拷贝到自己的工作内存中,这样,当线程对自己工作内存中的变量进行操作之后,就造成了工作内存中的变量拷贝的值与主内存中的变量值不同。
Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。
这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。
而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。
对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。
典型的例子
for(int i=0; i<100000; i++);
这个语句用来测试空循环的速度的
但是编译器可能会把它优化掉,根本就不执行
如果你写成
for(volatile int i=0; i<100000; i++);
它就会真正执行。
使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。由于使用volatile屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。
TreadLocal
ThreadLocal内部维护着一个map,这个map的key是线程,value是需要在多线程(多用户)中使用的东西。在Struts2中,不同ActionContext就存在一个ThreadLocl中。
String的intern()方法
我们知道String在我们创建字符串的时候,会先在字符串常量池检查是否有相等的字符串,如果有就返回字符串常量池中的对象。但是如果我们直接使用new String()的方式来创建对象,那么就不会走字符串常量池了。但是如果在创建字符串时候使用intern(),那么如果常量池中包括了一个等于此String对象的字符串(由equals方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并且返回此池中对象的引用。
如下:
String a = "aa";
String b ="aa";
String c ="a";
String d =("a"+c);
String e ="a"+"a";
System.out.println(a==b);
System.out.println(a==d);
System.out.println(a==e);
我们很容易知道,这样会首先输出true,然后输出false和true。这说明在直接使用String a = "";这种形式,那么会先走字符串常量池,所以在b="aa" 会使用字符串常量池中缓存的赋给b。因此a==b会得出true,但是使用"a"+c 会导致重新创建字符串。但是我们如e这种直接使用两个"a"这种字符串常量来拼接就不会创建新的字符串。
使用intern()我们能让d和a代表同一个对象。
String a = "aa";
String b ="aa";
String c ="a";
String d =("a"+c).intern();
String e ="a"+"a";
System.out.println(a==b);
System.out.println(a==d);
System.out.println(a==e);
我们发现会输出3个true,说明使用intern()会从字符串常量池中取得对象,如果已经存在那么就将常量池中的对象引用赋给d,不存在则放入常量池之后返回引用。
Lamad表达式
Java8 里面,lamad 表达式 可以用于替换继承接口的匿名类如下一样:
如一个list排序,如果使用匿名内部类的方式:
list.sort(new Comparator<String>(){
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
使用lamad表达式 则是这样的 list.sort((c,d)->{return c.compareTo(d);});
可以看到还是很简单的。但是要注意,使用lamad表达式替换的匿名内部类实现的接口只能有一个未实现的方法!!!!(可以有其他用default或者static修饰并给出了默认实现的方法)
Java1.8接口新增default/static方法
Java1.8中接口允许有default或者static修饰的实现方法,所以在Java8中接口和抽象类的不同之处不再有 接口不能有具体方法实现而抽象类可以有这种不同了。
public interface BB {
public void run();
default void run2(){};
static void run1(){
System.out.println("ffff");
};
}
控制台使用java/javac直接运行/编译依赖其他jar包的类
有时候没有开发工具,但是要使用java 或者 javac 执行或者编译一些类,但是这些类又依赖jar包 可以使用(当然直接将依赖的jar包加入到classpath里面)
javac –cp D:\yy.jar,D:\dd.jar test.java
java –cp D:\yy.jar,D:\dd.jar test
上面就是依赖的jar包设置。
但是有时候有好多jar包要引用那么 我们就可以这样
javac –Djava.ext.dirs=D:\lib test.java
java –Djava.ext.dirs=D:\lib test
可以看到 我们仅仅设置一个jar包的保存目录就可以了。
struts2个人理解
工作流程
初始的请求经过过滤器链,到达Servlet容器,然后调用FilterDispatcher,FilterDispatcher查找ActionMapper(对应某个Action)来确定请求是否调用Action,如果确定调用Action,则FilterDispatcher将控制权交给ActionProxy,然后ActionProxy依照Struts2的配置文件(struts.xml等action配置信息)找到对应的类,ActionProxy生成创建一个ActionInvocation实例,ActionInvocation先调用有关的拦截器方法(Action调用之前部分),最后调用Action,Action返回结果,则ActionInvocation根据配置文件寻找对应的路径,接着按照反次序先执行拦截器方法(Action调用之后部分),最后通过过滤器链返回。(如果过滤器链中存在ActionContextCleanUp,FilterDispatcher 不会清理线程局部的ActionContext。如果不存在ActionContextCleanUp 过滤器,FilterDispatcher 会清除所有线程局部变量。)
对Struts2的理解,请求经过过滤器链,然后查找是否有对应的Action,如果有则Dispatcher调用方法,得到ActionProxy,这个ActionProxy用代理的方式,将拦截器以及Action调用的真实方法执行,最后返回对应路径。
Struts2的Action不要求实现或继承任何接口/类,只是在使用过程中想使用Struts2提供的一些默认辅助方法才继承ActionSupport(为了重写validate来进行参数值的验证)。
过滤器拦截器监听器
感觉过滤器就是对请求进行一系列操作的,如国际化等,拦截器就是在调用一些方法的时候,会想在它的前面或者后面调用其他方法而使用(用到了反射)。而监听器,就是在启动的时候(或者什么时候)想监听记录一些东西。过滤器用的是函数回调方式,而拦截器用的是反射机制,过滤器能过滤一切请求,拦截器只能拦截Action。
得到原生HttpServletRequest
得到原生的HttpServletRequest可以使用org.apache.struts2.ServletActionContext中的静态方法得到。
封装请求参数的方式
a. (实体类封装参数)使用实体类来封装。即在先建立实体类如User,然后User封装属性,在Action里面声明一个User对象如u,在JSP参数处参数名设置为u.参数名(注意!这个u一定要与Action中对象名相对应)
b. (模型驱动封装参数)action继承ActionSupport并且实现ModelDriven接口。首先建立实体类,再Action中声明并实例化实体类对象。在Action中实现ModelDriven的getModel方法,返回自定义的Model类对象(实例化的对象)(这里注意实体类的参数名与JSP传递的参数名对应,并且必须实例化实例类的对象)。
c. (属性驱动接收参数)也就是在Action中提供对应属性的get/set方法(注意Action中参数名和JSP中参数名一定一致)。
参数值的验证方法
a). Action继承ActionSupport类,并重写validate方法,然后用里面的addActionError或者addFieldError,之后在JSP页面引入/struts-tags 标签库,再用<s:actionerror/>等标签来接收显示错误。
b). 在Action同目录下建立Action名+(-validation)的xml文件(如Action是HelloAction.java则这个验证文件为HelloAction-validation.xml),里面设置验证信息。之后在JSP页面引入/struts-tags 标签库,再用<s:actionerror/>等标签来接收显示错误。