java的多线程安全,ReentrantLock与synchronized锁
前言##
多线程总的来说是一个很大的模块,所以虽然之前就想写但一直感觉有地方没有理解透,在经过了一段时间学习后,终于有点感觉了,在此写下随笔。
多线程安全问题##:
首先和大家讨论一下多线程为什么会不安全,大家先看下面的程序。
/**
- @author lw
*/
public class Test extends Thread{
public void run()
{
for(int i=1;i<=10;i++)
{
System.out.println(i);
}
}
public static void main(String args[])
{
Test t1=new Test();
Test t2=new Test();
Test t3=new Test();
Test t4=new Test();
t1.start();
t2.start();
t3.start();
t4.start();
}
上面这段程序大致意思就是新建了四个线程,每个线程的操作都是输出1-10,按说来应该按线程启动顺序依次输出,但其实并不是。<--12345678112345678910234567891091012345678910-->这是输出的结果。线程并没有顺序执行,原因就是线程的抢占。在线程一执行到一半,输出到8的时候,便被其他线程抢占,其他线程继续输出。
这样的并发会带来什么问题呢?大家请看下面这段代码。
/**
- @author lw
- */
public class Test extends Thread{
static int temp=1;
public void run()
{
temp++;
System.out.println(temp);
}
public static void main(String args[])
{
Test t1=new Test();
Test t2=new Test();
Test t3=new Test();
Test t4=new Test();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
大家可以上面的程序大致是定义了一个static的整形变量,然后每一个线程可以对这个变量加1,
首先static变量是全局共享的,每一个线程都能操作这个变量,问题就出在这里。如果有一次线程1运行,然后读入了该变量为1,这个时候线程2抢占,然后对该变量进行了加一的操作,此时线程1再继续运行,但该变量现在已经是2了,线程1读入的确是之前的1,在加一之后为2,就出问题了。这种问题我们称为线程之间不同步,因为线程之间的操作互相是不可见的。下面,我们深入讨论一下为什么会这样。
线程不同步的原因
线程之所以会不同步,本质原因在于每个线程的高速缓存区。每个线程在创建后会有自己的一个缓存区,在线程要访问主存中的变量的时候会先将主存中的变量加入缓存,然后进行操作,这样可以避免主存访问过于频繁,可以加快线程的执行效率(类似于cache)。但问题在于每个线程的缓存区之间不可见,如果载入的是主存中的同一个变量,分别进行了更改,就会出现线程不同步的问题。
不同步解决策略
好了,上面都是铺垫,下面才是重点,如何解决线程不同步问题。java给出了锁的概念。所谓锁形象一点理解就是一个线程在用一个资源就像一个人进了一扇门,如果不锁门,其他人也会进来,但如果加了锁,就意味着这个资源被这个线程独占,而且必须要退出了才能被其他线程使用。我们常用的就是synchronized锁,又叫同步锁。我们先看一下这个锁的效果。
/**
* @author lw
*
*/
public class Test extends Thread{
String lo="";
public void run()
{
synchronized(lo)
{
for(int i=1;i<=10;i++)
{
System.out.println(i);
}
}
}
public static void main(String args[])
{
Test t1=new Test();
Test t2=new Test();
Test t3=new Test();
Test t4=new Test();
t1.start();
t2.start();
t3.start()
t4.start();
}
}
在经过了上面的同步之后,线程便可以按顺序运行,因为在第一个线程开始后,他会获得变量lo的锁,然后执行下面的代码块,其他线程在得到这个锁之前会处于一个阻塞状态,等待第一个线程释放锁之后其他线程竞争,然后获得锁的线程继续代码块里的操作,这样就可以保证线程之间的异步了,接下来我们需要知道的是为什么加了锁可以实现同步。
synchronized是如何实现同步的##
好吧其实很简单,比较机智的读者可能已经猜到了,他其实是使各个线程之间的高速缓存区失效了,然后线程要获取该变量的时候需要在主存中读写,这个时候对该变量的操作对于各个线程之间是可见的,然后操作结束之后再刷新其缓存区,哈哈哈是不是很简单。。。
synchronized需注意的事项
大家要注意的是synchronized加锁的目标是对象,并不是代码块。这是初学者容易进入的误区。有人认为只要是synchronized里面的操作一定不会有问题, 但当这样想的时候其实你已经凉了。请看下面的代码。
/**
* @author lw
*
*/
public class Test extends Thread{
String lo=new String();
public void run()
{
synchronized(lo)
{
for(int i=1;i<=10;i++)
{
System.out.println(i);
}
}
}
public static void main(String args[])
{
Test t1=new Test();
Test t2=new Test();
Test t3=new Test();
Test t4=new Test();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
看上去与上面的没太大差别,但细心的读者会发现有一行变成了String lo=new String();这个时候的锁便没有任何意义,因为这个对象每一个线程都会new一个,也就是说每一个线程都会获得一个,所以完全不起作用。可能基础欠佳的同学会问之前的String lo=“”;为什么可以,因为每一个lo都会指向常量池(常量池这里不展开讲了 ,手要废了。。不知道的可以百度一下)中的同一个对象,所以每一个线程的还都是指向同一段主存,锁就会起作用。大家是不是觉得synchonized已经很完美了,no no no还有更完美的,rentrantlock闪亮登场!!!(打字好累啊。。。。#==)
reentrantlock
首先我们讨论一下synchonized的缺点。一是不灵活,synchonized在锁定之后必须要代码块结束之后才能释放锁,然后被其他线程获得。那么如果获取到锁的这个线程要执行非常长的时间呢,那其他的线程不是会一直阻塞在这里,这时如果有哪个线程生气了不想等了怎么办?抱歉不可以,需要一直等待。另一方面,同步锁的释放顺序也很固定,必须是加锁的反顺序,很不潇洒等等。。。但我们的reentrantlock就不一样了,话不多说先看代码。
/**
* @author lw
*
*/
public class Test extends Thread{
private static ReentrantLock lock =new ReentrantLock();
public void run()
{
try{
lock.lock();
for(int i=1;i<=10;i++)
{
System.out.println(i);
}
}
finally
{
lock.unlock();
}
}
public static void main(String args[])
{
Test t1=new Test();
Test t2=new Test();
Test t3=new Test();
Test t4=new Test();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
上面我们可以看到声明了ReentrantLock对象后只需调用其中的lock方法便可直接加锁,而释放锁需要unlock方法。这样一是很灵活,不需要代码块结束再释放,还有就是 ReentrantLock是可中断的,如果等待的线程不想等了,好说,interrupt掉就好了,另外, ReentrantLock可以设为悲观锁和乐观锁,而synchonized则默认为悲观锁,不可改变,不够灵活。所以综上,ReentrantLock更加灵活多变。但大家在使用时一定要记得unlock,最好写在finally里面防止忘记,不然就会造成其他线程阻塞。
多线程是一个很大的知识块,以上是笔者自己学习思考后的总结归纳,还有很多没有涉及到,另外分享内容如有不当之处望大家多多指正,共同进步~
下期预告###
radius缓存
java的多线程安全,ReentrantLock与synchronized锁的更多相关文章
- 死磕 java同步系列之ReentrantLock VS synchronized——结果可能跟你想的不一样
问题 (1)ReentrantLock有哪些优点? (2)ReentrantLock有哪些缺点? (3)ReentrantLock是否可以完全替代synchronized? 简介 synchroniz ...
- Java基础-多线程-③线程同步之synchronized
使用线程同步解决多线程安全问题 上一篇 Java基础-多线程-②多线程的安全问题 中我们说到多线程可能引发的安全问题,原因在于多个线程共享了数据,且一个线程在操作(多为写操作)数据的过程中,另一个线程 ...
- java并发系列(三)-----ReentrantLock(重入锁)功能详解和应用演示
1. ReentrantLock简介 jdk中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantLock.虽然在性能上ReentrantLock和synchronize ...
- Java多线程(九) synchronized 锁对象的改变
public class MyService { private String lock = "123"; public void testMethod() { synchroni ...
- Java多线程之ReentrantLock重入锁简介与使用教程
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6543947.html 我们知道,线程安全问题需要通过线程之间的同步来解决,而同步大多使用syncrhoize ...
- Java中的ReentrantLock和synchronized两种锁机制的对比
原文:http://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html 多线程和并发性并不是什么新内容,但是 Java 语言设计中的创新之 ...
- Java:多线程,线程同步,同步锁(Lock)的使用(ReentrantLock、ReentrantReadWriteLock)
关于线程的同步,可以使用synchronized关键字,或者是使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象.本文探讨Lock对象. synchronize ...
- Java多线程4:synchronized锁机制
脏读 一个常见的概念.在多线程中,难免会出现在多个线程中对同一个对象的实例变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数据其实是被更改过 ...
- Java多线程6:Synchronized锁代码块(this和任意对象)
一.Synchronized(this)锁代码块 用关键字synchronized修饰方法在有些情况下是有弊端的,若是执行该方法所需的时间比较长,线程1执行该方法的时候,线程2就必须等待.这种情况下就 ...
随机推荐
- 【转】使用Badboy录制脚本,作为JMeter测试的素材
接触Badboy,是因为JMeter要引用Badboy导出的脚本 Badboy的录制提供两个模式:Request(默认模式) 和navigation模式.点击下图N,切换模式:但是要导出到Jmeter ...
- 使用wireshark观察SSL/TLS握手过程--双向认证/单向认证
SSL/TLS握手过程可以分成两种类型: 1)SSL/TLS 双向认证,就是双方都会互相认证,也就是两者之间将会交换证书.2)SSL/TLS 单向认证,客户端会认证服务器端身份,而服务器端不会去对客户 ...
- Pycharm快速复制当前行到下一行Ctrl+D
Pycharm快速复制当前行到下一行Ctrl+D
- php获取当前月月初至月末的时间戳,上个月月初至月末的时间戳
当前月 <?php $thismonth = date('m'); $thisyear = date('Y'); $startDay = $thisyear . '-' . $thismonth ...
- Unity3D的坑系列:你真想发布WinPhone版吗?
Unity 4.2加入了支持WinPhone发布,本来是一件令人开心的事情,不过最近听了Unity技术支持的一个事情后就发现,原来发布WinPhone版也是一个坑. 实际上如果你用Unity做小游戏发 ...
- Python Twisted系列教程14:Deferred用于同步环境
作者:dave@http://krondo.com/when-a-deferred-isnt/ 译者:杨晓伟(采用意译) 你可以从这里从头开始阅读这个系列. 介绍 这部分我们要介绍Deferred的 ...
- Rest Framework 认证源码流程
一.请求到来之后,都要先执行dispatch方法,dispatch方法方法根据请求方式的不同触发get/post/put/delete等方法 注意,APIView中的dispatch方法有很多的功能 ...
- 十分钟学会Fiddler
一.Fiddler介绍 Fiddler是一个http抓包改包工具,fiddle英文中有“欺骗.伪造”之意,与wireshark相比它更轻量级,上手简单,因为只能抓http和https数据包,所以在针对 ...
- http://www.jb51.net/list/list_233_2.htm(导航: 首页 >> 软件编程 >> Android)
日期:2015-04-24理解Android中Activity的方法回调 日期:2015-04-24Android获取手机通讯录.sim卡联系人及调用拨号界面方法 日期:2015-04-24And ...
- Elasticsearch之kopf插件安装之后的浏览详解
前提, Elasticsearch之插件介绍及安装 https://i.cnblogs.com/posts?categoryid=950999&page=2 (强烈建议,从头开始看) 比如, ...