您的位置:首页 > 编程语言 > Java开发

[java多线程]多线程同步(一)——synchronized

2014-08-24 14:55 423 查看
一、synchronized修饰方法

synchronized关键字可以用来修饰并保护我们在类中定义的方法,保证在某一时刻某个方法只有一个线程能访问,

对于普通成员方法,synchronized实际上是对这个类的某个对象实例加锁,等效于synchronized(this){...}。

1、一个方法同步无效的例子

线程类定义:

/**
* 这里我们在run()方法中加入了synchronized关键字,希望能对run方法进行互斥访问,但结果并不如我们希望那样,
* 这是因为这里synchronized锁住的是this对象,即当前运行线程对象本身。
* 代码中创建了3个线程,而每个线程都持有this对象的对象锁,这不能实现线程的同步。
*/
public class MyThread implements Runnable {
private int threadId;

public MyThread(int id) {
this.threadId = id;
}

@Override
public synchronized void run() {
String threadName = Thread.currentThread().getName();
for (int i = 0; i < 5; i++) {
System.out.println("threadName: " + threadName + ", Thread ID: " + this.threadId + ", i: " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

}
测试类定义:

public class Test {

public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; ++i) {
new Thread(new MyThread(i)).start();
Thread.sleep(1);
}
}

}
执行结果可以看到,三个线程还是在并发的执行,并没有因为run()方法加了synchronized修饰就同步了,

原因自然就是synchronized普通方法实际上是对对象自身的this加锁,三个线程是三个独立的对象,有三个不同的this,锁当然就无效了。

2、修饰普通方法

2.1. 还是用synchronized修饰run()方法

线程类定义:

public class MyThread implements Runnable {

@Override
public synchronized void run() {
String threadName = Thread.currentThread().getName();
for (int i = 0; i < 5; i++) {
System.out.println("threadName: " + threadName + ", i: " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

}


看到和刚才的线程并没有什么不同,仍然是对run()方法加锁;

测试类定义:

public class Test {

public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
for (int i = 0; i < 3; ++i) {
new Thread(myThread).start();
Thread.sleep(1);
}
}

}

执行结果是一个线程的run()方法执行完毕,下一个线程的run()方法才会执行,

因为三个线程都共用一个myThread对象,加锁的对象是同一个this,自然就能正确同步了。

2.2.
用synchronized修饰其它方法

线程类定义:

public class MyThread implements Runnable {

@Override
public void run() {
otherMethodOne();
otherMethodTwo();
}

private synchronized void otherMethodOne() {
String threadName = Thread.currentThread().getName();
for (int i = 0; i < 5; i++) {
System.out.println("threadName: " + threadName + ", otherMethodOne, i: " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

private synchronized void otherMethodTwo() {
String threadName = Thread.currentThread().getName();
for (int i = 0; i < 5; i++) {
System.out.println("threadName: " + threadName + ", otherMethodTwo, i: " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

}


我们看到,run()方法调用另外两个同步方法otherMethodOne()和otherMethodTwo(),每个其它同步方法内部将循环打印几次,

测试类定义:

public class Test {

public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
for (int i = 0; i < 3; ++i) {
new Thread(myThread).start();
Thread.sleep(1);
}
}

}
执行结果显示,一个线程的run()方法一个同步方法执行完毕,不一定紧接着执行第二个同步方法,而很有可能执行其他线程的某个同步方法,

但同步方法执行的时候,不会同时执行别的同步方法的打印语句,

可见,只对synchronized修饰的方法内部实现加锁,同步方法执行完毕之后,锁就被释放,其它线程也就有机会竞争得到这个锁;

但如果测试类如下方式执行线程:

public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; ++i) {
new Thread(new MyThread()).start();
Thread.sleep(1);
}
}
可想而知,同步又会失效……

3、修饰静态方法

对于静态成员方法,synchronized实际上是对这个类加锁,等效于synchronized(ClassName.class){...}。

线程类定义:

public class MyThread implements Runnable {
private int threadId;

public MyThread(int id) {
this.threadId = id;
}

@Override
public void run() {
otherMethodOne(this.threadId);
otherMethodTwo(this.threadId);
}

private static synchronized void otherMethodOne(int threadId) {
String threadName = Thread.currentThread().getName();
for (int i = 0; i < 5; i++) {
System.out.println("threadName: " + threadName + ", Thread ID: " + threadId + ", otherMethodOne, i: " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

private static synchronized void otherMethodTwo(int threadId) {
String threadName = Thread.currentThread().getName();
for (int i = 0; i < 5; i++) {
System.out.println("threadName: " + threadName + ", Thread ID: " + threadId + ", otherMethodTwo, i: " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}


说明:run()方法可不能static...所以我们将其他方法变成静态方法;

测试类定义:

public class Test {

public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; ++i) {
new Thread(new MyThread(i)).start();
Thread.sleep(1);
}
}

}
说明:

这里是三个独立的MyThread对象,执行结果可以看到一个静态方法执行完毕,另外的静态方法才会执行,

没有用下面的形式,之前就讨论过了,还用得着静态方法么?费那事干嘛……

MyThread myThread = new MyThread(...);
for (int i = 0; i < 3; ++i) {
new Thread(myThread).start();
Thread.sleep(1);
}


二、synchronized修饰成员对象

1、修饰基本数据类型,行不行?

看下面的线程类定义:



显然在编译时就会提示不行了,

对于这种情况我们可以用基本数据类型对应的包装类,比如这里把变量num换成Integer类型的就可以了,编译就不报错了,

但是真的就可以按照我们预想的执行么?我们接着往下看...

2、修饰普通成员对象

修饰普通成员对象,synchronized对这个成员对象加锁。

2.1. 我们用Integer对象来试试

线程类定义:

public class MyThread implements Runnable {

private Integer num; // int is not work...

public MyThread(Integer num) {
this.num = num;
}

@Override
public void run() {
String threadName = Thread.currentThread().getName();
for (int i = 0; i < 5; i++) {
synchronized (num) {
System.out.println("threadName: " + threadName + ", num: " + num++);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}

}
测试类定义:

public class Test {

public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread(0);
for (int i = 0; i < 3; ++i) {
new Thread(myThread).start();
Thread.sleep(1);
}
}
}
从执行结果可以看到num变量是原子的方式增加的,num从0增长到14,没有发生混乱;

那换成下面这样的测试类定义呢?

public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; ++i) {
new Thread(new MyThread(0)).start();
Thread.sleep(1);
}
}
执行结果每个线程中num都从0开始自增,显然这个不是我们想看到的,

调用类的构造方法时,直接赋值成0不行,那我们换成下面这样的测试类定义呢?

public static void main(String[] args) throws InterruptedException {
Integer num = new Integer(0);
for (int i = 0; i < 3; ++i) {
new Thread(new MyThread(num)).start();
Thread.sleep(1);
}
}
还是一样,执行结果每个线程中num都从0开始自增,为什么不行呢?

有网上的朋友给出这样的解释,转帖过来:

对于

Integer j = new Integer(10);

j++等同于j = new Integer(j + 1);

用下面的代码可以证明上面的结论:

Integer j = new Integer(10);
// Different hash code will be printed.
System.out.println(System.identityHashCode(j));
j++;
System.out.println(System.identityHashCode(j));
j = new Integer(j + 1);
System.out.println(System.identityHashCode(j));


2.2. 我们用自定义的bean来试试

自定义bean如下:

public class MyBean {
int num;

public MyBean(int num) {
this.num = num;
}

public int getNum() {
return num;
}

public void setNum(int num) {
this.num = num;
}

}

线程类定义:

public class MyThread implements Runnable {

private MyBean bean;

public MyThread(MyBean bean) {
this.bean = bean;
}

@Override
public void run() {
String threadName = Thread.currentThread().getName();
for (int i = 0; i < 5; i++) {
synchronized (bean) {
int num = bean.getNum();
System.out.println("threadName: " + threadName + ", num: " + num);
bean.setNum(num + 1);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}

}



测试类定义:
public class Test {
public static void main(String[] args) throws InterruptedException {
MyBean bean = new MyBean(0);
for (int i = 0; i < 3; ++i) {
new Thread(new MyThread(bean)).start();
Thread.sleep(1);
}
}
}
执行结果可以看出,bean中的num从0增长到14,我们想要的共享和同步的效果就是这样。

3、修饰静态对象

修饰静态成员对象,synchronized对这个静态成员对象加锁,

因为java的同一个类的多个对象共用同一个静态成员对象,所以能保证多线程同步的正常工作。

还是刚才的bean定义:

public class MyBean {
int num;

public MyBean(int num) {
this.num = num;
}

public int getNum() {
return num;
}

public void setNum(int num) {
this.num = num;
}

}
线程类定义:

public class MyThread implements Runnable {

private static MyBean bean = new MyBean(0);

@Override
public void run() {
String threadName = Thread.currentThread().getName();
for (int i = 0; i < 5; i++) {
synchronized (bean) {
int num = bean.getNum();
System.out.println("threadName: " + threadName + ", num: " + num);
bean.setNum(num + 1);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}

}
测试类定义:

public class Test {
public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 3; ++i) { new Thread(new MyThread()).start(); Thread.sleep(1); } }
}
从执行结果可以看到bean中的num从0增长到14。,


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: