对于并发工作,你永远不知道一个线程何时运行,你需要某种方式来避免两个任务访问相同的资源,即要避免资源竞争,至少在关键代码上不能出现这样的情况,否则多个线程同时对某个内存区域操作会导致数据破坏。
程序代码中的临界区是需要互斥访问的,同一时刻只能有一个线程来访问临界区,也就是线程对临界区的访问时互斥的。
竞争条件:当多个线程同时访问某个共享的内存区域并且对其进行读写操作时,就会出现数据破坏。这就是竞争条件。避免竞争条件的方法是synchronized加锁。
样例,设有一个现成,该线程的任务是对共享变量count值+1。设count值的初始值为0,开辟1000个相同的线程,那么线程执行完后,count的值应该为1000(因为每个线程中都对count做了加一操作),但是实际效果会出现错误,这是因为出现了竞争条件。
解决办法:使用synchronized锁,
先定义一个object对象:
private final static Object lockObj = new Object();
下面即是同步块,在同步块中进行关键操作。
synchronized(lockObj) { syn.count = syn.count+1; temp = temp+1; }
示例代码:
package ex5_Synchronize; public class SynchronizeTest { //volatile方法可以保证每次都会去内存中读取变量的值,即遵守happen-befor原则,但是骑兵不能保证并发时数据不被破坏 public volatile static int temp = 0; private final static Object lockObj = new Object(); public static void main(String[] args) { // TODO Auto-generated method stub Thread threads[] = new Thread[1000]; for(int i=0;i<1000;i++) { threads[i] = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } //使用synchronize上锁,保证线程互斥访问 synchronized(lockObj) { syn.count = syn.count+1; temp = temp+1; } } }); threads[i].start(); } //调用join,让主线程等待当前线程执行完毕后再执行 for(int i = 0;i<1000;i++) { try { threads[i].join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //如果没有thread.join,那么其他线程还没执行完毕,主线程就要结束了,此时print输出的值并不是所有线程执行完后的值,所以不能说明问题 System.out.print("count的值:"+syn.count+" "); System.out.print("temp的值:"+temp); } }
输出结果
count的值:1000
temp的值:1000
-------------------------------------------------------------------------------
下面是正题
JAVA中synchronized关键字能够作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。假如再细的分类,synchronized可作用于instance变量、object reference(对象引用,例如this)、static函数和class literals(类名称字面常量)身上。下面讨论synchronized用到不同地方对代码产生的影响:
假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都能够调用他们。
1.把synchronized当作函数修饰符时,示例代码如下:
public synchronized void method(){
//….
}
这也就是同步方法,那这时synchronized锁定的是哪个对象呢?他锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,他们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却能够任意调用这个被加了synchronized关键字的方法。上边的示例代码等同于如下代码:
public void method()
{
synchronized (this) // (1)
{
//…..
}
}
(1)处的this指的是什么呢?他指的就是调用这个方法的对象,如P1。可见,同步方法实质是将synchronized作用于Object Reference。那个拿到了P1对象锁的线程,才能够调用P1的同步方法,而对P2而言,P1这个锁和他毫不相干,程式也可能在这种情形下摆脱同步机制的控制,造成数据混乱。
2.同步块,示例代码如下:
public void method(SomeObject so) {
synchronized(so)
{
//…..
}
}
这时,锁就是so这个对象,谁拿到这个锁谁就能够运行他所控制的那段代码。当有一个明确的对象作为锁时,就能够这样写程式,但当没有明确的对象作为锁,只是想让一段代码同步时,能够创建一个特别的instance变量(它得是个对象)来充当锁:
class Foo implements Runnable
{
private byte[] lock = new byte[0]; // 特别的instance变量
Public void method()
{
synchronized(lock) { //… }
}
//…..
}
注:零长度的byte数组对象创建起来将比任何对象都经济。查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
3.将synchronized作用于static 函数,示例代码如下:
Class Foo
{
public synchronized static void method1() // 同步的static 函数
{
//….
}
public void method2()
{
synchronized(Foo.class) // class literal(类名称字面常量)
}
}
代码中的method2()方法是把class literal作为锁的情况,他和同步的static函数产生的效果是相同的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class类,而不再是由这个Class产生的某个具体对象了)。
转载请注明:struts教程网 » synchronized的深刻认识
如何来定义共享变量:
1、在主类中定义一个final变量,让其在其他线程中也可以执行。
2、使用static定义线程公用的成员变量,在一个类实现runnable接口,其中定义一个static变量,这个变量就为所有使用此接口的类对象所共用,即所有的类对象共用一份这个变量的拷贝。
1、java中synchronize可以用来给对象、代码块等加锁。synchronize关键字可以出现在函数外,也可以出现在函数体
2、没有volatile就没有java多线程
3、Thread.join()可以设置让当前线程执行完毕后再执行其他线程,这在控制线程的执行顺序上很有用。
下面通过一个代码来描述java多线程中可能出现的资源竞争条件(race condition)问题。