您的位置:首页 > 其它

全面解析Synchronized

2015-12-07 00:00 197 查看
摘要: 正确的使用synchronized关键字,避免多线程间的线程安全问题

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

synchronized关键字

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

synchronized关键字具有一下特征:

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

(2)如果对持有不同锁的synchronized方法或者代码块,异步执行

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

synchronized方法

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

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

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

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进行处理:

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语句块是,锁对象是哪一个对象,就会保持哪一个对象的锁。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息