Java多线程5:synchronized锁方法块
synchronized同步代码块
用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间。这种情况下可以尝试使用synchronized同步语句块来解决问题。看一下例子:
public class ThreadDomain18
{
public void doLongTimeTask() throws Exception
{
for (int i = 0; i < 100; i++)
{
System.out.println("nosynchronized threadName = " +
Thread.currentThread().getName() + ", i = " + (i + 1));
}
System.out.println();
synchronized (this)
{
for (int i = 0; i < 100; i++)
{
System.out.println("synchronized threadName = " +
Thread.currentThread().getName() + ", i = " + (i + 1));
}
}
}
}
public class MyThread18 extends Thread
{
private ThreadDomain18 td; public MyThread18(ThreadDomain18 td)
{
this.td = td;
} public void run()
{
try
{
td.doLongTimeTask();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args)
{
ThreadDomain18 td = new ThreadDomain18();
MyThread18 mt0 = new MyThread18(td);
MyThread18 mt1 = new MyThread18(td);
mt0.start();
mt1.start();
}
运行结果,分两部分来看:
synchronized threadName = Thread-1, i = 1
synchronized threadName = Thread-1, i = 2
nosynchronized threadName = Thread-0, i = 95
synchronized threadName = Thread-1, i = 3
nosynchronized threadName = Thread-0, i = 96
synchronized threadName = Thread-1, i = 4
nosynchronized threadName = Thread-0, i = 97
synchronized threadName = Thread-1, i = 5
nosynchronized threadName = Thread-0, i = 98
synchronized threadName = Thread-1, i = 6
nosynchronized threadName = Thread-0, i = 99
synchronized threadName = Thread-1, i = 7
nosynchronized threadName = Thread-0, i = 100
...
synchronized threadName = Thread-1, i = 98
synchronized threadName = Thread-1, i = 99
synchronized threadName = Thread-1, i = 100
synchronized threadName = Thread-0, i = 1
synchronized threadName = Thread-0, i = 2
synchronized threadName = Thread-0, i = 3
...
这个实验可以得出以下两个结论:
1、当A线程访问对象的synchronized代码块的时候,B线程依然可以访问对象方法中其余非synchronized块的部分,第一部分的执行结果证明了这一点
2、当A线程进入对象的synchronized代码块的时候,B线程如果要访问这段synchronized块,那么访问将会被阻塞,第二部分的执行结果证明了这一点
所以,从执行效率的角度考虑,有时候我们未必要把整个方法都加上synchronized,而是可以采取synchronized块的方式,对会引起线程安全问题的那一部分代码进行synchronized就可以了。
两个synchronized块之间具有互斥性
如果线程1访问了一个对象A方法的synchronized块,那么线程B对同一对象B方法的synchronized块的访问将被阻塞,写个例子来证明一下:
public class ThreadDomain19
{
public void serviceMethodA()
{
synchronized (this)
{
try
{
System.out.println("A begin time = " + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("A end time = " + System.currentTimeMillis());
}
catch (InterruptedException e)
{
e.printStackTrace();
} }
} public void serviceMethodB()
{
synchronized (this)
{
System.out.println("B begin time = " + System.currentTimeMillis());
System.out.println("B end time = " + System.currentTimeMillis());
}
}
}
写两个线程分别调用这两个方法:
public class MyThread19_0 extends Thread
{
private ThreadDomain19 td; public MyThread19_0(ThreadDomain19 td)
{
this.td = td;
} public void run()
{
td.serviceMethodA();
}
}
public class MyThread19_1 extends Thread
{
private ThreadDomain19 td; public MyThread19_1(ThreadDomain19 td)
{
this.td = td;
} public void run()
{
td.serviceMethodB();
}
}
写个main函数:
public static void main(String[] args)
{
ThreadDomain19 td = new ThreadDomain19();
MyThread19_0 mt0 = new MyThread19_0(td);
MyThread19_1 mt1 = new MyThread19_1(td);
mt0.start();
mt1.start();
}
看一下运行结果:
A begin time = 1443843271982
A end time = 1443843273983
B begin time = 1443843273983
B end time = 1443843273983
看到对于serviceMethodB()方法synchronized块的访问必须等到对于serviceMethodA()方法synchronized块的访问结束之后。那其实这个例子,我们也可以得出一个结论:synchronized块获得的是一个对象锁,换句话说,synchronized块锁定的是整个对象。
synchronized块和synchronized方法
既然上面得到了一个结论synchronized块获得的是对象锁,那么如果线程1访问了一个对象方法A的synchronized块,线程2对于同一对象同步方法B的访问应该是会被阻塞的,因为线程2访问同一对象的同步方法B的时候将会尝试去获取这个对象的对象锁,但这个锁却在线程1这里。写一个例子证明一下这个结论:
public class ThreadDomain20
{
public synchronized void otherMethod()
{
System.out.println("----------run--otherMethod");
} public void doLongTask()
{
synchronized (this)
{
for (int i = 0; i < 1000; i++)
{
System.out.println("synchronized threadName = " +
Thread.currentThread().getName() + ", i = " + (i + 1));
try
{
Thread.sleep(5);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
}
写两个线程分别调用这两个方法:
public class MyThread20_0 extends Thread
{
private ThreadDomain20 td; public MyThread20_0(ThreadDomain20 td)
{
this.td = td;
} public void run()
{
td.doLongTask();
}
}
public class MyThread20_1 extends Thread
{
private ThreadDomain20 td; public MyThread20_1(ThreadDomain20 td)
{
this.td = td;
} public void run()
{
td.otherMethod();
}
}
写个main函数调用一下,这里"mt0.start()"后sleep(100)以下是为了确保mt0线程先启动:
public static void main(String[] args) throws Exception
{
ThreadDomain20 td = new ThreadDomain20();
MyThread20_0 mt0 = new MyThread20_0(td);
MyThread20_1 mt1 = new MyThread20_1(td);
mt0.start();
Thread.sleep(100);
mt1.start();
}
看一下运行结果:
...
synchronized threadName = Thread-0, i = 995
synchronized threadName = Thread-0, i = 996
synchronized threadName = Thread-0, i = 997
synchronized threadName = Thread-0, i = 998
synchronized threadName = Thread-0, i = 999
synchronized threadName = Thread-0, i = 1000
----------run--otherMethod
证明了我们的结论。为了进一步完善这个结论,把"otherMethod()"方法的synchronized去掉再看一下运行结果:
...
synchronized threadName = Thread-0, i = 16
synchronized threadName = Thread-0, i = 17
synchronized threadName = Thread-0, i = 18
synchronized threadName = Thread-0, i = 19
synchronized threadName = Thread-0, i = 20
----------run--otherMethod
synchronized threadName = Thread-0, i = 21
synchronized threadName = Thread-0, i = 22
synchronized threadName = Thread-0, i = 23
...
"otherMethod()"方法和"doLongTask()"方法中的synchronized块异步执行了
将任意对象作为对象监视器
总结一下前面的内容:
1、synchronized同步方法
(1)对其他synchronized同步方法或synchronized(this)同步代码块呈阻塞状态
(2)同一时间只有一个线程可以执行synchronized同步方法中的代码
2、synchronized同步代码块
(1)对其他synchronized同步方法或synchronized(this)同步代码块呈阻塞状态
(2)同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码
前面都使用synchronized(this)的格式来同步代码块,其实Java还支持对"任意对象"作为对象监视器来实现同步的功能。这个"任意对象"大多数是实例变量及方法的参数,使用格式为synchronized(非this对象)。看一下将任意对象作为对象监视器的使用例子:
public class ThreadDomain21
{
private String userNameParam;
private String passwordParam;
private String anyString = new String(); public void setUserNamePassword(String userName, String password)
{
try
{
synchronized (anyString)
{
System.out.println("线程名称为:" + Thread.currentThread().getName() +
"在 " + System.currentTimeMillis() + " 进入同步代码块");
userNameParam = userName;
Thread.sleep(3000);
passwordParam = password;
System.out.println("线程名称为:" + Thread.currentThread().getName() +
"在 " + System.currentTimeMillis() + " 离开同步代码块");
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
写两个线程分别调用一下:
public class MyThread21_0 extends Thread
{
private ThreadDomain21 td; public MyThread21_0(ThreadDomain21 td)
{
this.td = td;
} public void run()
{
td.setUserNamePassword("A", "AA");
}
}
public class MyThread21_1 extends Thread
{
private ThreadDomain21 td; public MyThread21_1(ThreadDomain21 td)
{
this.td = td;
} public void run()
{
td.setUserNamePassword("B", "B");
}
}
写一个main函数调用一下:
public static void main(String[] args)
{
ThreadDomain21 td = new ThreadDomain21();
MyThread21_0 mt0 = new MyThread21_0(td);
MyThread21_1 mt1 = new MyThread21_1(td);
mt0.start();
mt1.start();
}
看一下运行结果:
线程名称为:Thread-0在 1443855101706 进入同步代码块
线程名称为:Thread-0在 1443855104708 离开同步代码块
线程名称为:Thread-1在 1443855104708 进入同步代码块
线程名称为:Thread-1在 1443855107708 离开同步代码块
这个例子证明了:多个线程持有"对象监视器"为同一个对象的前提下,同一时间只能有一个线程可以执行synchronized(非this对象x)代码块中的代码。
锁非this对象具有一定的优点:如果在一个类中有很多synchronized方法,这时虽然能实现同步,但会受到阻塞,从而影响效率。但如果同步代码块锁的是非this对象,则synchronized(非this对象x)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,大大提高了运行效率。
注意一下"private String anyString = new String();"这句话,现在它是一个全局对象,因此监视的是同一个对象。如果移到try里面,那么对象的监视器就不是同一个了,调用的时候自然是异步调用,可以自己试一下。
最后提一点,synchronized(非this对象x),这个对象如果是实例变量的话,指的是对象的引用,只要对象的引用不变,即使改变了对象的属性,运行结果依然是同步的。
细化synchronized(非this对象x)的三个结论
synchronized(非this对象x)格式的写法是将x对象本身作为对象监视器,有三个结论得出:
1、当多个线程同时执行synchronized(x){}同步代码块时呈同步效果
2、当其他线程执行x对象中的synchronized同步方法时呈同步效果
3、当其他线程执行x对象方法中的synchronized(this)代码块时也呈同步效果
第一点很明显,第二点和第三点意思类似,无非一个是同步方法,一个是同步代码块罢了,举个例子验证一下第二点:
public 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());
}
}
ThreadDomain24中持有MyObject的引用:
public class ThreadDomain24
{
public void testMethod1(MyObject mo)
{
try
{
synchronized (mo)
{
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();
}
}
}
写两个线程分别调用"speedPrintString()"方法和"testMethod1(MyObject mo)"方法:
public class MyThread24_0 extends Thread
{
private ThreadDomain24 td;
private MyObject mo; public MyThread24_0(ThreadDomain24 td, MyObject mo)
{
this.td = td;
this.mo = mo;
} public void run()
{
td.testMethod1(mo);
}
}
public class MyThread24_1 extends Thread
{
private MyObject mo; public MyThread24_1(MyObject mo)
{
this.mo = mo;
} public void run()
{
mo.speedPrintString();
}
}
写一个main函数启动这两个线程:
public static void main(String[] args)
{
ThreadDomain24 td = new ThreadDomain24();
MyObject mo = new MyObject();
MyThread24_0 mt0 = new MyThread24_0(td, mo);
MyThread24_1 mt1 = new MyThread24_1(mo);
mt0.start();
mt1.start();
}
看一下运行结果:
testMethod1__getLock time = 1443855939811, run ThreadName = Thread-0
testMethod1__releaseLock time = 1443855944812, run ThreadName = Thread-0
speedPrintString__getLock time = 1443855944812, run ThreadName = Thread-1
----------
speedPrintString__releaseLock time = 1443855944812, run ThreadName = Thread-1
看到"speedPrintString()"方法必须等待"testMethod1(MyObject mo)"方法执行完毕才可以执行,没有办法异步执行,证明了第二点的结论。第三点的验证方法类似,就不写代码证明了。
Java多线程5:synchronized锁方法块的更多相关文章
- java 多线程9 : synchronized锁机制 之 代码块锁
synchronized同步代码块 用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间.这种情况下可以尝试使用 ...
- java 多线程8 : synchronized锁机制 之 方法锁
脏读 一个常见的概念.在多线程中,难免会出现在多个线程中对同一个对象的实例变量或者全局静态变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数 ...
- Java多线程学习——synchronized锁机制
Java在多线程中使用同步锁机制时,一定要注意锁对对象,下面的例子就是没锁对对象(每个线程使用一个被锁住的对象时,得先看该对象的被锁住部分是否有人在使用) 例子:两个人操作同一个银行账户,丈夫在ATM ...
- Java多线程6:Synchronized锁代码块(this和任意对象)
一.Synchronized(this)锁代码块 用关键字synchronized修饰方法在有些情况下是有弊端的,若是执行该方法所需的时间比较长,线程1执行该方法的时候,线程2就必须等待.这种情况下就 ...
- Java多线程-同步:synchronized 和线程通信:生产者消费者模式
大家伙周末愉快,小乐又来给大家献上技术大餐.上次是说到了Java多线程的创建和状态|乐字节,接下来,我们再来接着说Java多线程-同步:synchronized 和线程通信:生产者消费者模式. 一.同 ...
- Java多线程系列--“JUC锁”03之 公平锁(一)
概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...
- Java多线程系列--“JUC锁”01之 框架
本章,我们介绍锁的架构:后面的章节将会对它们逐个进行分析介绍.目录如下:01. Java多线程系列--“JUC锁”01之 框架02. Java多线程系列--“JUC锁”02之 互斥锁Reentrant ...
- Java多线程系列--“JUC锁”06之 Condition条件
概要 前面对JUC包中的锁的原理进行了介绍,本章会JUC中对与锁经常配合使用的Condition进行介绍,内容包括:Condition介绍Condition函数列表Condition示例转载请注明出处 ...
- Java多线程系列--“JUC锁”05之 非公平锁
概要 前面两章分析了"公平锁的获取和释放机制",这一章开始对“非公平锁”的获取锁/释放锁的过程进行分析.内容包括:参考代码获取非公平锁(基于JDK1.7.0_40)释放非公平锁(基 ...
随机推荐
- java socket 网络编程常见异常
1.java.net.SocketTimeoutException 这个异常比较常见,socket超时.一般有2个地方会抛出这个,一个是connect的时候,这个超时参数由connect(Socket ...
- MVC中使用EF:排序,过滤,分页
原文链接:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/sorting-filtering-and-paging ...
- nfs:环境搭建
准备环境 通过VirtualBox创建两台虚拟机client1和client2,这两台虚拟机和物理主机组成一个网络.将物理主机作为NFS的服务端,虚拟机client1和client2作为NFS的客户端 ...
- 基于现有数据库的Code First模式迁移更新数据库
本文讨论的内容是基于EF4.1版本.文中谈论的现有的数据库不是由EF创建.本文假定你已经对Code First迁移有一定的了解,如果不了解Code First迁移更新数据库可以查看 文章涉及的主题如下 ...
- 关于HTTP session随笔
1).访问*.html的静态资源因为不会被编译为Servlet,也就不涉及session的问题. 2).当JSP页面没有显式禁止session的时候,在打开浏览器第一次请求该jsp的时候,服务器会自动 ...
- 大前端学习笔记整理【一】CSS盒模型与基于盒模型的6种元素居中方案
概览 CSS盒模型,规定了元素框来处理元素的 内容.内边距.边框和外边距的方式 元素部分是指内容部分,也是最实际的内容,包围内容的称之为内边距,内边距外围是边框,边框外围就是外边距:且外边距是透明的, ...
- archlinux 安装过程记录
2014年安装了一次,使用U盘启动安装的,但是当时网络有问题,断断续续,没有做详细记录. 现在到了杭州,重新来一次. 使用U盘安装 下载ISO :http://mirrors.163.com/arch ...
- 虚拟机上安装Linux操作系统
很久之前就知道虚拟机这个东西,也都在虚拟机上安装过Windows的操作系统和Linux的操作系统,但是一直都没有去做笔记. 最近还是比较有时间,就移除了前两天刚刚安装的Linux系统,重新安装一次,做 ...
- IIS处理并发请求时出现的问题及解决
一个ASP.NET项目在部署到生产环境时,当用户并发量达到200左右时,IIS出现了明显的请求排队现象,发送的请求都进入等待,无法及时响 应,系统基本处于不可用状态.因经验不足,花了很多时间精力解决这 ...
- 安装dos2unix
在http://linux.softpedia.com/progDownload/Dos2Unix-Download-5519.html下载hd2u-1.0.0.tgz [root@localhost ...