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就必须等待.这种情况下就 ...
随机推荐
- 【转】 Pro Android学习笔记(九六):AsyncTask(5):横竖屏切换问题
目录(?)[-] 横竖屏切换的问题 WeakReference 文章转载只能用于非商业性质,且不能带有虚拟货币.积分.注册等附加条件.转载须注明出处:http://blog.csdn.net/flow ...
- 【转】JMeter入门
一.JMeter概述 JMeter就是一个测试工具,相比于LoadRunner等测试工具,此工具免费,且比较好用,但是前提当然是安装Java环境: JMeter可以做 (1)压力测试及性能测试: (2 ...
- java中split的用法即回顾
package com.b; public class Cor { public static void main(String[] args) { String a = "this is ...
- PL/SQL 训练03 --异常
--程序员在开发的时候,经常天真的认为这个世界是完美的,用户如同自己般聪明,总能按照自己设想的方式--操作系统输入数据.但残酷的事实告诉我们,这是不可能的事情,用户总会跟我们相反的方式操作系统--于是 ...
- vs2017发布web项目
1.webform项目发布 1.右键选择项目. 2.选择自定义. 3.“配置文件名称”,发布后生成在项目中记录此次发布选择的配置信息文件名,下次发布默认为此次选择的配置信息,可以删除,随便填一个. 4 ...
- Py修行路 python基础(六)前期基础整理
计算机中,有且仅有CPU具有执行权限,能够执行指令的只有CPU! 人在应用程序上输入的文字或文本,叫做明文! 在屏幕上输入或是输出的是字符,计算机保存的是 有字符编码的 二进制数. 变量赋值规则:例如 ...
- 2014.8.25 CAD系统事件触发流程
各进近.离场.进场Arinc424数据录入界面在CADDataManager/UC/UCIAP(UCSID)下 UCAirport是一抽象用户控件类,在FormADHP初始化时实例化成airport控 ...
- 自定义inputformat和outputformat
1. 自定义inputFormat 1.1 需求 无论hdfs还是mapreduce,对于小文件都有损效率,实践中,又难免面临处理大量小文件的场景,此时,就需要有相应解决方案 1.2 分析 小文件的优 ...
- IdentityHashMap
区别与其他的键不能重复的容器,IdentityHashMap允许key值重复,但是——key必须是两个不同的对象,即对于k1和k2,当k1==k2时,IdentityHashMap认为两个key相等, ...
- linux中memset的正确用法
linux中memset的正确用法 [起因]希望对各种类型的数组进行初始化,避免野值 [函数头文件] 提示:在linux中可以在terminal中输入 "man memset"进行 ...