Java线程同步和锁定

同步和锁定

Java中每个对象都有一个内置锁。

1、synchronize

当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。

一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。

释放锁是指持锁线程退出了synchronized同步方法或代码块。

关于锁和同步的几个要点

  1. 只能同步方法,而不能同步变量和类;
  2. 每个对象只有一个锁;当提到同步时,应该清楚在哪个对象上同步。
  3. synchronized 锁的是对象,在应用数据共享时,要保证被锁的对象不要发生变化。
  4. 如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
  5. 如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
  6. 线程睡眠时,它所持的任何锁都不会释放。
  7. 线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。

在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。synchronized (this) 和 用synchronized修饰方法 有相同的效果,例如:

public int add(int m) {  
    synchronized (this) {  
        n = m + n;  
    }
    return n;  
} 

等同于

public synchronized int add(int m) {  
    n = m + n;
    return n;  
} 

静态方法同步

同步静态方法,需要一个类对象的锁,即Class对象

public static int add(int m) {  
    synchronized (Test.class) {  
        n = m + n;  
    }
    return n;  
} 

等同于

public static synchronized int add(int m) {  
    n = m + n;
    return n;  
} 

什么情况下,线程无法获得锁?

当线程A试图调用同步方法或同步代码块,要清楚这个同步是在哪个对象上的锁,如果此时锁被线程B占用,那么线程A在该对象上被阻塞,等待,直到锁被释放,线程A再次变为可运行或运行状态。

  • 对于非静态的同步方法,当然是,调用同一个对象的同步方法会被阻塞,调用不同对象的同步方法不会被阻塞
  • 对于静态的同步方法,都是在Class对象上加锁,调用它的同步方法当然会彼此阻塞

下面的例子可以帮助理解。两个不同的线程thread1和thread2,想同时操作Worker里面的index

public static void main(String[] args) throws InterruptedException {
   Worker worker = new Worker(5); 
   // 下面两个线程用的是同一个对象worker
   Thread thread1 = new Thread(worker);
   Thread thread2 = new Thread(worker);
   thread1.start();
   thread2.start();
}

private static class Worker implements Runnable {
    private Integer index;
    public Worker(Integer index) {
        this.index = index;
    }
    @Override
    public void run() {
   	    // 这里的this,表示当前对象,不同的实例,this指代的对象是不同的
        synchronized (this) {
            index++;
            System.out.println(Thread.currentThread().getName() + "-------" + index);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-------" + index + ", END");
        }
    }
}

synchronized (this),即对Worker对象加锁,打印结果如下:

Thread-0-------6
Thread-0-------6, END
Thread-1-------7
Thread-1-------7, END

上面new Thread用的是同一个worker对象,那么针对两个线程,一个获得对象锁后,另一个必然阻塞。

2、volatile关键字

  • 保证了不同线程对这个变量进行操作时的可见性。
    即一个线程修改了某个变量的值, 这新值对其他线程来说是立即可见的。
  • 不保证原子性
    即不能保证数据在多个线程下同时写时的线程安全

volatile最适用的场景: 一个线程写, 多个线程读
一个线程在修改某个变量的值,其他线程来读取该变量的值都是实时可见的。

3、ThreadLocal

1. 介绍

规避线程不安全的方法,除了加锁之外,还可以使用ThreadLocal 。
如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题。可理解为:线程局部变量。

2. ThreadLocal使用

在这里插入图片描述

  public class UseThreadLocal {
	    // int型ThreadLocal变量
		private static ThreadLocal<Integer> intLocal = new ThreadLocal<Integer>(){
	        @Override
	        protected Integer initialValue() {
	            return 100;
	        }
	    };

	    // String型ThreadLocal变量
	    private static ThreadLocal<String> stringThreadLocal;

		// 开启线程
	    public void StartThread(){
			Thread thread1 = new Thread(new TestRunnable(1));
			thread1.start();
	        Thread thread2 = new Thread(new TestRunnable(2));
			thread2.start();
	    }
	    
	    public static class TestRunnable implements Runnable{
	        public int id;
	        public TestRunnable(int id){
	            this.id = id;
	        }
	        public void run() {
	            System.out.println(Thread.currentThread().getName()+", intLocal.get:" + intLocal.get());
	            Integer i = intLocal.get();
	            i = i + id;
	            intLocal.set(i);
	            System.out.println(Thread.currentThread().getName() +", after set:"+ intLocal.get());
	            // ThreadLocal变量不再使用时,须remove
	            intLocal.remove();
	        }
	    }

	    public static void main(String[] args){
	    	UseThreadLocal test = new UseThreadLocal();
	        test.StartThread();
	    }
  }

输出结果:

Thread-0, intLocal.get:100
Thread-1, intLocal.get:100
Thread-0, after set:101
Thread-1, after set:102

3. ThreadLocal的实现原理

在这里插入图片描述

(1) Thread类

主要看它的成员变量 threadLocals

  • 类型:ThreadLocal.ThreadLocalMap(类似于HashMap),key是ThreadLocal,value是set的值
  • 初始化:此变量初始为null,只有调用ThreadLocal.set或get方法时,才会创建它
  • 赋值:ThreadLocal.set()、get()、remove()
  • 作用:存放该线程的ThreadLocal类型的本地变量
  • 注意:不使用本地变量时,需要调用remove方法将此线程在threadLocals中的本地变量删除

(2)ThreadLocal类
这里看下set()、get()、remove()方法源码

set()

 public void set(T value) {
    // (1) 获取当前线程(调用者线程)
    Thread t = Thread.currentThread();
    // (2) 获取Thread的成员变量threadLocals
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //  (3) 直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为value
         map.set(this, value);
     } else {
         // (4) 当map为null,说明首次添加,需要首先创建出对应的map
         createMap(t, value);
      }
 }

 ThreadLocalMap getMap(Thread t) {
     // 获取Thread的成员变量threadLocals
     return t.threadLocals; 
 }

 void createMap(Thread t, T firstValue) {
      // 创建threadLocals
      t.threadLocals = new ThreadLocalMap(this, firstValue);
 }

get()

 public T get() {
     Thread t = Thread.currentThread();
     ThreadLocalMap map = getMap(t);
     if (map != null) {
         ThreadLocalMap.Entry e = map.getEntry(this);
         T result = (T)e.value;
         return result;
     }
     return setInitialValue();
 }
 
 private T setInitialValue() {
     T value = initialValue(); // new ThreadLocal时会重写initialValue()进行赋值
     Thread t = Thread.currentThread();
     ThreadLocalMap map = getMap(t);
     if (map != null)
         map.set(this, value);
     else
         createMap(t, value);
     return value;
 }

remove()

 public void remove() {
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null)
         m.remove(this);
 }

ThreadLocalMap
ThreadLocalMap有一个内部类Entry,有一个Entry[]类型的变量,这个数组可以保存多个Entry对象。
值得注意的是,Entry持有ThreadLocal对象的弱引用。

弱引用:只要GC时,弱引用就会被回收。

因此,当GC时,Entry指向的ThreadLocal对象会被回收,那么Entry的key不在了,其value永远不会被访问,内存将暴增。因此,对于不再使用的线程本地变量,应及时remove。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值