多线程学习三:ThreadLocal,synchronized,volatile和Condition

ThreadLocal

1、ThreadLocal 是线程的局部变量, 是每一个线程所单独持有的,其他线程不能对其进行访问。

2、hreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题 :

     1.在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写.

     2.而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

3、先了解一下ThreadLocal类提供的几个方法:

    public T get() { } // 用来获取ThreadLocal在当前线程中保存的变量副本

  public void set(T value) { } //set()用来设置当前线程中变量的副本

  public void remove() { } //remove()用来移除当前线程中变量的副本

  protected T initialValue() { } //initialValue()是一个protected方法,一般是用来在使用时进行重写的

4、ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取,每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。

5、从上面的结构图,我们已经窥见ThreadLocal的核心机制:

  • 每个Thread线程内部都有一个Map。
  • Map里面存储线程本地对象(key)和线程的变量副本(value)
  • 但是,Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。

三.ThreadLocal详解

ThreadLocal这个对象就是为多线程而生的,没有了多线程ThreadLocal就没有存在的必要了。可以将任何你想在每个线程独享的对象放置其中,并在任何时候取出来。

使用ThreadLocal不会导致内存泄漏。

public class ThreadLocalUse{
    //创建一个静态的ThreadLocal对象,当做仓库,存储每个线程自己的资源
    //此处用Object来代替,可以使连接Connection
    private static ThreadLocal<Object> store=new ThreadLocal<Object>();
    
    //当每个线程要获取一个线程资源的时候,调用get方法
    public Object get(){
        //首先去仓库中去寻找
        Object obj=store.get();
        //如果不存在那就创建一个给线程,并把创建的放到仓库中
        if(obj==null){
            obj=new Object();
            set(obj);
        }
        return obj;
    }
    
    //想将线程资源放到仓库调用set方法,
    public void set(Object obj){
        store.set(obj);
    }
}

对比ThreadLocal和synchronized同步机制:

  很多人都会对这两个对象进行比对,我也谈一下我自己的想法。

  使用synchronized是为了将多条语句进行原子化操作,比说对于递增操作i++,任意一个线程在执行代码时都要保证别的线程不能执行这个代码,否则就会产生脏数据,使用synchronized可以避免这一点。而使用ThreadLocal就是给每个线程存储对象用的。既然每个线程使用了自己的对象,没有了竞争就不会出现多线程相关的问题。

四:线程锁synchronized和Lock和volatile和Condition :

多线程怎么防止竞争资源,即防止对同一资源进行并发操作,那就是使用加锁机制。这是Java并发编程中必须要理解的一个知识点。其实使用起来还是比较简单,但是一定要理解。

  有几个概念一定要牢记:

  • 加锁必须要有锁
  • 执行完后必须要释放锁
  • 同一时间、同一个锁,只能有一个线程执行

synchronized : 特点是自动释放锁作用在方法时自动获取锁,任意对象都可做为锁,它是最常用的加锁机制,锁定几行代码。

        当一个线程获取到锁的时候,其它线程只能等待这个线程释放锁。(执行完代码释放,或者程序发生异常释放);如果被阻塞了,没有释放锁,其它线程就会一直等待,浪费资源。如下:

//--------同步方法1
public synchronized void test(){
    //一段代码
}
//--------同步方法2,锁某个对象
private Object lock=new Object();
public void test2(){
    synchronized(lock){
        
    }
}
  • 作用于普通方法获得当前对象锁,等价于synchronized(this)
  • 作用于静态方法获得类锁,等价于synchronized(类.class)

Lock : 的特点是,必须自己创建锁(锁类型已经指定为Lock的实现类,不能使用其它对象),必须自己释放锁。代码结构如下:

Lock lc = new Lock(); 
lc.lock();
try {
    // 执行代码
} finally {
    lc.unlock();
}
//注意一定要在finally中释放锁,保证即便抛出异常也可以释放。

线程安全基本知识:

  • 同一时间一个锁只能被一个线程持有
  • 调用对象的wait()和notify()前必须持有它
  • wait()和notify()必须和synchronized一起使用,wait方法会释放锁,所以不会有死锁问题
  • sleep方法虽然不释放锁,但是它不需要唤醒,在使用的时候已经指定想要的睡眠时间了。

volatile和synchronized的区别:

  • volatile关键字解决的是变量在多个线程之间的可见性(对于用volatile修饰的变量,JVM虚拟机只是保证从主内存加载到线程工作内存的值是最新的);而sychronized关键字解决的是多个线程之间访问共享资源的同步性。 
  • volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的;
  • volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
  • volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  • volatile修饰变量适合于一写多读的并发场景,而多写场景一定会产生线程安全问题(因此使用volatile而不是synchronized的唯一安全情况是类中只有一个可变的域)。
  • 因为所有的操作都需要同步给内存变量,所以volatile一定会使线程的执行速度变慢。

synchronized和lock区别:

1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

5)Lock可以提高多个线程进行读操作的效率(读写锁)。

  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。 

 

Condition在前面并发一章已经介绍过,有兴趣的童鞋可以去看一看。https://www.cnblogs.com/2019lgg/p/11319656.html

 

posted @ 2019-08-08 20:33  威兰达  阅读(585)  评论(0编辑  收藏  举报