并发编程-线程安全

原因:Java内存模型

       共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本

多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。

 

解决 方法:同步(让其他线程知道变量被修改)

 

多线程有三大特性

原子性:多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

可见性:一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

有序性:程序执行的顺序按照代码的先后顺序执行。

 

synchronized

    private Object lock = new Object();

    /**
     * 修饰需要进行同步的方法,此时锁的对象为this
     */
    public synchronized void getId(){
        System.out.println("getId只允许一次被一个线程访问");
    }

    /**
     * 同步代码块,锁的粒度可以更细
     * 并且充当锁的对象不一定是this,也可以是其它对象,所以使用起来更加灵活
     */
    public void getName(){
        synchronized (lock){
            System.out.println("getName只允许一次被一个线程访问");
        }
    }

锁静态方法,是锁的.class 多个实例化公用一把锁。

 

Lock锁

需要在使用时手动获取锁和释放锁

Lock lock  = new ReentrantLock();
lock.lock();
try{
//可能会出现线程安全的操作
}finally{
//一定在finally中释放锁
//也不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常
  lock.ublock();
}

 

死锁

两个线程(多个),相互等待获取对方的锁。导致锁无法释放

 

Threadlocal

每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本

void set(Object value)设置当前线程的线程局部变量的值。

• public Object get()该方法返回当前线程所对应的线程局部变量。

• public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。当线程结束后,对应该线程的局部变量将自动被垃圾回收

• protected Object initialValue()返回该线程局部变量的初始值

(每一个线程里面,Threadlocal开始都为空,即便在开启这个线程之前已经赋值)

class Res {
	// 生成序列号共享变量
	public static Integer count = 0;
	public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
		protected Integer initialValue() {

			return 0;
		};

	};

	public Integer getNum() {
		int count = threadLocal.get() + 1;
		threadLocal.set(count);
		return count;
	}
}

重排序

为了提高运行效率,编译器和处理器可能会对操作做重排序。

(就是有可能会把第一行代码,放在第三行执行,前提是没有数据依赖)

(单线程没不需要考虑这个问题)

(多线程下给公共变量加上Volatile)

数据依赖分三种:

 

锁的分类

重入锁、读写锁

重入锁:(递归锁)同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。(ReentrantLock、synchronized)

读写锁:两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源。但是如果有一个线程想去写这些共享资源,就不应该再有其它线程对该资源进行读或写(ReentrantReadWriteLock

	static Map<String, Object> map = new HashMap<String, Object>();
	static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
	static Lock r = rwl.readLock();
	static Lock w = rwl.writeLock();

	// 获取一个key对应的value
	public static final Object get(String key) {
		r.lock();
		try {
			System.out.println("正在做读的操作,key:" + key + " 开始");
			Thread.sleep(100);
			Object object = map.get(key);
			System.out.println("正在做读的操作,key:" + key + " 结束");
			System.out.println();
			return object;
		} catch (InterruptedException e) {

		} finally {
			r.unlock();
		}
		return key;
	}

	// 设置key对应的value,并返回旧有的value
	public static final Object put(String key, Object value) {
		w.lock();
		try {

			System.out.println("正在做写的操作,key:" + key + ",value:" + value + "开始.");
			Thread.sleep(100);
			Object object = map.put(key, value);
			System.out.println("正在做写的操作,key:" + key + ",value:" + value + "结束.");
			System.out.println();
			return object;
		} catch (InterruptedException e) {

		} finally {
			w.unlock();
		}
悲观锁、乐观锁

乐观锁:不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改。有修改重试,直到修改成功。

悲观锁:加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。

CAS无锁模式

比较再交换,是一种乐观锁。

它包含三个参数CAS(V,E,N): V表示要更新的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。

问题:在一段期间A曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。(通过AtomicStampedReference控制变量版本保证CAS的正确性)

 

 

补充:

StringBuffer是线程安全的,每次操作字符串,String会生成一个新的对象,而StringBuffer不会;StringBuilder是非线程安全的

fail-fast:机制是java集合(Collection)中的一种错误机制。(在遍历的时候修改数据会报ConcurrentModificationException)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值