线程同步指多个线程同时访问某资源时,采用一系列的机制以保证同时最多只能一个线程访问该资源。
为什么需要线程同步呢?
我们举一个最简单的例子来说明为什么需要线程同步。
比如有一本书(有且只有一本),交给多个售货员同时去卖;
如果其中任何一个售货员把这本书给卖了,其他售货员就不能再卖这本书了。
现实生活中,如果要保证该书不会被多个售货员同时卖掉,必须要有一种机制来保证:
比如,售货员应该拿到该书之后才能开始卖书,暂时拿不到的话就只能等该书被退回柜台。
售书的完整的例子可以参考
范例解说Java里的线程概念与线程同步技术
一文
这里,每一个售货员售书可以看作一个线程。欲售的书便是各线程需要共享的资源。
开始售书之前,需要取得该书(资源),取不到情况下等待:
资源取得
开始售书之后,则需要取得对该书的独享控制(不让他人拿到该书):
资源加锁
售完书时,需要通知柜台该书已售出;或者未售出时,把书退回柜台(通知他人可以拿到该书):
资源解锁
synchronized控制线程同步的概念跟此完全一样。
Java里可以使用synchronized来同步代码块或者方法。
同步代码块例:
- synchronized(欲同步的对象obj) {
- 需要同步的代码块
- }
synchronized(欲同步的对象obj) {
需要同步的代码块
}
可以同步代码块。
synchronized (obj)
表示若多个线程同时访问时,只让其中一个线程最先取得obj对象并对其加锁,其它线程则阻塞直到取得obj对象的线程执行完代码块,此时被加锁的obj对象得到释放(解锁),其它线程得到通知取得该book对象继续执行。
很多情况下,可以使用synchronized
(this){...}来同步代码块。但需要注意的是,使用this作为同步对象的话,如果同一个类中存在多个synchronized
(this){...}代码块,其中任何一个synchronized(this)代码块处于被执行状态,则其它线程对其他synchronized(this)代码块的访问也会受到阻塞。
为了说明这个问题,我们举例说明:
HelloSynchronized.java
- publicclass HelloSynchronized {
- publicstaticvoid main(String[] args) {
- //
- HelloSynchronized helloSynchronized = new HelloSynchronized();
- //创建2个线程t1, t2,分别调用HelloSynchronized helloSynchronized的2个方法method1,与method2
- Thread t1 = new Thread(new HelloSynchronizedRunnalbe(helloSynchronized, "method1"), "t1");
- Thread t2 = new Thread(new HelloSynchronizedRunnalbe(helloSynchronized, "method2"), "t2");
- t1.start();
- t2.start();
- }
- //synchronized public void method1() { //同步方法
- publicvoid method1() {
- synchronized (this) { //同步块
- System.out.println(Thread.currentThread().getName()
- + " enter method1");
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- // do nothing
- }
- System.out.println(Thread.currentThread().getName()
- + " exit method1");
- }
- }
- //synchronized public void method2() { //同步方法
- publicvoid method2() {
- synchronized (this) { //同步块
- System.out.println(Thread.currentThread().getName()
- + " enter method2");
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- // do nothing
- }
- System.out.println(Thread.currentThread().getName()
- + " exit method2");
- }
- }
- }
- class HelloSynchronizedRunnalbe implements Runnable {
- private HelloSynchronized helloSynchronized;
- private String methodName;
- public HelloSynchronizedRunnalbe(HelloSynchronized helloSynchronized, String methodName) {
- this.helloSynchronized = helloSynchronized;
- this.methodName = methodName;
- }
- publicvoid run() {
- if (methodName.equals("method1")) {
- helloSynchronized.method1();
- } elseif (methodName.equals("method2")) {
- helloSynchronized.method2();
- }
- }
- }
public class HelloSynchronized {
public static void main(String[] args) {
//
HelloSynchronized helloSynchronized = new HelloSynchronized();
//创建2个线程t1, t2,分别调用HelloSynchronized helloSynchronized的2个方法method1,与method2
Thread t1 = new Thread(new HelloSynchronizedRunnalbe(helloSynchronized, "method1"), "t1");
Thread t2 = new Thread(new HelloSynchronizedRunnalbe(helloSynchronized, "method2"), "t2");
t1.start();
t2.start();
}
//synchronized public void method1() { //同步方法
public void method1() {
synchronized (this) { //同步块
System.out.println(Thread.currentThread().getName()
+ " enter method1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// do nothing
}
System.out.println(Thread.currentThread().getName()
+ " exit method1");
}
}
//synchronized public void method2() { //同步方法
public void method2() {
synchronized (this) { //同步块
System.out.println(Thread.currentThread().getName()
+ " enter method2");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// do nothing
}
System.out.println(Thread.currentThread().getName()
+ " exit method2");
}
}
}
class HelloSynchronizedRunnalbe implements Runnable {
private HelloSynchronized helloSynchronized;
private String methodName;
public HelloSynchronizedRunnalbe(HelloSynchronized helloSynchronized, String methodName) {
this.helloSynchronized = helloSynchronized;
this.methodName = methodName;
}
public void run() {
if (methodName.equals("method1")) {
helloSynchronized.method1();
} else if (methodName.equals("method2")) {
helloSynchronized.method2();
}
}
}
运行结果为:
t1 enter method1
t1 exit
method1
t2 enter method2
t2 exit
method2
等到线程t1结束后,t2才开始运行(t2受到阻塞)
再把synchronized
(this)去掉,运行结果为:
t1 enter method1
t2 enter
method2
t1 exit method1
t2 exit
method2
线程t1,t2同时运行
同步方法例:
- synchronizedprivatevoid sellBook(Book book) {
- ...
- }
synchronized private void sellBook(Book book) {
...
}
这种方法其实相当于
- privatevoid sellBook(Book book) {
- synchronized(this) {
- ...
- }
- }
private void sellBook(Book book) {
synchronized(this) {
...
}
}
由于默认采用this作为同步对象,所以当一个类中有多个synchronized方法时,同样会存在以上问题:即如果有一个线程访问其中某个synchronized方法时,直到该方法执行完毕,其它线程对其它synchronized方法的访问也将受到阻塞。
大家可以把上面的例子稍加改造,去掉代码中的synchronized
(this),改为synchronized public void method1(),synchronized public void
method2()同步形式,运行后会得到同样结果。
多同步代码块synchronized(this){...}的多线程阻塞问题(包括synchronized同步方法),在并发处理的系统中(比如WEB服务器)会严重影响性能,建议慎重使用。可以使用synchronized(obj){...}缩小同步资源对象的范围来解决这个问题。