一 同步代码块
1.为了解决并发操作可能造成的异常,java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块。其语法如下:
synchronized(obj){
//同步代码块
}
其中obj就是同步监视器,它的含义是:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。虽然java程序允许使用任何对象作为同步监视器,但 是同步监视器的目的就是为了阻止两个线程对同一个共享资源进行并发访问,因此通常推荐使用可能被并发访问的共享资源充当同步监视器。
2.小例子
Account.java
public class Account {
private String accountNo;
private double balance;
public Account(String accountNo,double balance){
this.accountNo=accountNo;
this.balance=balance;
} public double getBalance() {
return balance;
} public void setBalance(double balance) {
this.balance = balance;
} public String getAccountNo() {
return accountNo;
} public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; Account account = (Account) o; return accountNo.equals(account.accountNo); } @Override
public int hashCode() {
return accountNo.hashCode();
}
}
DrawThread.java
public class DrawThread extends Thread {
private Account account;
private double drawAmount; public DrawThread(String name, Account account, double drawAmount) {
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
public void run(){
synchronized (account){
if(account.getBalance()>=drawAmount){
System.out.println(getName() + "取钱成功,吐出钞票: " + drawAmount);
try{
Thread.sleep(1);
}catch(InterruptedException ex){
ex.getStackTrace();
}
account.setBalance(account.getBalance()-drawAmount);
System.out.println("\t余额为:"+account.getBalance());
}else{
System.out.println(getName()+"取钱失败,余额不足");
}
}
}
}
DrawTest.java
public class DrawTest {
public static void main(String[] args){
Account acct=new Account("1234567",1000);
new DrawThread("甲",acct,800).start();
new DrawThread("乙",acct,800).start();
}
}
甲取钱成功,吐出钞票: 800.0
    余额为:200.0
乙取钱失败,余额不足
3.如果将DrawThread的同步去掉:
public class DrawThread extends Thread {
private Account account;
private double drawAmount; public DrawThread(String name, Account account, double drawAmount) {
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
public void run(){
// synchronized (account){
if(account.getBalance()>=drawAmount){
System.out.println(getName() + "取钱成功,吐出钞票: " + drawAmount);
try{
Thread.sleep(1);
}catch(InterruptedException ex){
ex.getStackTrace();
}
account.setBalance(account.getBalance()-drawAmount);
System.out.println("\t余额为:"+account.getBalance());
}else{
System.out.println(getName()+"取钱失败,余额不足");
}
// }
}
}

会出现这些情况的结果:

乙取钱成功,吐出钞票: 800.0
甲取钱成功,吐出钞票: 800.0
    余额为:200.0
    余额为:-600.0
 
甲取钱成功,吐出钞票: 800.0
乙取钱成功,吐出钞票: 800.0
    余额为:200.0
    余额为:200.0
程序使用synchronized将run()方法里的方法体修改成同步代码块,同步监视器就是account对象,这样的做法符合“加锁-修改-释放锁”的逻辑,这样就可以保证并发线程在任一时刻只有一个线程进入修改共享资源的代码区。多次运行,结果只有一个。
 
二 同步方法
1.同步方法就是使用synchronized关键字修饰某个方法,这个方法就是同步方法。这个同步方法(非static方法)无须显式指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。通过同步方法可以非常方便的实现线程安全的类,线程安全的类有如下特征:
该类的对象可以方便的被多个线程安全的访问;
每个线程调用该对象的任意方法之后都能得到正确的结果;
每个线程调用该对象的任意方法之后,该对象状态依然能保持合理状态。
2.不可变类总是线程安全的,因为它的对象状态不可改变可变类需要额外的方法来保证其线程安全,在Account类中我们只需要把balance的方法变成同步方法即可。
Account.java
public class Account {
private String accountNo;
private double balance;
public Account(String accountNo,double balance){
this.accountNo=accountNo;
this.balance=balance;
} //因为账户余额不可以随便更改,所以只为balance提供getter方法
public double getBalance() {
return balance;
} public String getAccountNo() {
return accountNo;
} public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; Account account = (Account) o; return accountNo.equals(account.accountNo); } @Override
public int hashCode() {
return accountNo.hashCode();
} //提供一个线程安全的draw()方法来完成取钱操作
public synchronized void draw(double drawAmount){
if(balance>=drawAmount){
System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票:"+drawAmount);
try{
Thread.sleep(1);
}catch (InterruptedException ex){
ex.printStackTrace();
}
balance-=drawAmount;
System.out.println("\t余额为:"+balance);
}else{
System.out.println(Thread.currentThread().getName()+"取钱失败,余额不足");
}
}
}
DrawThread.java
public class DrawThread extends Thread {
private Account account;
private double drawAmount; public DrawThread(String name, Account account, double drawAmount) {
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
public void run(){
account.draw(drawAmount);
}
}
DrawTest.java
public class DrawTest {
public static void main(String[] args){
Account acct=new Account("1234567",1000);
new DrawThread("甲",acct,800).start();
new DrawThread("乙",acct,800).start();
}
}
注意,synchronized可以修饰方法,修饰代码块,但是不能修饰构造器、成员变量等。在Account类中定义draw()方法,而不是直接在 run()方法中实现取钱逻辑,这种做法更符合面向对象规则。DDD设计方式,即Domain Driven Design(领域驱动设计),认为每个类都应该是完备的领域对象,Account代表用户账户,就应该提供用户账户的相关方法。通过draw()方法来执行取钱操作,而不是直接将setBalance()方法暴露出来任人操作。
但是,可变类的线程安全是以降低程序的运行效率为代价的,不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源(共享资源)的方法进行同步。同时,可变类有两种运行环境:单线程环境和多线程环境, 则应该为可变类提供两种版本,即线程安全版本和线程不安全版本。如JDK提供的StringBuilder在单线程环境下保证更好的性能,StringBuffer可以保证多线程安全。
 
三 释放同步监视器的锁定
1.任何线程进入同步代码块,同步方法之前,必须先获得对同步监视器的锁定,那么如何释放对同步监视器的锁定呢,线程会在一下几种情况下释放同步监视器:
当前线程的同步方法、同步代码块执行结束,当前线程即释放同步监视器;
当前线程在同步代码块、同步方法中遇到break,return终止了该代码块、方法的继续执行;
当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致了该代码块、方法的异常结束;
当前线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器;
2.以下几种情况,线程不会释放同步监视器:
线程执行同步代码块或同步方法时,程序调用Thread.sleep(),Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器;
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放同步监视器,当然,程序应尽量避免使用suspend()和resume()方法来控制线程。
 
四 同步锁:
1.Java5开始,Java提供了一种功能更加强大的线程同步机制——通过显式定义同步锁对象来实现同步,这里的同步锁由Lock对象充当。
Lock 对象提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock是控制多个线程对共享资源进行访问的工具。通常, 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象。
某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁),Lock,ReadWriteLock是Java5提供的两个根接口,并为 Lock提供了ReentrantLock实现类,为ReadWriteLock提供了ReentrantReadWriteLock实现类。在 Java8中提供了新型的StampLock类,在大多数场景下它可以替代传统的ReentrantReadWriteLock。 ReentrantReadWriteLock为读写操作提供了三种锁模式:Writing,ReadingOptimistic,Reading。
2.在实现线程安全的控制中,比较常用的是ReentrantLock(可重入锁)。主要的代码格式如下:
class X{
//定义锁对象
private final ReentrantLock lock=new ReentrantLock();
//定义需要保证线程安全的方法
public void m(){
//加锁
lock.lock();
try{
//...method body
}
//使用finally块来保证释放锁
finally{
lock.unlock();
}
}
}
将Account.java修改为:
public class Account {
private final ReentrantLock lock=new ReentrantLock();
private String accountNo;
private double balance;
public Account(String accountNo,double balance){
this.accountNo=accountNo;
this.balance=balance;
} //因为账户余额不可以随便更改,所以只为balance提供getter方法
public double getBalance() {
return balance;
} public String getAccountNo() {
return accountNo;
} public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; Account account = (Account) o; return accountNo.equals(account.accountNo); } @Override
public int hashCode() {
return accountNo.hashCode();
} //提供一个线程安全的draw()方法来完成取钱操作
public void draw(double drawAmount){
//加锁
lock.lock();
try {
if (balance >= drawAmount) {
System.out.println(Thread.currentThread().getName() + "取钱成功!吐出钞票:" + drawAmount);
try {
Thread.sleep(1);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
balance -= drawAmount;
System.out.println("\t余额为:" + balance);
} else {
System.out.println(Thread.currentThread().getName() + "取钱失败,余额不足");
}
}finally {
lock.unlock();
}
}
}
使用Lock与使用同步代码有点相似,只是使用Lock时可以显式使用Lock对象作为同步锁,而使用同步方法时系统隐式使用当前对象作为同步监视器。使用 Lock时每个Lock对象对应一个Account对象,一样可以保证对于同一个Account对象,同一个时刻只能有一个线程进入临界区。Lock提供 了同步方法和同步代码块所没有的其他功能,包括使用非块结构的tryLock()方法,以及试图获取可中断锁的lockInterruptibly()方法,还有获取超时失效锁的tryLock(long,TimeUnit)方法。
ReentrantLock可重入锁的意思是,一个线程可以对已被加锁的ReentrantLock锁再次加锁,ReentrantLock对象会维持一个计数器来追踪lock()方法的嵌套调用,线程在每次调用lock()加锁后,必须显式调用unlock()来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。
 
五 死锁
当两个线程相互等待对方释放同步监视器时就会发生死锁,Java虚拟机没有检测,也没有采取措施来处理死锁情况,所以多线程编程时应该采取措施避免死锁出现。一旦出现死锁,程序既不会发生任何异常,也不会给出任何提示,只是所有线程都处于阻塞状态,无法继续。
如DeadLock.java
class A{
public synchronized void foo(B b){
System.out.println("当前线程名为:"+Thread.currentThread().getName()+"进入了A实例的foo()方法");
try{
Thread.sleep(200);
}catch(InterruptedException ex){
ex.printStackTrace();
}
System.out.println("当前线程名为:"+Thread.currentThread().getName()+"试图调用B实例的last()方法");
b.last();
}
public synchronized void last(){
System.out.println("进入了A类的last()方法内部");
}
}
class B{
public synchronized void bar(A a){
System.out.println("当前线程名为:"+Thread.currentThread().getName()+"进入了B实例的bar()方法");
try{
Thread.sleep(200);
}catch(InterruptedException ex){
ex.printStackTrace();
}
System.out.println("当前线程名为:"+Thread.currentThread().getName()+"试图调用A实例的last()方法");
a.last();
}
public synchronized void last(){
System.out.println("进入了B类的last()方法内部");
}
}
public class DeadLock implements Runnable{
A a =new A();
B b=new B();
public void init(){
Thread.currentThread().setName("主线程");
a.foo(b);
System.out.println("进入了主线程之后...");
}
public void run(){
Thread.currentThread().setName("副线程");
b.bar(a);
System.out.println("进入了副线程之后...");
}
public static void main(String[] args){
DeadLock d1=new DeadLock();
new Thread(d1).start();
d1.init();
}
}

结果:

当前线程名为:主线程进入了A实例的foo()方法
当前线程名为:副线程进入了B实例的bar()方法
当前线程名为:主线程试图调用B实例的last()方法
当前线程名为:副线程试图调用A实例的last()方法

线程同步 synchronized 同步代码块 同步方法 同步锁的更多相关文章

  1. JAVA之旅(十三)——线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this

    JAVA之旅(十三)--线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this 我们继续上个篇幅接着讲线程的知识点 一.线程的安全性 当我们开启四个窗口(线程 ...

  2. About 静态代码块,普通代码块,同步代码块,构造代码块和构造函数的纳闷

    构造函数用于给对象进行初始化,是给与之对应的对象进行初始化,它具有针对性,函数中的一种.特点:1:该函数的名称和所在类的名称相同.2:不需要定义返回值类型.3:该函数没有具体的返回值.记住:所有对象创 ...

  3. 线程的同步机制:同步代码块&同步方法

    解决存在的线程安全问题:打印车票时出现重票,错票 使用同步代码块的解决方案 TestWindow2 package com.aff.thread; /* 使用实现Runnable接口的方式,售票 存在 ...

  4. java的同步方法和同步代码块,对象锁,类锁区别

    /** * @author admin * @date 2018/1/12 9:48 * 作用在同一个实例对象上讨论 * synchronized同步方法的测试 * 两个线程,一个线程调用synchr ...

  5. 深入理解Java中的同步静态方法和synchronized(class)代码块的类锁

    一.回顾学习内容 在前面几篇博客中我我们已经理解了synchronized对象锁.对象锁的重入.synchronized方法块.synchronized非本对象的代码块, 链接:https://www ...

  6. 2.2.9静态同步synchronized方法与synchronized(class)代码块

    关键字synchronized还可以应用在static静态方法上,这样写那是对当前的*.java文件对应的class类进行持锁, 测试如下 package com.cky.bean; /** * Cr ...

  7. 牛客网Java刷题知识点之什么是代码块、普通代码块、静态代码块、同步代码块、构造代码块以及执行顺序

    不多说,直接上干货! 这种形式的程序段我们将其称之为代码块,所谓代码块就是用大括号({})将多行代码封装在一起,形成一个独立的数据体,用于实现特定的算法.一般来说代码块是不能单独运行的,它必须要有运行 ...

  8. JAVA_四大代码块_普通代码块、构造代码块、静态代码块、同步代码块。

    普通代码块 在方法或语句中出现的{}里面的内容就被称为普通代码块,普通代码块和一般的语句执行顺序一样,由他们在代码中出现的次序决定,即--"先出现先执行". 但是不同的普通代码块即 ...

  9. 静态同步synchronized方法和synchronized(class)代码块

    关键字synchronized还可以应用在static静态方法上,如果这样写,那是对当前的*.java文件对应的Class类进行持锁. package synStaticMethod; /** * C ...

随机推荐

  1. 在Linux下不使用密码远程登陆其他Linux

    有时需要再一台Linux上登陆其他Linux服务器,通常可以直接使用SSH命令,加入两台服务器一台服务器A,IP地址192.168.1.2,另一台服务器B,IP地址192.168.1.3,如果想从A服 ...

  2. 在ubuntu下利用minicom实现串口通信

    windos有串口调试助手,linux下也有这样的工具——minicom.不过,minicom和linux下的许多工具都一样,也是命令行模式,没有图形化界面供我们享受.作为一款串口调试工具,虽然难看但 ...

  3. S3C2440触摸屏控制总结

    触摸屏控制原理,其实与ADC读取一个滑动变阻器中间触点电压的原理一样.只不过,读取触摸屏的X.Y方向上的电压需要两次,而且需要设置其工作模式以实现一个ADC读取两个通道的电压. S3C2440的ADC ...

  4. Android中的pix,sp,dp相关概念

    px( pixel) 像素,可以简单的理解为一个点或方块,用以颜色的显示(单位),一般指印刷品或屏幕设置设备的颜色显示定义. dip(device independent pixels)设备独立像素. ...

  5. Remove linked list elements | leetcode

    Remove all elements from a linked list of integers that have value val. Example Given: 1 --> 2 -- ...

  6. .where(provider).FirstOrDefault()和.FirstOrDefault(provider)的性能比较

    最近遇到一个关于Linq的问题,.where(provider).FirstOrDefault();和.FirstOrDefault(provider);的性能比较 关于这个主要有以下三种说法,但这方 ...

  7. JDK神坑:JAVA中Calendar的月份Month少1

    很多朋友在使初次使用Calendar时,会发现月份莫名其妙对不上,显示的结果总是比预期中小1个月,检查好几遍也没发现程序有错,于是开始抓狂.其实这个时候,只要去看JDK就会明白问题所在.JDK告诉我们 ...

  8. Google java编程技术规范

    不遵循规范的程序猿,不是好的coder. 学习java有一段时间了,一直想找java编程技术规范来学习一下,幸而网络资源丰富,各路玩家乐于分享,省去了好多麻烦,姑且算站在网友的肩上,砥砺前行. /** ...

  9. skiplist 跳表(1)

    最近学习中遇到一种新的数据结构,很实用,搬过来学习. 原文地址:skiplist 跳表   为什么选择跳表 目前经常使用的平衡数据结构有:B树,红黑树,AVL树,Splay Tree, Treep等. ...

  10. TFS环境搭建

    这篇文章主要介绍了微软源代码管理工具TFS2013安装与使用图文教程,本文详细的给出了TFS2013的安装配置过程.使用教程,需要的朋友可以参考下 最近公司新开发一个项目要用微软的TFS2013进行项 ...