1、事务
事务指的是逻辑上的一组操作,这组操作要么全部成功,要么全部失败。
事务的4大特性:
ACID,指数据库事务正确执行的四个基本要素的缩写。包含:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
详见:http://blog.csdn.net/scythe666/article/details/51790655
2、悲观锁和乐观锁
秒懂乐观锁和悲观锁
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。
原文:http://blog.csdn.net/hongchangfirst/article/details/26004335
3、非阻塞同步算法与CAS(Compare and Swap)无锁算法
要实现无锁(lock-free)的非阻塞算法有多种实现方法,其中CAS(比较与交换,Compare and swap)是一种有名的无锁算法。CAS, CPU指令,在大多数处理器架构,包括IA32、Space中采用的都是CAS指令。
CAS的语义是“我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少”,CAS是项乐观锁技术。
当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。CAS无锁算法的C实现如下:
int compare_and_swap (int* reg, int oldval, int newval)
{
ATOMIC();
int old_reg_val = *reg;
if (old_reg_val == oldval)
*reg = newval;
END_ATOMIC();
return old_reg_val;
}
CAS比较与交换的伪代码可以表示为:
do{
备份旧数据;
基于旧数据构造新数据;
}while(!CAS( 内存地址,备份的旧数据,新数据 ))
(上图的解释:CPU去更新一个值,但如果想改的值不再是原来的值,操作就失败,因为很明显,有其它操作先改变了这个值。)
就是指当两者进行比较时,如果相等,则证明共享数据没有被修改,替换成新值,然后继续往下运行;如果不相等,说明共享数据已经被修改,放弃已经所做的操作,然后重新执行刚才的操作。容易看出 CAS 操作是基于共享数据不会被修改的假设,采用了类似于数据库的 commit-retry 的模式。当同步冲突出现的机会很少时,这种假设能带来较大的性能提升。
详见:http://www.cnblogs.com/Mainz/p/3546347.html?utm_source=tuicool&utm_medium=referral
4、JVM内存模型
JVM内存模型分区见下图:
(1)方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。对于习惯在HotSpot 虚拟机上开发和部署程序的开发者来说,很多人愿意把方法区称为“永久代”(Permanent Generation),本质上两者并不等价。
(2)堆区:Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。由于逃逸分析等分析或优化技术,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。
Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”
(3)栈区:与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。栈中主要存放一些基本类型的变量(,int, short, long, byte, float,double, boolean, char)和对象句柄。
(4)程序计数器:程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
(5)本地方法栈:本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。
详见:http://blog.csdn.net/scythe666/article/details/51700142
5、Java反射机制
什么是反射
一般在开发针对java语言相关的开发工具和框架时使用,比如根据某个类的函数名字,然后执行函数,实现类的动态调用!
反射实例:
package test;
import java.lang.reflect.*;
/**
* 基类Animal
*/
class Animal {
public Animal() {
}
public int location;
protected int age;
private int height;
int length;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
/**
* 子类 —— Dog
*/
class Dog extends Animal {
public Dog() {
}
public String color;
protected String name;
private String type;
String fur;
}
public class test {
public static void main(String[] args) {
System.out.println("Hello World!");
testRefect();
}
private static void testRefect() {
try {
//返回类中的任何可见性的属性(不包括基类)
System.out.println("Declared fileds: ");
Field[] fields = Dog.class.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
System.out.println(fields[i].getName());
}
//获得类中指定的public属性(包括基类)
System.out.println("public fields: ");
fields = Dog.class.getFields();
for (int i = 0; i < fields.length; i++) {
System.out.println(fields[i].getName());
}
//getMethod()只能调用共有方法,不能反射调用私有方法
Dog dog = new Dog();
dog.setAge(1);
Method method1 = dog.getClass().getMethod("getAge", null);
Object value = method1.invoke(dog);
System.out.println("age: " + value);
//调用基类的private类型方法
/**先实例化一个Animal的对象 */
Animal animal = new Animal();
Class a = animal.getClass();
//Animal中getHeight方法是私有方法,只能使用getDeclaredMethod
Method method2 = a.getDeclaredMethod("getHeight", null);
method2.setAccessible(true);
//java反射无法传入基本类型变量,可以通过如下形式
int param = 12;
Class[] argsClass = new Class[] { int.class };
//Animal中getHeight方法是共有方法,可以使用getMethod
Method method3 = a.getMethod("setHeight", argsClass);
method3.invoke(animal, param);
//Animal中height变量如果声明为static变量,这样在重新实例化一个Animal对象后调用getHeight(),还可以读到height的值
int height = (Integer) method2.invoke(animal);
System.out.println("height: " + height);
/**不用先实例化一个Animal,直接通过反射来获得animal的class对象*/
Class anotherAnimal = Class.forName("test.Animal");
//Animal中getHeight方法是私有方法,只能使用getDeclaredMethod
Method method4 = anotherAnimal.getDeclaredMethod("getHeight", null);
method4.setAccessible(true);
//java反射无法传入基本类型变量,可以通过如下形式
int param2 = 15;
Class[] argsClass2 = new Class[] { int.class };
//Animal中setHeight方法是共有方法,可以使用getMethod
Method method5 = anotherAnimal.getMethod("setHeight", argsClass2);
method5.invoke(anotherAnimal.newInstance(), param2);
//Animal中height变量必须声明为static变量,这样在重新实例化一个Animal对象后调用getHeight()才能读到height的值
// 否则重新实例化一个新的Animal对象,读到的值为初始值
int height2 = (Integer) method4.invoke(anotherAnimal.newInstance());
System.out.println("height:" + height2);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
详见:http://blog.csdn.net/scythe666/article/details/51704809
6、类加载机制
深度好文:http://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html
类加载器负责加载 Java 类的字节代码到 Java 虚拟机中。
类名.class是什么用法
反射相关知识,类名.class是获得这个类所对应的Class实例
从面向对象的角度上来看,一个类也是对象,它们是类这个类的对象,听起来有些抽象,但是在java中的实现就是所有的加载进来的类在虚拟机中都是一个java.lang.Class类的对象,而“类名.class”就是获得这个类的对象(在同一个ClassLoader中,类对象都是单例的)
当虚拟机载入某个class文件时,首先生成该class文件对应的类的Class对象,用这个对象描述类
7、Reactor和Proactor模式
Reactor读取操作:
(1)应用程序注册读就绪事件和相关联的事件处理器
(2)事件分离器等待事件的发生
(3)当发生读就绪事件的时候,事件分离器调用第一步注册的事件处理器
(4)事件处理器首先执行实际的读取操作,然后根据读取到的内容进行进一步的处理
写入操作类似于读取操作,只不过第一步注册的是写就绪事件。
Proactor读取操作:
(1)应用程序初始化一个异步读取操作,然后注册相应的事件处理器,此时事件处理器不关注读取就绪事件,而是关注读取完成事件,这是区别于Reactor的关键。
(2)事件分离器等待读取操作完成事件
(3)在事件分离器等待读取操作完成的时候,操作系统调用内核线程完成读取操作(异步IO都是操作系统负责将数据读写到应用传递进来的缓冲区供应用程序操作,操作系统扮演了重要角色),并将读取的内容放入用户传递过来的缓存区中。这也是区别于Reactor的一点,Proactor中,应用程序需要传递缓存区。
(4)事件分离器捕获到读取完成事件后,激活应用程序注册的事件处理器,事件处理器直接从缓存区读取数据,而不需要进行实际的读取操作。
Proactor中写入操作和读取操作,只不过感兴趣的事件是写入完成事件。
从上面可以看出,Reactor和Proactor模式的主要区别就是真正的读取和写入操作是有谁来完成的:
(1)Reactor中需要应用程序自己读取或者写入数据
(2)而Proactor模式中,应用程序不需要进行实际的读写过程,它只需要从缓存区读取或者写入即可,操作系统会读取缓存区或者写入缓存区到真正的IO设备。
综上所述,同步和异步是相对于应用和内核的交互方式而言的,同步 需要主动去询问,而异步的时候内核在IO事件发生的时候通知应用程序,而阻塞和非阻塞仅仅是系统在调用系统调用的时候函数的实现方式而已。
详见:
http://blog.csdn.net/sshhbb/article/details/6331954
http://blog.csdn.net/scythe666/article/details/51811565
8、长连接 短连接
详情见:http://blog.chinaunix.net/uid-354915-id-3587924.html
(1)长连接就是Client和Server连接建立以后,不断开,直接进行报文的发送和接收。常用于点对点通讯
(2)短连接是每进行一次报文收发交易,才进行通讯连接。交易完毕后立即断开连接。此种方式常用于一点对多点通讯,比如多个Client连接一个Server。
9、线程池
在Executors类里面提供了一些静态工厂,生成一些常用的线程池。
(1)newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
MyThread.java
package threadpool;
/**
* Created by hupo.wh on 2016/7/2.
*/
public class WhThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行...");
}
}
TestSingleThreadExecutor.java
package threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by hupo.wh on 2016/7/3.
*/
public class TestSingleThreadExecutor {
public static void main(String[] args) {
//创建一个可重用固定线程数的线程池
ExecutorService pool = Executors. newSingleThreadExecutor();
//创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口
Thread t1 = new WhThread();
Thread t2 = new WhThread();
Thread t3 = new WhThread();
Thread t4 = new WhThread();
Thread t5 = new WhThread();
//将线程放入池中进行执行
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
//关闭线程池
pool.shutdown();
}
}
输出结果:
pool-1-thread-1正在执行…
pool-1-thread-1正在执行…
pool-1-thread-1正在执行…
pool-1-thread-1正在执行…
pool-1-thread-1正在执行…
(2)newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
//创建一个可重用固定线程数的线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
(3)newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
//创建一个可重用固定线程数的线程池
ExecutorService pool = Executors.newCachedThreadPool();
(4)newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
package threadpool;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Created by hupo.wh on 2016/7/3.
*/
public class TestSingleThreadExecutor {
public static void main(String[] args) {
ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1);
exec.scheduleAtFixedRate(new Runnable() {//每隔一段时间就触发异常
@Override
public void run() {
System.out.println("================");
throw new RuntimeException();
}
}, 1000, 5000, TimeUnit.MILLISECONDS);
exec.scheduleAtFixedRate(new Runnable() {//每隔一段时间打印系统时间,证明两者是互不影响的
@Override
public void run() {
System.out.println(System.nanoTime());
}
}, 1000, 2000, TimeUnit.MILLISECONDS);
}
}
ThreadPoolExecutor构造函数
jvm本身提供的concurrent并发包,提供了高性能稳定方便的线程池,可以直接使用。
ThreadPoolExecutor是核心类,都是由它与3种Queue结合衍生出来的。
BlockingQueue + LinkedBlockingQueue + SynchronousQueue
ThreadPoolExecutor的完整构造方法的签名是:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) .
corePoolSize - 池中所保存的线程数,包括空闲线程。
maximumPoolSize-池中允许的最大线程数。
keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit - keepAliveTime 参数的时间单位。
workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务。
threadFactory - 执行程序创建新线程时使用的工厂。
handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
ThreadPoolExecutor是Executors类的底层实现。
在JDK帮助文档中,有如此一段话:
“强烈建议程序员使用较为方便的Executors工厂方法Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)Executors.newSingleThreadExecutor()(单个后台线程)
线程池实现原理
先从 BlockingQueue<Runnable> workQueue
这个入参开始说起。在JDK中,其实已经说得很清楚了,一共有三种类型的queue。
所有BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:
(1)如果运行的线程少于 corePoolSize,则 Executor始终首选添加新的线程,而不进行排队。(如果当前运行的线程小于corePoolSize,则任务根本不会存放,添加到queue中,而是直接抄家伙(thread)开始运行)
(2)如果运行的线程等于或多于 corePoolSize,则 Executor始终首选将请求加入队列,而不添加新的线程。
(3)如果无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。
线程的状态有 new、runnable、running、waiting、timed_waiting、blocked、dead 一旦线程调用了start 方法,线程就转到Runnable 状态,注意,如果线程处于Runnable状态,它也有可能不在运行,这是因为还有优先级和调度问题。
排队策略
排队有三种通用策略:
(1)直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
(2)无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
(3)有界队列。当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
详参:http://www.oschina.net/question/565065_86540
10、接口中的成员变量
用final定义出来的是常量.如public static final double CM=2.54;
如果楼主在接口定义里看到的是类似如上的定义的话,这是一个常量,可以通过接口继承直接取值.
最后,接口中声明的变量会被编译器和解释器自动当作public static final类型,也就是常量.所以在接口里声明的变量都要初始化
11、Java中的序列化与反序列化
详见:http://blog.csdn.net/scythe666/article/details/51718784