全面解析Synchronized

在多线程的环境下,常常存在线程安全问题,这种问题产生的缘由在于:该是原子操做的代码段被其余的线程切割,从而引发的数据混乱问题。在本篇博客中将讲述如何使用synchronized关键字保证代码段的原子操做。java

synchronized关键字

无论synchronized以何种方式使用,都会对一个对象加锁,这个对象也就是所谓的监视器安全

synchronized关键字具备一下特征:多线程

(1)若是对持有相同锁的synchronized方法或者代码块,同步执行(即排队,执行完一个,另外一个才能执行)异步

(2)若是对持有不一样锁的synchronized方法或者代码块,异步执行ide

(3)synchronized与非synchronized方法或者代码块,异步执行性能

synchronized方法

使用synchronized来修饰方法(非static方法),其实就是this对象加锁测试

下面经过synchronized方法来看一下上述的三个特征优化

首先定义一个操做类MyObject,在类中有三个方法init,alter,print,其中init,alter方法使用synchronized修饰,代码以下:
this

package com.feng.example;

public class MyObject {
	
	synchronized public void init()
	{
		
		try {
			System.out.println(Thread.currentThread().getName()+"init begin");
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getName()+"init end");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	synchronized public void alter()
	{
		try {
			System.out.println(Thread.currentThread().getName()+"alter begin");
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getName()+"alter end");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	
	public void printInfo()
	{
		try {
			System.out.println(Thread.currentThread().getName()+"print begin");
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getName()+"print end");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	

}

定义三个线程,分别对MyObject进行处理:spa

package com.feng.example;

public class MyThreadA extends Thread{

	private MyObject object;
	
	public MyThreadA(MyObject object)
	{
		this.object = object;
	}
	
	@Override
	public void run() {
		
		object.init();
	}	
}

package com.feng.example;

public class MyThreadB extends Thread{

private MyObject object;
	
	public MyThreadB(MyObject object)
	{
		this.object = object;
	}
	
	@Override
	public void run() {

		object.alter();
	}
	
}

package com.feng.example;

public class MyThreadC extends Thread{

private MyObject object;
	
	public MyThreadC(MyObject object)
	{
		this.object = object;
	}
	
	@Override
	public void run() {

		object.printInfo();
	}
	
}

测试类以下:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		MyObject object1 = new MyObject();
		MyObject object2 = new MyObject();
		
		Thread a = new MyThreadA(object1);
		Thread b = new MyThreadB(object1);
		Thread c = new MyThreadC(object1);
		a.start();
		b.start();
		c.start();
		
		
	}

}

分析:在测试类中,三个线程都是以实例化对象object1做为参数,那么三个类处理的都是object1对象,由于MyThreadA,MyThreadB处理的object1对象的同步方法,他们持有的监视器都是object1对象,所以二者应该是同步的,即排队执行,MyThreadC处理的是非synchronized方法,所以和另外两个线程是异步执行的。因此init方法,和alter方法是顺序执行(即一个执行完,另外一个在执行,可是谁在前取决于谁先抢到object1的锁),print方法和init,alter方法是异步执行,能够在任意位置输出。

程序运行结果以下:

Thread-2print begin
Thread-0init begin
Thread-2print end
Thread-0init end
Thread-1alter begin
Thread-1alter end

从本例中说明对持有相同锁的多个线程在执行synchronized方法时同步执行。synchronized方法与非synchronized方法异步执行。

下面修改测试类,观察若是线程处理的对象不是一个对象会出现什么状况:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		MyObject object1 = new MyObject();
		MyObject object2 = new MyObject();
		
		Thread a = new MyThreadA(object1);
		Thread b = new MyThreadB(object2);
		
		a.start();
		b.start();	
		
	}

}

分析:线程a操做的对象是object1,线程b操做的对象是object2。另外一层意思,线程a执行init方法是获取的是object1对象的锁,线程b执行alter方法时获取的是object2对象的锁。虽然两个方法都是synchronized方法,可是二者想要获取的锁不一样,所以也就不存在同步问题。

执行结果以下:

Thread-0init begin
Thread-1alter begin
Thread-0init end
Thread-1alter end

从结果中能够看出,线程0中init的操做被线程1的alter操做分隔了,说明二者异步执行。

从而证实了synchronized方法持有的是this对象锁。对持有不一样锁对象的synchronized方法异步执行。

经过上面的实验证实了synchronized关键字的3个特征。

synchronized的可重入性

(1)同步方法中调用其余持有相同监视器的同步方法

修改操做类,代码以下:

package com.feng.example;

public class MyObject {
	
	synchronized public void init()
	{
		
		try {
			System.out.println(Thread.currentThread().getName()+"====init begin");
			Thread.sleep(2000);
			alter();
			System.out.println(Thread.currentThread().getName()+"====init end");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	synchronized public void alter()
	{
		try {
			System.out.println(Thread.currentThread().getName()+"====alter begin");
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getName()+"====alter end");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	
	public void printInfo()
	{
		try {
			System.out.println(Thread.currentThread().getName()+"====print begin");
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getName()+"====print end");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	

}

测试类以下:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		try {
			MyObject object1 = new MyObject();
			
			Thread a = new MyThreadA(object1);
			Thread b = new MyThreadB(object1);
			
			a.start();
			Thread.sleep(1000);
			b.start();	
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}

}

分析:实例化操做类对象object1,建立两个线程a,b,a线程启动,执行init方法,线程a获取了object1对象的锁,在init方法方法中睡眠2s,执行init方法1s后启动线程b,b企图得到object1对象锁,因为被线程a占有,因此须要等待。2s事后init方法中调用alter方法,须要获取object1对象的锁,如今线程a已经得到了object1对象的锁,能够直接进入alter方法,这就叫课重入性。执行完alter方法以后,线程a释放锁,线程b再执行alter方法。

输出结果以下:

Thread-0====init begin
Thread-0====alter begin
Thread-0====alter end
Thread-0====init end
Thread-1====alter begin
Thread-1====alter end

(2)子类调用父类的同步方法

建立父类:

package com.feng.example;

public class Person {
	
	synchronized public void eat()
	{
		System.out.println("person eat");
	}

}

建立子类:

package com.feng.example;

public class Student extends Person{
	
	synchronized public void study()
	{
		System.out.println("student study");
		super.eat();
		System.out.println("study end");
	}

}

测试类:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
	
			Student student = new Student();
			
			Thread a = new MyThread(student);
			
			a.start();
		
		
	}

}

分析:启动线程a,a获取了student的锁,执行study方法,在study方法中调用父类的eat方法,不须要从新获取锁便可进入同步方法。

执行结果以下:

student study
person eat
study end

synchronized抛出异常,自动释放锁

修改操做类:

package com.feng.example;

public class MyObject {
	
	synchronized public void init()
	{
		
		try {
			System.out.println(Thread.currentThread().getName()+"====init begin");
			Thread.sleep(2000);
			throw new InterruptedException();
		} catch (InterruptedException e) {
			System.out.println("抛出异常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	synchronized public void alter()
	{
		try {
			System.out.println(Thread.currentThread().getName()+"====alter begin");
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getName()+"====alter end");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}	
}

线程类使用上面的MyThreadA,MyThreadB,此处再也不列出

测试类以下:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
			
			try {
				MyObject object = new MyObject();
				
				Thread a = new MyThreadA(object);
				Thread b = new MyThreadB(object);
				
				a.start();
				Thread.sleep(1000);       //此处是要保证线程a先执行
				b.start();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} 
			
	}

}

分析:线程a执行首先获取object对象锁,执行init方法,执行完init时抛出异常,若是线程a不释放锁,那么线程b就不会执行。只要线程b执行了,就说明抛出异常,线程锁释放。

运行结果以下:

Thread-0====init begin
抛出异常
java.lang.InterruptedException
	at com.feng.example.MyObject.init(MyObject.java:11)
	at com.feng.example.MyThreadA.run(MyThreadA.java:15)
Thread-1====alter begin
Thread-1====alter end

说明线程a抛出异常时,释放了线程锁

synchronized方法不具备继承性

不具备继承性主要体如今方法的重写上,对父类的同步方法进行重写,若是不加synchronized关键字就不是同步方法

经过程序验证上述结论:

package com.feng.example;

public class Person {
	
	synchronized public void eat()
	{
		System.out.println("person eat");
	}

}

package com.feng.example;

public class Student extends Person{

	public void eat() {
		
		try {
			System.out.println("student eat...");
			Thread.sleep(4000);
			System.out.println("student eat end...");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	synchronized public void study()
	{
			
		try {
			System.out.println("student study...");
			Thread.sleep(2000);
			System.out.println("student study end...");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}

定义两个线程:

package com.feng.example;

public class StudentThreadA extends Thread{
	
	private Student student;
	
	public StudentThreadA(Student student)
	{
		this.student = student;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		student.eat();
	}

}

package com.feng.example;

public class StudentThreadB extends Thread{
	
	private Student student;
	
	public StudentThreadB(Student student)
	{
		this.student = student;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		student.study();
	}	

}

测试类:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
			
		Student student = new Student();
		Thread a = new StudentThreadA(student);
		Thread b = new StudentThreadB(student);
		
		a.start();
		b.start();
			
	
	}

}

执行结果以下:

student eat...
student study...
student study end...
student eat end...

分析:从运行结果中能够看出两个方法并非同步输出的,所以重写的eat方法不是同步方法。

synchronized语句块

synchronized方法的弊端

使用synchronized方法,存在明显的性能问题,在一个方法中可能只有几个语句须要同步,使用synchronized方法却使整个方法都同步了。执行速度确定会慢,咱们能够经过synchronized语句块来优化程序。

举例说明synchronized方法的性能问题。

定义操做类:

package com.feng.example;

public class MyObject {
	
	private String data1;
	private String data2;
	
	synchronized public void init()
	{
		
		try {
			System.out.println(Thread.currentThread().getName()+"====init begin");
			Thread.sleep(2000);
			data1 = "长时间的处理后从服务端获取的值data1";   //模拟从服务端取数据
			data2 = "长时间的处理后从服务端获取的值data2";
			System.out.println(data1);
			System.out.println(data2);
			System.out.println(Thread.currentThread().getName()+"====init end");
		} catch (InterruptedException e) {
			System.out.println("抛出异常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

定义线程类:

package com.feng.example;

public class MyThread extends Thread {
	
	private MyObject object;
	
	public MyThread(MyObject object)
	{
		this.object = object;
	}

	@Override
	public void run() {
		
		System.out.println(Thread.currentThread().getName()+":开始运行时间为:"+System.currentTimeMillis());
		object.init();
		System.out.println(Thread.currentThread().getName()+":结束时间为:"+System.currentTimeMillis());
	}
}

测试类:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
			
		MyObject object = new MyObject();
		
		Thread a = new MyThread(object);
		Thread b = new MyThread(object);
		
		a.start();
		b.start();
			
	
	}

}

分析:在测试类中定义了两个线程实例a,b  这两个线程都以object实例做为参数,对object进行操做。首先线程a执行,输出线程a的开始执行时间,执行object对象的init方法,init方法是同步方法。在执行init的同时,线程b也启动了,输出线程b的开始执行时间,这时线程b企图获取object对象的锁,因为如今object的锁被线程a占用,因此必须等待线程a执行完init方法以后才会得到。线程a休眠2秒,这2s主要是模拟长时间的操做(假设就是从服务端获取数据),给成员变量赋值,打印两个成员变量,运行完成,释放锁。输出线程a的结束时间,线程b执行object对象的init的放,进行一些列的操做。

运行结果以下:

Thread-1:开始运行时间为:1449471254353
Thread-1====init begin
Thread-0:开始运行时间为:1449471254366
长时间的处理后从服务端获取的值data1
长时间的处理后从服务端获取的值data2
Thread-1====init end
Thread-1:结束时间为:1449471256364
Thread-0====init begin
长时间的处理后从服务端获取的值data1
长时间的处理后从服务端获取的值data2
Thread-0====init end
Thread-0:结束时间为:1449471258365

查看这两个线程都完成操做所用的总时间就是Thread-0的结束时间减去Thread-1开始运行时间:1449471258365-1449471254353 = 4012 花费了大约4s的时间。可是想服务端获取数据彻底能够异步去执行,这样就能够减小执行时间了。

修改操做对象MyObject的代码,使用synchronized语句块

package com.feng.example;

public class MyObject {
	
	private String data1;
	private String data2;
	
	public void init()
	{
		
		try {
			System.out.println(Thread.currentThread().getName()+"====init begin");
			Thread.sleep(2000);
			String data1Temp = "长时间的处理后从服务端获取的值data1"+Thread.currentThread().getName();   //模拟从服务端取数据
			String data2Temp = "长时间的处理后从服务端获取的值data2"+Thread.currentThread().getName();
			synchronized(this)
			{
				data1 = data1Temp;
				data2 = data2Temp;
			}
			System.out.println(data1);
			System.out.println(data2);
			System.out.println(Thread.currentThread().getName()+"====init end");
		} catch (InterruptedException e) {
			System.out.println("抛出异常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

其线程类与测试类代码不变,分析程序:

在测试类中建立两个线程实例a,b 线程a与线程b同时执行object的init方法,因为此方法是非synchronized方法,所以,两只异步执行,(二者谁先执行到synchronized语句块不肯定)假设线程a先执行到synchronized语句块,线程a得到了进入synchronized语句块的锁,当线程b执行到synchronized语句块时,尝试获取锁,但要获得线程a执行完synchronized语句块,线程a执行完synchronized语句块后释放锁,线程b获取锁执行synchronized语句块的内容。

执行结果以下:

Thread-1:开始运行时间为:1449472151572
Thread-1====init begin
Thread-0:开始运行时间为:1449472151577
Thread-0====init begin
长时间的处理后从服务端获取的值data1Thread-1
长时间的处理后从服务端获取的值data2Thread-1
Thread-1====init end
Thread-1:结束时间为:1449472153572
长时间的处理后从服务端获取的值data1Thread-0
长时间的处理后从服务端获取的值data2Thread-0
Thread-0====init end
Thread-0:结束时间为:1449472153577

从执行结果中能够看出,执行的总时间大约为2s,相比于使用synchronized方法快了不少。程序的性能获得了提高

总结:synchronized语句块就是将同步的内容最小化,使须要同步的信息放到synchronized语句块中同步执行,不须要同步的信息放到synchronized语句块外异步执行。

synchronized语句块的锁对象,this,synchronized方法,class的区别

synchronized语句块的锁对象能够是任意的对象,不论是什么对象只要记住文章开头synchronized关机字的特征便可。

下面比较一下this,synchronized方法,class做为锁的区别:

首先synchronized(this){ } 使用的锁对象就是操做类自己,不如上例中在线程类中调用的是object.init()方法,说明init方法中的this指的就是object对象本身,所以使用this做为锁和synchronized方法没有本质的区别,区别只在于synchronized(this){ }能够将同步的范围缩小,是同步的内容更加精确,提升程序的性能。

sychronized(MyObject.class){ },是将class对象做为锁,同一个类的实例都只有一个class文件,class文件是惟一的,也就是说若是使用class做为锁,即便调用的是此类不一样实例对象的方法,都会呈现出同步的效果。

举例说明:

定义操做类MyObject

package com.feng.example;

public class MyObject {
		
	public void init()
	{
	
		try {
			synchronized(this)
			{
				System.out.println(Thread.currentThread().getName()+"====init begin");
				Thread.sleep(2000);		
				System.out.println(Thread.currentThread().getName()+"====init end");
			}
		} catch (InterruptedException e) {
			System.out.println("抛出异常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

定义线程类:

package com.feng.example;

public class MyThread extends Thread {
	
	private MyObject object;
	
	public MyThread(MyObject object)
	{
		this.object = object;
	}

	@Override
	public void run() {
		
		object.init();
	}
}

测试类代码以下:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
			
		MyObject object1 = new MyObject();
		MyObject object2 = new MyObject();
		
		Thread a = new MyThread(object1);
		Thread b = new MyThread(object2);
		
		a.start();
		b.start();
			
	
	}

}

分析:因为在MyObject类中使用的是this做为锁,在测试类中定义了两个MyObject对象object1,object2,实例化线程时使用两个不一样的对象做为参数。线程a调用的是object1.init()方法,线程b调用的是object2.init()方法。两个线程在执行object.init()方法时,this表明的就不是一个对象,因此是异步执行。

运行结果以下:

Thread-0====init begin
Thread-1====init begin
Thread-0====init end
Thread-1====init end

下面修改MyObject操做类的代码:将synchronized(this){ } 改成synchronized(MyObject.class){ }

package com.feng.example;

public class MyObject {
		
	public void init()
	{
	
		try {
			synchronized(MyObject.class)
			{
				System.out.println(Thread.currentThread().getName()+"====init begin");
				Thread.sleep(2000);		
				System.out.println(Thread.currentThread().getName()+"====init end");
			}
		} catch (InterruptedException e) {
			System.out.println("抛出异常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

分析:两个线程a,b  线程a执行的是object1.init()方法,线程b执行的object2.init()方法,在执行到synchronized(MyObject.class){ }时,谁先执行到,谁就得到此类文件的class锁,因为object1和object2的class文件是同一个,所以两个线程在synchronized语句块处同步执行。

程序运行结果以下:

Thread-0====init begin
Thread-0====init end
Thread-1====init begin
Thread-1====init end

synchronized语句块其余对象做为锁

只要是对象均可以做为锁,修改上例中的MyObject类:

package com.feng.example;

public class MyObject {
	
	private String lock = new String();
	
	public void init()
	{
	
		try {
			synchronized(lock)
			{
				System.out.println(Thread.currentThread().getName()+"====init begin");
				Thread.sleep(2000);		
				System.out.println(Thread.currentThread().getName()+"====init end");
			}
		} catch (InterruptedException e) {
			System.out.println("抛出异常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

分析:两个线程共用一个object对象,object中lock是object的成员变量,也就是说两个线程共用一个锁。

所以两个线程在synchronized语句块处同步执行。

运行结果以下:

Thread-0====init begin
Thread-1====init begin
长时间的处理后从服务端获取的值data1Thread-0
长时间的处理后从服务端获取的值data2Thread-0
Thread-0====init end
长时间的处理后从服务端获取的值data1Thread-1
长时间的处理后从服务端获取的值data2Thread-1
Thread-1====init end

特殊状况,字符串做为锁对象

字符串通常是存放在常量池中的,若是两个字符串表示的内容同样,两个字符串指向的是同一个对象。举例说明:

修改操做类MyObject代码

package com.feng.example;

public class MyObject {

	
	public void init()
	{
		String str = "我是锁";
	
		try {
			synchronized(str)
			{
				System.out.println(Thread.currentThread().getName()+"====init begin");
				Thread.sleep(2000);		
				System.out.println(Thread.currentThread().getName()+"====init end");
			}
		} catch (InterruptedException e) {
			System.out.println("抛出异常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

分析:在init方法中str是一个局部变量,在方法内部局部变量都有本身的临时空间,各个方法互不干扰,按常理来讲,两个线程a,b应该想获取的锁不是同一个锁,可是这里是String类型,String类型的内容是放在常量池中的,在本程序中,str虽然是方法内的局部变量,但在不一样的方法中局部变量都是指向的同一个对象,所以表示的是同一个锁。

所以不建议使用字符串做为锁对象

程序运行结果以下:

Thread-0====init begin
Thread-0====init end
Thread-1====init begin
Thread-1====init end

静态同步方法

synchronized方法对应synchronized(this){ }语句块,一样的synchronized static 方法对应synchronized(class){ }语句块

静态同步方法的使用

举例说明:修改上述程序的操做类代码:

package com.feng.example;

public class MyObject {

	
	synchronized static public void init()
	{
	
		try {
			
			System.out.println(Thread.currentThread().getName()+"====init begin");
			Thread.sleep(2000);		
			System.out.println(Thread.currentThread().getName()+"====init end");
		} catch (InterruptedException e) {
			System.out.println("抛出异常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

分析:两个线程虽然执行的是不一样对象的init方法,可是init方法是static的synchronized方法,两个对象的class文件是同一个,所以两个线程同步执行(与synchronized(MyObject.class){ }一个效果),运行结果以下

Thread-0====init begin
Thread-0====init end
Thread-1====init begin
Thread-1====init end

class锁与对象锁异步执行

定义操做类MyObject

package com.feng.example;

public class MyObject {

	synchronized static public void init() {

		try {

			System.out.println(Thread.currentThread().getName()
					+ "====init begin");
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getName()
					+ "====init end");
		} catch (InterruptedException e) {
			System.out.println("抛出异常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	synchronized public void print() {
		try {

			System.out.println(Thread.currentThread().getName()
					+ "====print begin");
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getName()
					+ "====print end");
		} catch (InterruptedException e) {
			System.out.println("抛出异常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

定义两个线程类:

package com.feng.example;

public class MyThreadA extends Thread{

	private MyObject object;
	
	public MyThreadA(MyObject object)
	{
		this.object = object;
	}
	
	@Override
	public void run() {
		
		object.init();
	}	
}

package com.feng.example;

public class MyThreadB extends Thread{

private MyObject object;
	
	public MyThreadB(MyObject object)
	{
		this.object = object;
	}
	
	@Override
	public void run() {

		object.print();
	}
	
}

测试类以下:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
			
		MyObject object = new MyObject();
		
		Thread a = new MyThreadA(object);
		Thread b = new MyThreadB(object);
		
		a.start();
		b.start();
			
	
	}

}

运行结果以下:

Thread-1====print begin
Thread-0====init begin
Thread-1====print end
Thread-0====init end

从运行结果能够看出,class锁与对象锁不是同一个锁,二者异步执行

调用多个synchronized方法的脏读问题

若是在程序中连续调用几个synchronized方法,其中存在分支判断就会出现脏读的问题,下面举例说明:

定义一个只能存一个数据的对象MyObject,判断当数据的个数<1时添加元素,其中获取元素个数,添加元素都为synchronized方法。

定义操做类:

package com.feng.example;

import java.util.ArrayList;
import java.util.List;

public class MyObject {

	private List<String> list = new ArrayList<String>();
	
	synchronized  public void add() {
		
		list.add("xu");

	}

	synchronized  public int size() {
		
		return list.size();

	}
}

定义线程类:

package com.feng.example;

public class MyThread extends Thread {
	
	private MyObject object;
	
	public MyThread(MyObject object)
	{
		this.object = object;
	}

	@Override
	public void run() {
		
		try {
			//保证object中的list中只有一个数据
			if(object.size() < 1)
			{
				
				Thread.sleep(2000);	
				object.add();
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

测试类以下:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	
	public static void main(String[] args) {
			
		MyObject object = new MyObject();
		
		Thread a = new MyThread(object);
		Thread b = new MyThread(object);
		
		a.start();
		b.start();
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(object.size());
			
	
	}

}

分析:两个线程a,b在执行时,线程a执行完size( )执行add()方法前释放锁,此时,线程b获取锁执行了size()方法,两个线程同时进入了if(object.size()<1)的语句块,两个线程均可以往object对象中添加数据,因此添加了两条数据,然而咱们定义的确是只能保存一个数据的对象。所以程序出现了问题。

程序运行结果以下:

2

修改程序将线程类的run方法中使用synchronized语句块。

修改线程类:

package com.feng.example;

public class MyThread extends Thread {
	
	private MyObject object;
	
	public MyThread(MyObject object)
	{
		this.object = object;
	}

	@Override
	public void run() {
		
		try {
			synchronized(object)
			{
				//保证object中的list中只有一个数据
				if(object.size() < 1)
				{
					
					Thread.sleep(2000);	
					object.add();
				}
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

程序运行结果:

1

锁对象的改变

在程序的运行过程当中,锁对象有可能会发生变化,当锁对象发生变化时会出现什么状况呢?

定义操做类:

package com.feng.example;

public class MyObject {

	private String str = "hahah";
	 public void init() {

		try {

			synchronized(str)
			{
				System.out.println(Thread.currentThread().getName()
					+ "====init begin");
				str = "hehhe";    //锁对象发生了变化
				Thread.sleep(2000);
				System.out.println(Thread.currentThread().getName()
					+ "====init end");
			}
		} catch (InterruptedException e) {
			System.out.println("抛出异常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
}

定义线程类:

package com.feng.example;

public class MyThread extends Thread {
	
	private MyObject object;
	
	public MyThread(MyObject object)
	{
		this.object = object;
	}

	@Override
	public void run() {
		
		object.init();
	}
}

定义测试类:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	
	public static void main(String[] args) {
			
		MyObject object = new MyObject();
		
		Thread a = new MyThread(object);
		Thread b = new MyThread(object);
		
		a.start();
		b.start();
	
	}

}

分析:线程a,b执行同一个对象的object.init( )方法,二者谁先执行到synchronized(str)是不肯定的,假设线程a先到,那么a获取到str对象锁,执行synchronized语句块,接着修改了锁对象str = "hehhe",此时线程b执行到语句块synchronized(str),这里str执行的是hehhe了,不在是hahah,二者不是同一个锁,所以会异步执行

执行结果:

Thread-1====init begin
Thread-0====init begin
Thread-1====init end
Thread-0====init end

修改操做类MyObject对象的代码:

package com.feng.example;

public class MyObject {

	private String str = "hahah";
	 public void init() {

		try {

			synchronized(str)
			{
				System.out.println(Thread.currentThread().getName()
					+ "====init begin");
				Thread.sleep(2000);
				str = "hehhe";    //锁对象发生了变化
				System.out.println(Thread.currentThread().getName()
					+ "====init end");
			}
		} catch (InterruptedException e) {
			System.out.println("抛出异常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
}

分析:线程a,b执行同一个对象的object.init( )方法,二者谁先执行到synchronized(str)是不肯定的,假设线程a先到,线程a获取了str的锁,而后休眠2s,在执行str="hehhe"以前,线程b执行到synchronized(str),二者抢的是同一个对象的锁,所以会同步执行。

运行结果以下;

Thread-0====init begin
Thread-0====init end
Thread-1====init begin
Thread-1====init end

总结:当线程运行到synchronized语句块是,锁对象是哪个对象,就会保持哪个对象的锁。

相关文章
相关标签/搜索