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缓存

时间: 2024-10-14 06:11:46

java的多线程安全,ReentrantLock与synchronized锁的相关文章

Java Synchronized 锁的实现原理详解及偏向锁-轻量锁-重量锁

Synchronize是重量级锁吗?是互斥锁吗? 它的实现原理? 前言 线程安全是并发编程中的重要关注点,造成线程安全问题的主要诱因有两点,一是存在共享数据(也称临界资源),二是存在多个线程共同操作共享数据.因此为了解决这个问题,我们可能需要这样一个方案,当存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,这种方式叫互斥锁,即能达到互斥访问目的的锁,也就是说当一个共享数据被当前正在访问的线程加上互斥锁后,在同一个时刻,其他线程只

java中多线程模拟(多生产,多消费,Lock实现同步锁,替代synchronized同步代码块)

import java.util.concurrent.locks.*; class DuckMsg{ int size;//烤鸭的大小 String id;//烤鸭的厂家和标号 DuckMsg(){ } DuckMsg(int size, String id){ this.size=size; this.id=id; } public String toString(){ return id + " 大小为:" + size; } } class Duck{ private int

Java多线程4:synchronized锁机制

脏读 一个常见的概念.在多线程中,难免会出现在多个线程中对同一个对象的实例变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数据其实是被更改过的. 多线程线程安全问题示例 看一段代码: public class ThreadDomain13 { private int num = 0; public void addNum(String userName) { try { if ("a".equals(userName)) {

java 多线程8 : synchronized锁机制 之 方法锁

脏读 一个常见的概念.在多线程中,难免会出现在多个线程中对同一个对象的实例变量或者全局静态变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数据其实是被更改过的.注意这里 局部变量是不存在脏读的情况 多线程线程实例变量非线程安全 看一段代码: public class ThreadDomain13 { private int num = 0; public void addNum(String userName) { try { if ("

Java多线程和并发(九),ReentrantLock(公平锁)

目录 1.ReentrantLock 2.ReentrantLock的实现 3.synchronized和ReentrantLock的区别 九.ReentrantLock(公平锁) 1.ReentrantLock 2.ReentrantLock的实现 public class ReentrantLockDemo implements Runnable{ private static ReentrantLock lock = new ReentrantLock(true); @Override p

Java多线程总结之由synchronized说开去(转)

这几天不断添加新内容,给个大概的提纲吧,方面朋友们阅读,各部分是用分割线隔开了的: synchronized与wait()/notify() JMM与synchronized ThreadLocal与synchronized ReentrantLock与synchronized 最重要一条: synchronized是针对对象的隐式锁使用的,注意是对象! 举个小例子,该例子没有任何业务含义,只是为了说明synchronized的基本用法: Java代码   Class MyClass(){ sy

Java中的ReentrantLock和synchronized两种锁定机制的对比

多线程和并发性并不是什么新内容,但是 Java 语言设计中的创新之一就是,它是第一个直接把跨平台线程模型和正规的内存模型集成到语言中的主流语言.核心类库包含一个 Thread 类,可以用它来构建.启动和操纵线程,Java 语言包括了跨线程传达并发性约束的构造 —— synchronized 和 volatile .在简化与平台无关的并发类的开发的同时,它决没有使并发类的编写工作变得更繁琐,只是使它变得更容易了. synchronized 快速回顾 把代码块声明为 synchronized,有两个

(转) Java中的ReentrantLock和synchronized两种锁定机制的对比

原文:http://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html 多线程和并发性并不是什么新内容,但是 Java 语言设计中的创新之一就是,它是第一个直接把跨平台线程模型和正规的内存模型集成到语言中的主流语言.核心类库包含一个 Thread 类,可以用它来构建.启动和操纵线程,Java 语言包括了跨线程传达并发性约束的构造 -- synchronized 和 volatile .在简化与平台无关的并发类的开发的同时,它决没有使并

[转] Java的ReentrantLock和synchronized两种锁定机制

多线程和并发性并不是什么新内容,但是 Java 语言设计中的创新之一就是,它是第一个直接把跨平台线程模型和正规的内存模型集成到语言中的主流语言.核心类库包含一个 Thread 类,可以用它来构建.启动和操纵线程,Java 语言包括了跨线程传达并发性约束的构造 —— synchronized 和 volatile .在简化与平台无关的并发类的开发的同时,它决没有使并发类的编写工作变得更繁琐,只是使它变得更容易了. synchronized 快速回顾 把代码块声明为 synchronized,有两个