锁的作用

  锁是一种线程同步机制,用于实现互斥,当线程占用一个对象锁的时候,其它线程如果也想使用这个对象锁就需要排队。如果不使用对象锁,不同的线程同时操作一个变量的时候,有可能导致错误。让我们做一个测试:

class Entity {
public int value = 0;
}
class IncreaseThread implements Runnable {
@Override
public void run() {
for(int i=0;i < 100000; i++) {
AndOneTest.instance.value++;
}
}
}
public class AndOneTest {
public static Entity instance = new Entity();
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new IncreaseThread());
exec.execute(new IncreaseThread());
exec.shutdown();
Thread.sleep(5000);//等待两个线程执行结束
System.out.println("Value = " + instance.value);
}
}

5秒后输出以下结果,如果重新运行程序,得出的结果还会不同:

Value = 111260

我们创建了两个线程,这两个线程同时对AddOneTest.value执行十万次自增操作,我们期望的值是200000,然而得到的结果却并不是。假设两个线程都要对int value=0变量实现value++操作,value++操作会被虚拟机分成三步执行,

1.读取value当前的值。

2.将这个值加1。

3.将加1的结果写入value变量。

两个线程执行的顺序有可能是:

1.线程一读取value值(0)

2.线程二读取value值(0)

3.线程一将值加1(1)

4.线程二将值加1(1)

5.线程一将结果写入value变量(1)

6.线程二将结果写入value变量(1)

最后两个线程执行的结果是i=1,而不是i=2。因此我们无法得到Value =200000。线程之间的运行顺序的可不预测性会导致我们得不到正确的结果,因此我们需要加锁来保证结果是正确的。

Java内置锁

内置锁使用synchronized关键字定义,synchronized关键字有两种使用方法,一种是作为修饰词定义在方法中代码如下:

public synchronized void test() {
//临界区
}

另一种是指定一个对象,后面接一个代码块,代码如下:

public void test() {
     synchronized(object) {
//临界区
}
}

二者有两个区别:

1. 锁的对象不同,第一种方式获取的是当前对象的锁,相当于synchronized(this){},第二种方法获取的是指定对象的锁。

2. 作用域不同,第一种方式锁的是整个方法,第二种锁的只是代码块内部。

使用锁对上例自增测试的改进,只需要在AndOneTest.instance.value++外面加上如下代码即可:

synchronized(AndOneTest.instance) {
    AndOneTest.instance.value++;
}

  

运行后5秒后输出以下结果:

Value = 200000

这里有个需要注意的地方:1. 所有的锁都对应一个对象,同一个对象锁之间会互斥;2. 不同对象锁之间不会互斥;3. 没有申请锁的方法不和任何对象锁互斥。因此我们需要对哪个对象进行修改的时候就获取哪个对象的锁,这样就可以保证不同的线程不会同时修改这个对象。如果需要对两个对象修改,则应分别获取两个对象的锁,代码如下:

public void change() {
synchronized(instanceA) {
//对instanceA进行修改
}//释放对instanceA的锁
synchronized(instanceB) {
//对instanceB进行修改
}//释放对instanceB的锁
}

当synchronized关键字修饰static方法时,获取的就不是当前对象的锁了,而是类对象锁,因为调用static方法时可能类还没有对象。实际上类锁也有一个对应的对象,它所对应的对象是ClassName.class。这个对象用于存储类信息的,在使用反射的时候经常使用到。

class TargetClass{
public synchronized static void sleepMethod() {
try {
Thread.sleep(3000);//睡3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我睡醒了");
}
public synchronized static void getLockMethod() {
synchronized(Target.class) {
System.out.println("我得到了class锁");
}
}
} class ExampleThread implements Runnable {
@Override
public void run() {
TargetClass.sleepMethod();
}
}
public class StaticLockTest {
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new ExampleThread());
exec.shutdown();
Thread.sleep(100);//等待新创建的线程获得锁
TargetClass.getLockMethod();
}
}

3秒钟之后输出以下结果:

我睡醒了

我得到了class锁

执行main()方法的线程我们称之为主线程,此外我们还通过线程池创建了一个新的线程,新线程执行sleepMethod(),主线程执行getLockMethod()。启动新线程后主线程会等待新线程0.1秒,以确保新线程拿到了锁,0.1秒之后,主线程想获得锁,但是锁已经被占用了,只能等到新线程执行完sleepMethod()方法。本例中synchronized static synchronized(TargetClass.class){}等价,获得的都是TargetClass.class对象的锁。

总结

未完待续。

公众号:今日说码。关注我的公众号,可查看连载文章。遇到不理解的问题,直接在公众号留言即可。

Java并发编程(四)锁的使用(上)的更多相关文章

  1. Java并发编程:锁的释放

    Java并发编程:锁的释放 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: #839496;} Ja ...

  2. Java 并发编程(四):如何保证对象的线程安全性

    01.前言 先让我吐一句肺腑之言吧,不说出来会憋出内伤的.<Java 并发编程实战>这本书太特么枯燥了,尽管它被奉为并发编程当中的经典之作,但我还是忍不住.因为第四章"对象的组合 ...

  3. 【Java并发编程四】关卡

    一.什么是关卡? 关卡类似于闭锁,它们都能阻塞一组线程,直到某些事件发生. 关卡和闭锁关键的不同在于,所有线程必须同时到达关卡点,才能继续处理.闭锁等待的是事件,关卡等待的是其他线程. 二.Cycli ...

  4. Java并发编程之锁机制

    锁分类 悲观锁与乐观锁 悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改.因此对于同一个数据的并发操作,悲观锁采取加锁的形式.悲观的认为,不加锁的并发操作一定会出问题 ...

  5. java并发编程:锁的相关概念介绍

    理解同步,最好先把java中锁相关的概念弄清楚,有助于我们更好的去理解.学习同步.java语言中与锁有关的几个概念主要是:可重入锁.读写锁.可中断锁.公平锁 一.可重入锁 synchronized和R ...

  6. Java并发编程 (四) 线程安全性

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.线程安全性-原子性-atomic-1 1.线程安全性 定义: 当某个线程访问某个类时,不管运行时环境 ...

  7. Java并发编程-各种锁

    安全性和活跃度通常相互牵制.我们使用锁来保证线程安全,但是滥用锁可能引起锁顺序死锁.类似地,我们使用线程池和信号量来约束资源的使用, 但是缺不能知晓哪些管辖范围内的活动可能形成的资源死锁.Java应用 ...

  8. Java并发编程(四):并发容器(转)

    解决并发情况下的容器线程安全问题的.给多线程环境准备一个线程安全的容器对象. 线程安全的容器对象: Vector, Hashtable.线程安全容器对象,都是使用 synchronized 方法实现的 ...

  9. 【Java并发编程】并发编程大合集-值得收藏

    http://blog.csdn.net/ns_code/article/details/17539599这个博主的关于java并发编程系列很不错,值得收藏. 为了方便各位网友学习以及方便自己复习之用 ...

  10. 【Java并发编程】并发编程大合集

    转载自:http://blog.csdn.net/ns_code/article/details/17539599 为了方便各位网友学习以及方便自己复习之用,将Java并发编程系列内容系列内容按照由浅 ...

随机推荐

  1. Ubuntu 17.04 upgrade to 17.10

    Just try  sudo do-release-upgrade if you get this An upgrade from 'zesty' to 'bionic' is not support ...

  2. QT5.9 新特性与版本回顾

    原文链接: http://blog.qt.io/blog/2017/05/31/qt-5-9-released 翻译内容如下,采用的是第三方某在线翻译软件,所以有些地方不是太精确,纵然大吉做了一定的调 ...

  3. weex 数据绑定,动态控制组件的显示内容及样式

    无论的原生开发还是weex开发,经常会需要我们对一些组件/控件动态赋值,在原生中,我们大家都知道,对控件setText就可以了,那么在weex中呢,我们需要怎么做呢,其实很简单,几行代码就可以搞定!首 ...

  4. 从golang-gin-realworld-example-app项目学写httpapi (四)

    https://github.com/gothinkster/golang-gin-realworld-example-app/blob/master/users/routers.go 路由定义 pa ...

  5. sort 、sorted、range、join方法 数字的正序、倒叙、翻转

    大家可以想象一下 如果一串数字 是混乱的没有顺序这个时候你想要排序怎么办呢?  这个时候顺势而生的就有了sort方法 把数字从大到小的排列----->sort() 只对列表 li = [3, 5 ...

  6. HBuilder:一个不错的web前端IDE(代码编辑器)

    Web前端开发,2000之后基本就是三剑客的天下.到现在DW也是不错的HTMLcoder,如今的前端开发早已是JS的天下.但是DW对于JS方面就弱爆了.DW虽然支持JS语法高亮也支持JQuery Jq ...

  7. Linux下的Mysql备份/恢复

    数据库逻辑备份 逻辑备份:将数据库的数据以逻辑的SQL语句的方式导出 查看帮助 mysqldump --help 0.数据库开启状态 1.备份某个特定的库: mysqldump -uroot -pro ...

  8. Linux Samba配置文件常用参数详解

    目录 1.全局参数 2.共享参数   Samba的主配置文件叫smb.conf,默认在/etc/samba/目录下. smb.conf含有多个段,每个段由段名开始,直到下个段名.每个段名放在方括号中间 ...

  9. Asp.Net MVC源码调试

    首先下载MVC源代码,下载地址为:https://aspnetwebstack.codeplex.com/ 打开项目,卸载test文件夹下的所有项目和System.Web.WebPages.Admin ...

  10. 以太网的 MAC 层

    一.MAC 层的硬件地址 在局域网中,主机的硬件地址又称为物理地址,或 MAC 地址.6个字节. IEEE 的注册管理机构 RA 负责向厂家分配地址字段的前三个字节(即高位 24 位,组织唯一标识符O ...