线程同步--synchronized详解
来源:互联网 发布:淘宝大学的vip课靠谱吗 编辑:程序博客网 时间:2024/05/23 18:33
在Java的多线程中有两种编程模型:异步编程模型和同步编程模型。
假设t1和t2为两个线程,则
异步编程模型: t1线程执行t1的,t2线程执行t2的,两个线程之间谁也不等谁
同步编程模型:t1线程和t2线程执行,当t1线程必须等t2线程之行结束之后,t1线程才能执行,这是同步编程模型。
为什么要引入同步呢?
为了数据的安全。尽管应用程序的使用率降低,但是为了保证数据是安全的,必须加入线程同步机制。 线程同步机制使程序变成了(等同于)单线程。
什么时候要同步呢?
第一、必须是多线程环境
第二、共享同一个数据 [ 注意:该数据必须是类中的变量(也即实例变量)对应的数据]
第三、共享的数据涉及到修改操作
必须同时满足上面的三个条件才用同步模型。
以下示例演示采用异步编程对共享数据进行修改操作,看会出现什么问题?
/* * 以下程序演示取款例子,在不使用线程同步机制, * 多线程同时对同一个账户进行操作,会出现什么问题? * */public class Test { public static void main(String[] args) throws InterruptedException { // 创建一个公共账户 Account act = new Account("num-001", 1000.0); MyRunnable myRunnable = new MyRunnable(act); // 创建两个线程,对同一个账户取款 Thread t1 = new Thread(myRunnable); Thread t2 = new Thread(myRunnable); t1.start(); t2.start(); }}class MyRunnable implements Runnable { Account act; public MyRunnable(Account act) { this.act = act; } @Override public void run() { act.withdraw(100.0); System.out.println("取款100.0成功,余额为:" + act.getBalance()); }}class Account { private String actnum; private Double balance; public Account(String actnum, double balance) { this.actnum = actnum; this.balance = balance; } public String getActnum() { return actnum; } public void setActnum(String actnum) { this.actnum = actnum; } public Double getBalance() { return balance; } public void setBalance(Double balance) { this.balance = balance; } // 对外提供一个取款的方法 public void withdraw(double money) { // 计算余额 double after = balance - money; // 延迟 try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 更新 this.setBalance(after); }}
运行结果如图 6-1 所示:
图 6-1 不使用同步对共享数据进行修改操作会出现“脏读”
使用同步可以解决上述问题。同步的方式大概可以分为两种:同步方法和同步语句块。下面分别用这两种方式解决上述问题。
先用同步语句块解决,对上面的代码做如下修改
//把需要同步的代码放到同步语句块中 synchronized (this) { // 计算余额 double after = balance - money; // 延迟 try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 更新 this.setBalance(after); }
运行结果如图 6-2 所示
图 6-2 使用同步语句块解决
然后用同步方法解决,对上面的代码做如下修改
// 对外提供一个取款的方法,在方法上加上synchronized关键词 public synchronized void withdraw(double money) { // 计算余额 double after = balance - money; // 延迟 try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 更新 this.setBalance(after); }
运行结果如图 6-3 所示
图 6-3 使用同步方法解决
使用synchronized(this)语句块同步的原理:t1线程和t2线程
t1线程在执行过程中,遇到了synchronized关键字,就会去找this的对象锁, 如果找到this对象锁,则进入同步语句块中执行程序。当同步语句块中的代码执行结束后, t1线程归还this的对象锁。
在t1线程执行同步语句块的过程中,如果t2线程也过来执行以下代码,也遇到synchronized 关键字,所以也去找this的对象锁,但是该对象锁被t1线程持有,只能在这等待this对象的归还。
使用synchronized方法同步的原理:t1线程和t2线程
每个对象都有一个锁(也称监控器monitor),它是对象生来就有的东西(因此你不必为此写任何代码)。
t1线程在执行过程中,遇到了synchronized方法,就会去找this的对象锁, 如果找到this对象锁,这个对象就被锁住了。当同步方法执行结束后,t1线程归还这个对象的对象锁,在方法返回并且解锁之前,谁也不能调用同一个对象的其它synchronized方法。
在t1线程执行同步方法中的代码过程中,如果t2线程也过来执行以下代码,也遇到synchronized 关键字,所以也去找this的对象锁,但是该对象锁被t1线程持有,只能在这等待this对象的归还。
同步方法又能细分为:普通同步方法和静态同步方法。虽然这两种方法同步效果一样,但是本质却是不同的:synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到普通方法上是给对象上锁。下面用一个示例验证不是同一个锁
/* * 验证静态同步方法和普通同步方法的本质区别: * 静态同步方法是给Class类上锁,普通同步方法是给对象上锁。 */public class Test { public static void main(String[] args) throws InterruptedException { Service service = new Service(); MyRunnableA myRunnableA = new MyRunnableA(service); Thread a = new Thread(myRunnableA); a.setName("A"); a.start(); MyRunnableB myRunnableB = new MyRunnableB(service); Thread b = new Thread(myRunnableB); b.setName("B"); b.start(); MyRunnableC myRunnableC = new MyRunnableC(service); Thread c = new Thread(myRunnableC); c.setName("C"); c.start(); }}class MyRunnableA implements Runnable { private Service service; public MyRunnableA(Service service) { this.service = service; } @Override public void run() { service.printA(); }}class MyRunnableB implements Runnable { private Service service; public MyRunnableB(Service service) { this.service = service; } @Override public void run() { service.printB(); }}class MyRunnableC implements Runnable { private Service service; public MyRunnableC(Service service) { this.service = service; } @Override public void run() { service.printC(); }}/* * 提供两个静态同步方法和一个普通同步方法,通过输出结果(异步或同步)来判断是否是不同的锁 */class Service { public synchronized static void printA() { try { System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA"); // 设置延迟 Thread.sleep(3000); System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA"); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized static void printB() { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB"); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB"); } public synchronized void printC() { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printC"); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printC"); }}
运行结果如图 6-4 所示:
图 6-4 方法printC()为异步执行
异步的原因是持有不同的锁,printC()方法是对象锁,printA()方法是Class锁,而Class锁可以对类的所有对象实例起作用。下面用一个示例进行验证
/* * 验证Class锁可以对类的所有对象实例起作用 */public class Test { public static void main(String[] args) throws InterruptedException { // 设置两个不同的对象实例 Service service1 = new Service(); Service service2 = new Service(); MyRunnableA myRunnableA = new MyRunnableA(service1); Thread a = new Thread(myRunnableA); a.setName("A"); a.start(); MyRunnableB myRunnableB = new MyRunnableB(service2); Thread b = new Thread(myRunnableB); b.setName("B"); b.start(); }}class MyRunnableA implements Runnable { private Service service; public MyRunnableA(Service service) { this.service = service; } @Override public void run() { service.printA(); }}class MyRunnableB implements Runnable { private Service service; public MyRunnableB(Service service) { this.service = service; } @Override public void run() { service.printB(); }}/* * 提供两个静态同步方法 */class Service { public synchronized static void printA() { try { System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA"); // 设置延迟 Thread.sleep(3000); System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA"); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized static void printB() { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB"); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB"); }}
运行结果如同 6-5 所示
图 6-5 虽然是不同对象,但静态同步方法还是同步执行
同步语句块又能细分为:同步synchronized(class)语句块、同步synchronized(this)语句块、同步synchronized(非this对象x)语句块和同步synchronized(string)语句块。
同步synchronized(this)语句块在上面已经演示过,下面不再演示。
同步synchronized(class)语句块的作用其实和synchronized static方法的作用一样,下面用一个示例进行演示
对图 6-5 上面的代码做以下更改
class Service { public static void printA() { synchronized (Service.class) { try { System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA"); // 设置延迟 Thread.sleep(3000); System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA"); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized static void printB() { synchronized (Service.class) { System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB"); System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB"); } }}
运行结果如同 6-6 所示
图 6-6 和静态同步方法的作用一样,也是同步执行
同步synchronized(string)语句块会因为String常量池的特性而带来问题,因此同步synchronized 语句块都不使用String作为锁对象,而改用其它,比如new Object()实例化一个Object对象,但它并不放入缓存中。
在JVM中具有String常量池缓存的功能,所以如图 6-7 所示的结果为true。
图 6-7 Sting常量池缓存
同步synchronized(非this对象x)语句块具有一定的优点:如果在一个类中有很多个synchronized 方法,这时虽然能实现同步,但会受到阻塞,所以影响运行效率;但如果使用同步语句块非this对象,则synchronized (非this对象x)语句块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,则可以大大提高运行效率。
“synchronized (非this对象x)”格式的写法是将x对象本身作为“对象监视器”,这样就可以得出以下3个结论:
- 当多个线程同时执行synchronized (x){}同步语句块时呈同步效果。
- 当其他线程执行x对象中synchronized 同步方法时呈同步效果。
- 当其他线程执行x对象方法里面的synchronized (this)语句块时也呈同步效果。
注意:如果其他线程调用不加synchronized 关键字的方法时,还是异步调用。
验证第一个结论:当多个线程同时执行synchronized (x){}同步语句块时呈同步效果,示例如下
/*验证当多个线程同时执行synchronized (x){}同步语句块时呈同步效果*/public class Test { public static void main(String[] args) throws InterruptedException { Service service = new Service(); Object object = new Object(); ThreadA a = new ThreadA(service, object); a.setName("A"); a.start(); ThreadB b = new ThreadB(service, object); b.setName("B"); b.start(); }}class ThreadA extends Thread { private Service service; private Object object; public ThreadA(Service service, Object object) { super(); this.service = service; this.object = object; } @Override public void run() { super.run(); service.testMethod1(object); }}class ThreadB extends Thread { private Service service; private Object object; public ThreadB(Service service, Object object) { super(); this.service = service; this.object = object; } @Override public void run() { super.run(); service.testMethod1(object); }}class Service { public void testMethod1(Object object) { synchronized (object) { try { System.out.println("testMethod1--getLock time:" + System.currentTimeMillis() + " run ThreadName:" + Thread.currentThread().getName()); Thread.sleep(2000); System.out.println("testMethod1--releaseLock time:" + System.currentTimeMillis() + " run ThreadName:" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } }}
运行结果如图 6-8 所示
图 6-8 同步调用
同步的原因是使用了同一个“对象监视器”,如果使用不同的“对象监视器”就会异步执行。
对上面的代码作以下更改:
public class Test { public static void main(String[] args) throws InterruptedException { Service service = new Service(); ThreadA a = new ThreadA(service, new Object()); a.setName("A"); a.start(); ThreadB b = new ThreadB(service, new Object()); b.setName("B"); b.start(); }}
运行结果如图 6-9 所示
图 6-9 异步调用
验证第二个结论:当其他线程执行x对象中synchronized 同步方法时呈同步效果,示例如下
public class Test { public static void main(String[] args) throws InterruptedException { Service service = new Service(); MyObject object = new MyObject(); ThreadA a = new ThreadA(service, object); a.setName("A"); a.start(); ThreadB b = new ThreadB(service, object); b.setName("B"); b.start(); }}class ThreadA extends Thread { private Service service; private MyObject object; public ThreadA(Service service, MyObject object) { super(); this.service = service; this.object = object; } @Override public void run() { super.run(); service.testMethod1(object); }}class ThreadB extends Thread { private Service service; private MyObject object; public ThreadB(Service service, MyObject object) { super(); this.service = service; this.object = object; } @Override public void run() { super.run(); object.speedPrintString(); }}class Service { public void testMethod1(MyObject object) { synchronized (object) { try { System.out.println("testMethod1--getLock time:" + System.currentTimeMillis() + " run ThreadName:" + Thread.currentThread().getName()); Thread.sleep(5000); System.out.println("testMethod1--releaseLock time:" + System.currentTimeMillis() + " run ThreadName:" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } }}class MyObject { public synchronized void speedPrintString() { System.out.println("speedPrintString--getLock time:" + System.currentTimeMillis() + " run ThreadName:" + Thread.currentThread().getName()); System.out.println("-------------------"); System.out.println("speedPrintString--releaseLock time:" + System.currentTimeMillis() + " run ThreadName:" + Thread.currentThread().getName()); }}
运行结果如图 6-10 所示
图 6-10 同步效果
验证第三个结论:当其他线程执行x对象方法里面的synchronized (this)语句块时也呈同步效果,示例如下
对上面的代码作以下更改
class MyObject { public void speedPrintString() { synchronized (this) { System.out.println("speedPrintString--getLock time:" + System.currentTimeMillis() + " run ThreadName:" + Thread.currentThread().getName()); System.out.println("-------------------"); System.out.println("speedPrintString--releaseLock time:" + System.currentTimeMillis() + " run ThreadName:" + Thread.currentThread().getName()); } }}
运行结果如图 6-11 所示
图 6-11 也是同步效果
- 线程同步--synchronized详解
- 线程同步---synchronized
- 线程同步synchronized
- 线程同步---synchronized
- cocoa线程同步synchronized
- 线程同步----synchronized
- synchronized线程同步
- java 线程同步 synchronized
- java 线程同步 synchronized
- java synchronized 线程同步
- Java 线程同步 synchronized
- Java线程同步synchronized
- synchronized 线程同步
- 线程同步synchronized
- 线程同步1-synchronized
- 线程同步(synchronized)
- 线程的同步synchronized
- 线程同步(synchronized)
- JQuery 导航
- poj-3067-japan--(树状数组)
- 51nod 1799 二分答案 思维 + 分块打表
- css3弹性盒及其属性和视口的介绍
- JQuery效果
- 线程同步--synchronized详解
- Comparator和Comparable和类Collator的区别?(对象排序)
- Selenium实例1-自动登录小米社区并获取主题帖
- ubuntu下安装selenium以及chromedriver、geckodriver和phantomjs等驱动
- codevs 玛丽卡(最短路路径记录)
- 阿里云WINDOWS SERVER2012 不能正常发送邮件
- nyoj 36 最长公共子序列
- 爬虫
- Oracle select读的多种形式