多线程读书笔记一(内部锁,过期数据,非原子的64位操作,线程封闭)

关于内部锁
synchronized关键字在多线程中,是一种“锁”的机制,它是一种 内部锁
内部锁是 可重进入的,所谓可重进入,就是指请求是 基于每线程,而不是每调用
synchronized锁是加在对象上的,但是 如果同一线程,多次请求这个锁,是可以的,如:
public class ReentrancySynTest  {
  public synchronized void a() {
     System.out.println("this is a");
  }
  public synchronized void b() {
    a();
  }
  public static void main(String[] args) {
    ReentrancySynTest reentrancySynTest = new ReentrancySynTest();
    reentrancySynTest.b();  //同一线程,可以重进入这个锁 , 所以在b()中可以再进入a()执行
  }
}
重进入实现机制:通过为每个锁关联一个请求计数以及占有它的线程。当计数为0时,认为锁是未被占有的。线程请求一个未被占用的锁时,JVM将记录锁的占有者,并且将请求计数置为1.如果同一线程再次请求这个锁,计数将递增;每次占用线程退出同步块时,计数值减1.当计数值为0时,锁被释放。
过期数据:
当多个线程同时对某个变量进行读、写时,如果没有设置线程同步,很可能某线程读到的不是此变量的最新值,而是过期数据:如果一个线程调用了set,而另一个线程此时正在调用get,那么最后get得到的数据有可能是过期数据。而不是set设置的最新值。
解决方法:让一个线程调用set(get)时,其他线程不能同时调用get(set)。这可以使用synchronized关键字实现。
以下程序就不会读取过期数据,输出结果为: Thread 2 : 1
class SyncInteger {
	private int value = 0;

	public synchronized int get() {
		return this.value;
	}

	public synchronized void set(int value) {
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.value = value;
	}
}

public class ExpireDataTest {
	public static void main(String[] args) {
		final SyncInteger syncInteger = new SyncInteger();
		Thread thread1 = new Thread() {
			public void run() {
				// 当Thread1执行对象的set方法后,它便获取了对象的锁,在它执行的过程中,Thread2不能执行同一对象的get方法
				syncInteger.set(1);
			}
		};

		Thread thread2 = new Thread() {
			public void run() {
				try {
					Thread.sleep(100); // 目的让Thread1先执行set方法
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				// 由于之前的sleep使得Thread1先获得对象的锁并执行其set方法,那Thread2只有在set方法执行退出后,才能执行get
				System.out.println("Thread 2 : " + syncInteger.get());
			}
		};
		thread1.start();
		thread2.start();
	}
}
如果将get,set方法前面的synchronized去掉,则结果输出:Thread 2 : 0,get返回的事更新前的值,即过期数据。

非原子的64位操作
Java存储模型要求获取和存储操作都是原子的,但对于double和long类型的变量, JVM允许见64位的读或写操作划分为两个32位操作。如果读和写发生在不同的线程,可能会出现得到一个值的高32位和另一个值的低32位。 
如:开始读之前: a b
 Thread1更改为 :a1  b1      Thread2更改为:a2  b2
Thread1与Thread2同时写入, Thread1写入高位a1在Thread2写入高位a2之前(a2覆盖a1),Thread2写入低位在Thread1写入低位之前(b1覆盖b2),最终结果得到: a2 b1

解决方法:可以讲它们声明为volatile类型

线程封闭
访问共享数据需要进行同步,那么如果 让数据不能共享, 就不需要进行同步了,这是实现线程安全的最简单 一种方式。
线程封闭ThreadLocal:

ThreadLocal可以 将一个“域”与一个“线程”关联起来,通过持有类型的方式,被ThreadLocal持有的类型就是域的实际类型
ThreadLocal提供了get与set访问器,它实际上 为每个使用它的线程维护了一份单独的拷贝,所以不同线程的数据不共享,某个线程的get方法得到的总是set方法设置 值。
使用ThreadLocal进行线程封闭的代码,线程安全:
//使用ThreadLocal进行线程封闭的
private static ThreadLocal<Connection> connectionHolder
           = new ThreadLocal<Connection>() {
                    public Connection initialValue() {
                           return DriverManager.getConnection(DB_URL);
                   }
         };

public static Connection getConnection() {
    return connectionHolder.get();
}
同样的功能,不使用ThreadLocal进行线程封闭的代码,非线程安全:
//不使用ThreadLocal进行线程封闭的
private static Connection connection = DriverManager.getConnection(DB_URL);
public static Connection getConnection() {
  return connection;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值