1 前言
上一篇文章讲了多线程编程中Synchronized同步方法的相关内容,Synchronized除了同步方法之外还可以同步语句块,这篇文章就介绍Synchronized如何同步语句块。
2 正文
1、Synchronized同步方法的缺点
在介绍Synchronized同步语句块之前,先来说说Synchronized同步方法的缺点。先看代码:
public class Demo9 { public static void main(String[] args) throws InterruptedException { Demo9Service service = new Demo9Service(); Thread t1 = new Demo9ThreadA(service); t1.setName("A"); Thread t2 = new Demo9ThreadA(service); t2.setName("B"); t1.start(); t2.start(); Thread.sleep(20000); long start = Demo9Untils.start1 > Demo9Untils.start2 ? Demo9Untils.start2 : Demo9Untils.start1; long end = Demo9Untils.end1 > Demo9Untils.end2 ? Demo9Untils.end1 : Demo9Untils.end2; System.out.println("总耗时:" + (end - start) / 1000 + "秒"); } } class Demo9Untils{ static long start1; static long start2; static long end1; static long end2; } class Demo9Service{ synchronized public void foo(){ try{ System.out.println("开始任务"); Thread.sleep(5000); System.out.println("长时任务处理完成,线程" + Thread.currentThread().getName()); System.out.println("结束任务"); }catch(InterruptedException e){ e.printStackTrace(); } } } class Demo9ThreadA extends Thread{ public Demo9Service service; public Demo9ThreadA(Demo9Service service){ this.service = service; } @Override public void run() { Demo9Untils.start1 = System.currentTimeMillis(); service.foo(); Demo9Untils.end1 = System.currentTimeMillis(); } } class Demo9ThreadB extends Thread{ public Demo9Service service; public Demo9ThreadB(Demo9Service service){ this.service = service; } @Override public void run() { Demo9Untils.start2 = System.currentTimeMillis(); service.foo(); Demo9Untils.end2 = System.currentTimeMillis(); } } 复制代码
结果:
2、Synchronized同步代码块
再来看看synchronized同步代码块的代码:
public class Demo10 { public static void main(String[] args) { Demo10Service service = new Demo10Service(); Thread t1 = new Demo10Thread(service); t1.setName("A"); t1.start(); Thread t2 = new Demo10Thread(service); t2.setName("B"); t2.start(); } } class Demo10Service{ public void foo(){ try { synchronized (this) { System.out.println(Thread.currentThread().getName() + "开始于" + System.currentTimeMillis()); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "结束于" + System.currentTimeMillis()); } }catch (InterruptedException e){ e.printStackTrace(); } } } class Demo10Thread extends Thread{ private Demo10Service service; public Demo10Thread(Demo10Service service){ this.service = service; } @Override public void run() { service.foo(); } } 复制代码
结果:
通过上面的示例可以发现Synchronized同步方法时间耗费过长。
3、使用同步代码块解决同块方法的问题
那么如何使用同步代码块解决同步方法的问题,代码如下:
public class Demo11 { public static void main(String[] args) throws InterruptedException { Demo11Service service = new Demo11Service(); Thread t1 = new Demo11ThreadA(service); t1.setName("A"); t1.start(); Thread t2 = new Demo11ThreadB(service); t2.setName("B"); t2.start(); Thread.sleep(10000); long start = Demo11Utils.start1 > Demo11Utils.start2 ? Demo11Utils.start2 : Demo11Utils.start1; long end = Demo11Utils.end1 > Demo11Utils.end2 ? Demo11Utils.end1 : Demo11Utils.end2; System.out.println("耗时:" + (end - start) /1000 + "秒"); } } class Demo11Utils{ static long start1; static long start2; static long end1; static long end2; } class Demo11Service{ /*synchronized*/ public void foo(){ try { System.out.println(Thread.currentThread().getName() + "开始任务"); Thread.sleep(3000); synchronized (this) { System.out.println(Thread.currentThread().getName() + "处理计算结果"); } System.out.println(Thread.currentThread().getName() + "结束任务"); }catch(InterruptedException e){ e.printStackTrace(); } } } class Demo11ThreadA extends Thread{ private Demo11Service service; public Demo11ThreadA(Demo11Service service){ this.service = service; } @Override public void run() { Demo11Utils.start1 = System.currentTimeMillis(); service.foo(); Demo11Utils.end1 = System.currentTimeMillis(); } } class Demo11ThreadB extends Thread{ private Demo11Service service; public Demo11ThreadB(Demo11Service service){ this.service = service; } @Override public void run() { Demo11Utils.start2 = System.currentTimeMillis(); service.foo(); Demo11Utils.end2 = System.currentTimeMillis(); } } 复制代码
结果:
可见耗时明显缩短!
4、synchronized代码块间的同步
/** * synchronized代码块的同步 */ public class Demo13 { public static void main(String[] args) throws InterruptedException { DemoService13 service13 = new DemoService13(); Thread t1 = new Demo13Thread1(service13); t1.start(); Thread.sleep(100); Thread t2 = new Demo13Thread2(service13); t2.start(); } } //声明一个服务 class DemoService13{ public void foo1(){ //同步代码块 synchronized (this){ //睡眠2s try { System.out.println("foo1方法开始时间:"+System.currentTimeMillis()); Thread.sleep(2000); System.out.println("foo1方法结束时间:"+System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } } public void foo2(){ synchronized (this){ System.out.println("foo2方法开始时间: "+System.currentTimeMillis()); System.out.println("foo2方法结束时间: "+System.currentTimeMillis()); } } } //声明一个子线程 class Demo13Thread1 extends Thread{ private DemoService13 service13; //构造方法 public Demo13Thread1(DemoService13 service){ this.service13 = service; } public void run(){ service13.foo1(); } } //再声明一个子线程 class Demo13Thread2 extends Thread{ private DemoService13 service13; //构造方法 public Demo13Thread2(DemoService13 service){ this.service13 = service; } public void run(){ service13.foo2(); } } 复制代码
结果:
使用同步synchronize(this)代码时需要注意,当一个线程访问object的一个synchronized(this)同步代码块时,其它线程对这个object的其它syncrhonized(this)同步的访问会被阻塞,说明synchronized使用的对象锁是同一个。
synchroinze(this)代码块锁定的是当前对象。比如:
/** *synchroinze(this)代码块是锁定当前对象 */ public class Demo14 { public static void main(String[] args) throws InterruptedException { Demo14Service service = new Demo14Service(); Thread t1 = new Demo14ThreadA(service); Thread t2 = new Demo14ThreadB(service); t2.start(); Thread.sleep(10); t1.start(); } } class Demo14Service{ synchronized public void foo1(){ System.out.println("foo1方法正在运行"); } public void foo2(){ try { synchronized (this) { System.out.println("foo2方法开始"); Thread.sleep(2000); System.out.println("foo2方法结束"); } }catch (InterruptedException e){ e.printStackTrace(); } } } class Demo14ThreadA extends Thread{ private Demo14Service service; public Demo14ThreadA(Demo14Service service){ this.service = service; } @Override public void run() { service.foo1(); } } class Demo14ThreadB extends Thread{ private Demo14Service service; public Demo14ThreadB(Demo14Service service){ this.service = service; } @Override public void run() { service.foo2(); } } 复制代码
结果:
synchronized(this)代码块与synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。
5、使用任意对象作为对象锁
除了可以使用syncrhonized(this)来同步代码块,java还支持任意对象作为对象锁来实现同步的功能。这个任意对象就是成员变量或者方法中的参数,语法格式为:
synchronized(lockobj) 复制代码
在多个线程持有的对象锁为同一个对象的情况下,同一时间只有一个线程可以执行synchoronized(lockobj)同步代码块中的代码。如果使用的不是同一个对象锁,运行的结果就是异步调用,交叉输出结果。
比如:
/** * 使用任意对象作为对象锁 */ public class Demo15 { public static void main(String[] args) { Demo15Service service = new Demo15Service(); Thread t1 = new Demo15Thread(service); t1.setName("线程1"); t1.start(); Thread t2 = new Demo15Thread(service); t2.setName("线程2"); t2.start(); } } class Demo15Service{ //锁对象 private Object lockobject = new Object(); public void foo(){ //synchronized锁定对象 synchronized (lockobject){ try { System.out.println(Thread.currentThread().getName() + "开始于" + System.currentTimeMillis()); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "结束于" + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Demo15Thread extends Thread{ private Demo15Service service; public Demo15Thread(Demo15Service service){ this.service = service; } @Override public void run() { service.foo(); } } 复制代码
结果:
与synchronized (this)相比非tihs对象具有一定的优点。
一个类有很多个synchrnoized方法,虽然可以实现同步,但是会受到阻塞,影响运行效率。如果使用同步代码块非this对象,则synchronized非this对象的代码块中的程序与同步方法是不与其它的锁(this)争抢this锁,大大提高运行的效率。
/** * 非this对象具有的优点 */ public class Demo16 { public static void main(String[] args) throws InterruptedException { Demo16Service service = new Demo16Service(); Thread t1 = new Demo16ThreadA(service); t1.start(); Thread.sleep(10); Thread t2 = new Demo16ThreadB(service); t2.start(); } } class Demo16Service{ private Object lockObject = new Object(); public void foo(){ try { synchronized (lockObject) { System.out.println("foo方法开始时间" + System.currentTimeMillis()); Thread.sleep(2000); System.out.println("foot方法结束时间" + System.currentTimeMillis()); } }catch (InterruptedException e){ e.printStackTrace(); } } synchronized public void foo2(){ System.out.println("foo2方法开始时间" + System.currentTimeMillis()); System.out.println("foo2方法结束时间" + System.currentTimeMillis()); } } class Demo16ThreadA extends Thread{ private Demo16Service service; public Demo16ThreadA(Demo16Service service){ this.service =service; } @Override public void run() { service.foo(); } } class Demo16ThreadB extends Thread{ private Demo16Service service; public Demo16ThreadB(Demo16Service service){ this.service = service; } @Override public void run() { service.foo2(); } } 复制代码
结果:
5、synchronized(非this对象)解决脏读
import java.util.ArrayList; import java.util.List; public class Demo17 { public static void main(String[] args) throws InterruptedException { Demo17List list = new Demo17List(); Thread t1 = new Demo17ThreadA(list); t1.start(); Thread t2 = new Demo17ThreadB(list); t2.start(); Thread.sleep(5000); System.out.println("list size is " + list.size()); } } class Demo17List{ private List list = new ArrayList(); synchronized public void add(Object obj){ list.add(obj); } synchronized public int size(){ return list.size(); } } class Demo17Service{ private Object lockObject = new Object(); public void add(Demo17List list, Object obj){ try { synchronized (list) { if (list.size() < 1) { Thread.sleep(2000); list.add(obj); } } }catch (InterruptedException e){ e.printStackTrace(); } } } class Demo17ThreadA extends Thread{ private Demo17List list; public Demo17ThreadA(Demo17List list){ this.list = list; } @Override public void run() { Demo17Service service = new Demo17Service(); service.add(list, "a"); } } class Demo17ThreadB extends Thread{ private Demo17List list; public Demo17ThreadB(Demo17List list){ this.list = list; } @Override public void run() { Demo17Service service = new Demo17Service(); service.add(list, "b"); } } 复制代码
结果:
对于synchronized(lockobj)格式的写法是将lockobj本身作为对象锁,总结上面的结果这样得出以下的结论:
a. 当多个线程同时执行syncrhonized(lockobj)同步代码块时结果是同步效果;
b. 当其它线程执行lockobj对象中的synchronized同步方法时也是同步效果;
c. 当其它线程执行lockobj对象方法里的synchronize(this)代码块时也是同步效果
如果其它线程调用不加synchronized关键字的方法时,还是异步调用。
6、半异步半同步
在并发模式中的同步和异步与IO模型中的同步和异步是完全不同的概念。
在IO模型中,同步和异步区分的是内核向应用程序通知的是何种IO事件(是就绪事件还是完成事件),以及该由谁来完成IO读写(是应用程序还是内核)。
而在并发模式中的同步指的是程序完全按照代码序列的顺序执行,异步指的是程序的执行需要由系统事件来驱动。常见的系统事件包括中断信号等。
按照同步方式运行的线程称为同步线程,按照异步方式运行的线程成为异步线程。显然异步线程的执行效率高,实时性强,这是很多嵌入式程序采用的模型。但编写异步方式执行的程序相对复杂,难于调试和扩展,且不适合大量的并发。而同步线程则相反,它虽然效率比较低,实时性较差,但逻辑简单。因此,对于某些既要求较好的实时性,又要求同时处理多个客户请求的应用程序,就可以同时使用同步线程和异步线程来实现。即使用半同步/半异步模式来实现!
如果使用synchronized关键字来实现半同步/半异步代码如下:
/** * 半同步和半异步 */ public class Demo12 { public static void main(String[] args) { Demo12Service service = new Demo12Service(); Thread t1 = new Demo12Thread(service); t1.setName("A"); t1.start(); Thread t2 = new Demo12Thread(service); t2.setName("B"); t2.start(); } } class Demo12Service { public void foo(){ try{ for (int i = 0; i < 100; i++) { System.out.println("非同步线程" + Thread.currentThread().getName() + ", i=" + i); Thread.sleep(10); } System.out.println(); synchronized (this){ for (int i = 0; i < 100; i++) { System.out.println("同步线程" + Thread.currentThread().getName() + ", i=" + i); Thread.sleep(10); } } }catch (InterruptedException e){ e.printStackTrace(); } } } class Demo12Thread extends Thread{ private Demo12Service service; public Demo12Thread(Demo12Service service){ this.service = service; } @Override public void run() { service.foo(); } } 复制代码
结果:
不在synchronized块中的就是异步执行,在synchroinzed块中的代码就是同步执行。
非同步的输出是交叉的,而同步线程只有线程a执行完了才执行线程B。
7、synchronized同步静态方法与synchronized(class)代码块
前面讲了synchronized关键字修饰方法,但是并没有讲修饰静态方法,关键字synchronized还可以修饰静态方法,
synchronized修饰静态方法就是对象当前的*.java文件对应的Class进行加锁。比如:
public class Demo18 { public static void main(String[] args) { Thread t1 = new Demo18ThreadA(); t1.setName("A"); t1.start(); Thread t2 = new Demo18ThreadB(); t2.setName("B"); t2.start(); } } class Demo18Service{ synchronized public static void foo1(){ System.out.println(Thread.currentThread().getName() + "进入方法foo1在" + System.currentTimeMillis()); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束方法foo1在" + System.currentTimeMillis()); } synchronized public static void foo2(){ System.out.println(Thread.currentThread().getName() + "进入方法foo2在" + System.currentTimeMillis()); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束方法foo2在" + System.currentTimeMillis()); } } class Demo18ThreadA extends Thread{ @Override public void run() { Demo18Service.foo1(); } } class Demo18ThreadB extends Thread{ @Override public void run() { Demo18Service.foo2(); } } 复制代码
结果:
从上面的结果可以发现在静态方法上使用synchronized修饰与在非静态方法上使用的结果是一致的,都是同步运行。但是其实从本质来讲它们是有区别的,在静态方法上使用synchronized是给Class类上锁,而在非静态方法上使用syncrhonized是给对象上锁。
public class Demo19 { public static void main(String[] args) { Demo19Service service = new Demo19Service(); Thread t1 = new Demo19ThreadA(service); t1.setName("A"); t1.start(); Thread t2 = new Demo19ThreadB(service); t2.setName("B"); t2.start(); } } class Demo19Service{ synchronized public static void foo1(){ System.out.println(Thread.currentThread().getName() + "进入foo1方法在" + System.currentTimeMillis()); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束foo1方法在" + System.currentTimeMillis()); } synchronized public void foo2(){ System.out.println(Thread.currentThread().getName() + "进入foo2方法在" + System.currentTimeMillis()); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束foo2方法在" + System.currentTimeMillis()); } } class Demo19ThreadA extends Thread{ private Demo19Service service; public Demo19ThreadA(Demo19Service service){ this.service = service; } @Override public void run() { service.foo2(); } } class Demo19ThreadB extends Thread{ private Demo19Service service; public Demo19ThreadB(Demo19Service service){ this.service = service; } @Override public void run() { service.foo1(); } } 复制代码
结果:
上面以异步方式运行的原因是因为持有锁是不一样的,非静态方法持有提对象锁,而静态方法持有的是Class锁,Class锁可以对类的所有对象实例起作用。
同步synchronized(class)代码块的作用其实和synchronized static方法的作用是一样的
public class Demo20 { public static void main(String[] args) { Demo20Service service = new Demo20Service(); Thread t1 = new Demo20ThreadA(service); t1.setName("A"); t1.start(); Thread t2 = new Demo20ThreadA(service); t2.setName("B"); t2.start(); } } class Demo20Service{ synchronized public static void foo1(){ System.out.println(Thread.currentThread().getName() + "进入foo1方法在" + System.currentTimeMillis()); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束foo1方法在" + System.currentTimeMillis()); } public static void foo2(){ synchronized (Demo20Service.class) { System.out.println(Thread.currentThread().getName() + "进入foo1方法在" + System.currentTimeMillis()); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束foo1方法在" + System.currentTimeMillis()); } } } class Demo20ThreadA extends Thread{ private Demo20Service service; public Demo20ThreadA(Demo20Service service){ this.service = service; } @Override public void run() { service.foo1(); } } class Demo20ThreadB extends Thread{ private Demo20Service service; public Demo20ThreadB(Demo20Service service){ this.service = service; } @Override public void run() { service.foo2(); } } 复制代码
结果:
8、synchronized(String)*
当使用synchronized关键字将String作为锁对象时:
public class Demo21 { public static void main(String[] args) { Thread t1 = new Demo21ThreadA(); t1.setName("A"); t1.start(); Thread t2 = new Demo21ThreadB(); t2.setName("B"); t2.start(); } } class Demo21Service{ public static void foo1(String lockObject){ try { synchronized (lockObject) { while (true) { System.out.println("线程" + Thread.currentThread().getName()); Thread.sleep(1000); } } }catch (InterruptedException e){ e.printStackTrace(); } } public static void foo2(Object lockObject){ try { synchronized (lockObject) { while (true) { System.out.println("线程" + Thread.currentThread().getName()); Thread.sleep(1000); } } }catch (InterruptedException e){ e.printStackTrace(); } } } class Demo21ThreadA extends Thread{ @Override public void run() { Demo21Service.foo1("AA"); } } class Demo21ThreadB extends Thread{ @Override public void run() { Demo21Service.foo2("AA"); } } 复制代码
结果:
通过上面结果可以发现只会打印线程B。只有线程B在运行的原因是,两个线程的对象锁都是使用AA,两个线程持有的相同的锁,所以造成线程B能运行。这就是String常量所带来的问题。所以不应该使用字符串类型作为对象锁,而应该使用其它类型,例如new Object(),对象就不会放入缓存 。
3 总结
多线程编程之线程的同步机制(上): Synchronized同步方法和本篇文章主要讲了在多线程编程中
Synchronized关键字对于同步机制的作用。Synchronized关键字不仅仅能够修饰方法还能够修饰代码块。