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

一、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。,

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值