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就必须等待.这种情况下就 ...
随机推荐
- centos7防火墙 启动和关闭
CentOS 7.0默认使用的是firewall作为防火墙,这里改为iptables防火墙.firewall:systemctl start firewalld.service #启动firewa ...
- 几大PHP套件
UPUPW:http://www.upupw.net/ PHPStudy:http://www.phpstudy.net/ PHPNow:http://servkit.org/
- Laravel5 cookie和session设置 Cookie::queue()自动添加
Cookies Laravel会加密所有已创建的cookie信息,并附加上授权码,当客户端擅自修改cookie信息时,该cookie将被废弃,从而保证安全性. 获取一个指定的cookie值 $valu ...
- maven学习5 构建MyBatis项目
2. 修改pom.xml,添加MyBatis依赖 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi=& ...
- List和Map常用的几种遍历方式
遍历一个List有以下几种方法: /*//1.普通for循环的方式: List<String> list=new ArrayList<String>(); list.add(& ...
- ORACLE 存储过程解及表解锁和停止执行
查看进程: select * from v$process 根据存储过程名称查找是否被锁: select * FROM dba_ddl_locks where name =upper('sp_1'); ...
- sata2.0和sata3.0的区别
sata是指电脑主板上的硬盘接口,3.0是2.0的升级版本,发布于2009年,所以2010年之前的电脑主板基本都只提供sata2.0接口,如果你不知道自己的电脑是sata2.0还是sata3.0,想想 ...
- SSD知识
不管什么接口的SSD,一般都由以下部分组成:主控,Flash,板,壳,品牌.下面本佬就这些部分一一发帖,仅供娱乐参考,不作任何推荐和偏向,有不同见解请直接发表,有任何错误,请直接指正,不为吵架,只为娱 ...
- Python可执行对象——exec、eval、compile
Python提供的调用可执行对象的内建函数进行说明,涉及exec.eval.compile三个函数.exec语句用来执行存储在代码对象.字符串.文件中的Python语句,eval语句用来计算存储在代码 ...
- vue简单路由(二)
在实际项目中我们会碰到多层嵌套的组件组合而成,但是我们如何实现嵌套路由呢?因此我们需要在 VueRouter 的参数中使用 children 配置,这样就可以很好的实现路由嵌套. index.html ...