原子类
JAVASE5 引入了诸如:AtomicInteger、AtomicLong、AtomicRenference等原子性变量,他们提供下面形式的原子性条件更新:
boolean cpmpareAndSet(exceptedValue,updateValue);
在常规编程中很难看见,在涉及性能调优的时候就有用武之地了。
public class AtomicIntegerTest implements Runnable {
private AtomicInteger a = new AtomicInteger(0);
public int getVal() {
return a.get();
}
public void incr() {
a.addAndGet(2);
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
incr();
}
}
public static void main(String[] args) {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
System.out.println("timer running");
System.exit(0);
}
}, 5000); //定义一个定时器 来终止这个程序
ExecutorService exec = Executors.newCachedThreadPool();
AtomicIntegerTest at = new AtomicIntegerTest();
exec.execute(at);
exec.shutdown();//停止线程的提交,线程执行完之后尽快退出
while (true) {
int a = at.getVal();
if(a%2!=0){
System.out.println("found a error val:"+a);
}
}
}
}
Atomic类是用来构建 juc 中的类的,只有在特殊的情况下才在自己的代码中使用他们,通常依赖于锁要更安全一些。(要么是synchronized 关键字要么是显示的Lock锁)
通过同步控制块,而不是对整个方法的进行同步,可以使多个任务访问对象的时间性能得到显著的提高。
下面是synchronized同步整个方法和同步代码块的性能比较
public class Pair {
private int x;
private int y;
public Pair(int x, int y) {
super();
this.x = x;
this.y = y;
}
public Pair() {
this(0, 0);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void incrX() {
x++;
}
public void incrY() {
y++;
}
@SuppressWarnings("serial")
public class NotEqualsException extends RuntimeException {
@Override
public String toString() {
return "Pair X、Y not equals :pair:" + this;
}
}
public void check() {
if (x != y) {
throw new NotEqualsException();
}
}
@Override
public synchronized String toString() {
return "Pair [x=" + x + ", y=" + y + "]";
}
}
public abstract class PairManager {
/**
* 记录check的次数
*/
AtomicInteger checkTime = new AtomicInteger(0);
protected Pair p = new Pair(); // 继承可得
/**
* 保存Pair对象
*/
private List<Pair> storge = Collections.synchronizedList(new ArrayList<Pair>());
public synchronized Pair getPair() {
return new Pair(p.getX(), p.getY()); // 这里返回的是获取那一时刻的对象的副本
}
public void store(Pair p) {
storge.add(p);
/**
* list线程不安全的原因: 一个 ArrayList ,在添加一个元素的时候,它可能会有两步来完成: 1. 在 Items[Size]
* 的位置存放此元素; 2. 增大 Size 的值。 在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且
* Size=1; 而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B
* 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0
* (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加
* Size 的值。 那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于
* 2。这就是“线程不安全”了。
*/
}
public abstract void increment();
@Override
public String toString() {
return "PairManager [checkTime=" + checkTime + ", pX=" + p.getX() + ",pY=" + p.getY() + "]";
}
}
public class PairManager1 extends PairManager {
@Override
public synchronized void increment() {
// TODO Auto-generated method stub
p.incrX();
p.incrY();
store(getPair());
}
}
public class PairManager2 extends PairManager {
@Override
public void increment() {
Pair temp;
synchronized (this) {
p.incrX();
p.incrY();
temp=getPair();
}
store(temp);
}
}
public class PairManager3 extends PairManager {
private ReentrantLock lock=new ReentrantLock();
@Override
public void increment() {
// TODO Auto-generated method stub
lock.lock();
try{
p.incrX();
p.incrY();
store(getPair());
}finally {
lock.unlock();
}
}
}
public class PairManager4 extends PairManager {
private ReentrantLock lock = new ReentrantLock();
@Override
public void increment() {
// TODO Auto-generated method stub
Pair temp;
lock.lock();
try {
p.incrX();
p.incrY();
temp = getPair();
} finally {
lock.unlock();
}
store(temp);
}
}
public class PairManipulator implements Runnable {
private PairManager pm;
public PairManipulator(PairManager pm) {
super();
this.pm = pm;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
pm.increment();
try {
TimeUnit.MILLISECONDS.sleep(10); //这里加这段代码是为了不让循环执行的太快导致线程栈溢出!!
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class PairChecker implements Runnable {
private PairManager pm;
public PairChecker(PairManager pm) {
super();
this.pm = pm;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
pm.checkTime.incrementAndGet();
pm.getPair().check();
}
}
}
public class CriticalSection {
static void test(PairManager p1, PairManager p2, PairManager p3, PairManager p4) {
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
// TODO Auto-generated method stub
System.out.println("Thread: " + t.getName() + " Exception:" + e);
}
});
ExecutorService exec = Executors.newFixedThreadPool(8);
PairManipulator pm1 = new PairManipulator(p1), pm2 = new PairManipulator(p2), pm3 = new PairManipulator(p3),
pm4 = new PairManipulator(p4);
PairChecker pc1 = new PairChecker(p1), pc2 = new PairChecker(p2), pc3 = new PairChecker(p3),
pc4 = new PairChecker(p4);
exec.execute(pm1);
exec.execute(pm2);
exec.execute(pm3);
exec.execute(pm4);
exec.execute(pc1);
exec.execute(pc2);
exec.execute(pc3);
exec.execute(pc4);
exec.shutdown();
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("p1:" + p1 + "\np2:" + p2 + "\np3:" + p3 + "\np4:" + p4);
System.exit(0);
}
public static void main(String[] args) {
PairManager p1 = new PairManager1(), p2 = new PairManager2(), p3 = new PairManager3(), p4 = new PairManager4();
test(p1, p2, p3, p4);
}
}
以上图片的输出不是一定是这个趋势的 ,这样测试有点不准。建议p1与p2测试,p3与p4测试,这样大概准一些。(从输出可以看出 检查的次数越多,越代表锁占用的时间越少)
一般的电脑运行这个程序多半都会报 线程栈溢出
接下来做一下eclipse的jvm参数配置:(参数设置的很暴力)
比较 synchonized 整个方法和 synchonized 代码块的对比:
大概可以看出 两个之间的性能