一.什么是程序的原子性
举个例子:
A想要从自己的帐户中转1000块钱到B的帐户里。那个从A开始转帐,到转帐结束的这一个过程,称之为一个事务。在这个事务里,要做如下操作:
1. 从A的帐户中减去1000块钱。如果A的帐户原来有3000块钱,现在就变成2000块钱了。
2. 在B的帐户里加1000块钱。如果B的帐户如果原来有2000块钱,现在则变成3000块钱了。
如果在A的帐户已经减去了1000块钱的时候,忽然发生了意外,比如停电什么的,导致转帐事务意外终止了,而此时B的帐户里还没有增加1000块钱。那么,我们称这个操作失败了,要进行回滚。回滚就是回到事务开始之前的状态,也就是回到A的帐户还没减1000块的状态,B的帐户的原来的状态。此时A的帐户仍然有3000块,B的帐户仍然有2000块。
我们把这种要么一起成功(A帐户成功减少1000,同时B帐户成功增加1000),要么一起失败(A帐户回到原来状态,B帐户也回到原来状态)的操作叫原子性操作。
如果把一个事务可看作是一个程序,它要么完整的被执行,要么完全不执行。这种特性就叫原子性]
二.volatile和synchronized
volatile关键字相信了解Java多线程的读者都很清楚它的作用。volatile关键字用于声明简单类型变量,如int、float、boolean等数据类型。如果这些简单数据类型声明为volatile,对它们的操作就会变成原子级别的。但这有一定的限制。例如,下面的例子中的n就不是原子级别的:
public class JoinThread extends Thread
{
public static volatile int n = 0;
public void run()
{
for (int i = 0; i < 10; i++)
try
{
n = n + 1;
sleep(3); // 为了使运行结果更随机,延迟3毫秒
}
catch (Exception e)
{
}
}
public static void main(String[] args) throws Exception
{
Thread threads[] = new Thread[100];
for (int i = 0; i < threads.length; i++)
// 建立100个线程
threads[i] = new JoinThread();
for (int i = 0; i < threads.length; i++)
// 运行刚才建立的100个线程
threads[i].start();
for (int i = 0; i < threads.length; i++)
// 100个线程都执行完后继续
threads[i].join();
System.out.println("n=" + JoinThread.n);
}
}
如果对n的操作是原子级别的,最后输出的结果应该为n=1000,而在执行上面积代码时,很多时侯输出的n都小于1000,这说明n=n+1不是原子级别的操作。原因是声明为volatile的简单变量如果当前值由该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:
n = n + 1;
n++;
如果要想使这种情况变成原子操作,需要使用synchronized关键字,如上的代码可以改成如下的形式:
public class JoinThread extends Thread
{
public static int n = 0;
public static synchronized void inc()
{
n++;
}
public void run()
{
for (int i = 0; i < 10; i++)
try
{
inc(); // n = n + 1 改成了 inc();
sleep(3); // 为了使运行结果更随机,延迟3毫秒
}
catch (Exception e)
{
}
}
public static void main(String[] args) throws Exception
{
Thread threads[] = new Thread[100];
for (int i = 0; i < threads.length; i++)
// 建立100个线程
threads[i] = new JoinThread();
for (int i = 0; i < threads.length; i++)
// 运行刚才建立的100个线程
threads[i].start();
for (int i = 0; i < threads.length; i++)
// 100个线程都执行完后继续
threads[i].join();
System.out.println("n=" + JoinThread.n);
}
}
上面的代码将n=n+1改成了inc(),其中inc方法使用了synchronized关键字进行方法同步。因此,在使用volatile关键字时要慎重,并不是只要简单类型变量使用volatile修饰,对这个变量的所有操作都是原来操作,当变量的值由自身的上一个决定时,如n=n+1、n++等,volatile关键字将失效,只有当变量的值和自身上一个值无关时对该变量的操作才是原子级别的,如n = m + 1,这个就是原级别的。所以在使用volatile关键时一定要谨慎,如果自己没有把握,可以使用synchronized来代替volatile。
三.搞懂java中的synchronized关键字
1、synchronized关键字的作用域有二种:
1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象;
3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
4、疑问:
我知道了有static区块(其中的加载时机好像也不清楚,需要学习一下,原来看Thinking in Java好像是说: static区块加载的时机是类的一个对象实例创建或类的一个static方法被访问,但是我今天早上看了javaeye.com上面的一个关于“<static块到底什么时候执行?-eway -JavaEye技术社区>”的文章后又有点迷糊了:),也许,需要认真学习一下JVM的内在机制才是理解这类问题最根本的途径),也有synchronized区块,那么,有synchronized static 区块吗?意思是问:有没有一个类作用域内的synchronized区块?
5.下面这段说明了1.1中的
是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)
public class ThreadTest4
{
public static void main(String[] args)
{
Counter1 c = new Counter1();
Counter1 c1 = new Counter1();
C3 c3 = new C3(c, "c3");
// C4 c4 = new C4(c1, "c4"); 如果用这段代码替换下面的 最后结果就可能在1000上下
C4 c4 = new C4(c, "c4");
c3.start();
c4.start();
}
}
class Counter1
{
private int count = 1000;
protected int getCount()
{
return count;
}
public void setCount(int count)
{
this.count = count;
}
public synchronized void addCounter(int i, String sname)
{
while (this.getCount() > 1020)
{
try
{
this.wait();
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
int j = this.getCount();
System.out.println(sname + "第 " + i + " 次 before is " + j);
this.setCount(j + 1);
this.notifyAll();
System.out.println(sname + "第 " + i + " 次 after " + this.getCount());
}
public synchronized void delCounter(int i, String sname)
{
while (this.getCount() < 970)
{
try
{
this.wait();
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
int j = this.getCount();
System.out.println(sname + "第 " + i + " 次 before is " + j);
this.setCount(j - 1);
this.notifyAll();
System.out.println(sname + "第 " + i + " 次 after " + this.getCount());
}
}
class C4 extends Thread
{
private Counter1 count;
private String sname;
public C4(Counter1 count, String sname)
{
this.count = count;
this.sname = sname;
}
public Counter1 getCount()
{
return count;
}
public void setCount(Counter1 count)
{
this.count = count;
}
public String getSname()
{
return sname;
}
public void setSname(String sname)
{
this.sname = sname;
}
public void AddCounter(int i)
{
count.setCount(i);
}
public void run()
{
for (int i = 0; i < 100; i++)
{
this.count.delCounter(i, sname);
}
}
}
class C3 extends Thread
{
private Counter1 count;
private String sname;
public C3(Counter1 count, String sname)
{
this.count = count;
this.sname = sname;
}
public Counter1 getCount()
{
return count;
}
public void setCount(Counter1 count)
{
this.count = count;
}
public String getSname()
{
return sname;
}
public void setSname(String sname)
{
this.sname = sname;
}
public void run()
{
for (int i = 0; i < 100; i++)
{
this.count.addCounter(i, sname);
}
}
}