从零开始java多线程到分布式锁(三):锁解决多线程的问题

本文介绍了多线程在JVM中的运行模型,探讨了多线程数据安全的共享性、互斥性、原子性和可见性四大特性,并详细讲解了如何通过悲观锁和乐观锁来解决多线程数据问题。
摘要由CSDN通过智能技术生成

一:回顾多线程在JVM中运行模型

   这里直接将(二)中的图复制过来吧。

                  

   在回顾一下多线程运行快的原因吧。每一个线程都会复制一个工作副本以供本线程操作使用,在线程运行结束后再将工作副本同步到主内存中。

  优点:每一个线程独立运行,效率贼快。

  缺点:对于公共变量的处理,会产生"脏读"的可能。

 

二:多线程数据安全的几大特性

   也许我们在接触这个问题之前应该会接触到数据库的事务的问题,数据库的事务具有以下的特性:一致性(分布式事务),隔离性(分布式锁),原子性(与一致性保持一致事务),持久性(数据库容灾处理)。同样多线程也拥有类似的几大特性(其实很好理解这2者的共同性:一般数据库是磁盘运存,二多线程则是内存的运存)。

1.共享性

  对于一个多线程而言,数据共享是导致多线程的锁(数据安全)产生的最根本原因。可以这么说数据的共享是导致多线程多发问题的根本原因。下面我们看一下多线程下数据共享产生的问题:

package project;

public class test {

	/**
	 * 共享数据
	 */
	public  static Integer  count = 0;
	
	
	public static void main(String[] args) {
		
		//启用10个线程对共享数据 count 都进行100次自增
		for(int i =0 ;i < 10 ;  i++ ) {
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					
					
					try {
                        //进入的时候暂停1毫秒,增加并发问题出现的几率
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
					
					for(int j =0 ; j< 100 ;j++) {
						addOne();
					}
					
				}
			}).start();
		}

		 try {
	            //主程序暂停3秒,以保证上面的程序执行完成
	            Thread.sleep(3000);
	        } catch (InterruptedException e) {
	            e.printStackTrace();
	        }
		
		System.out.println("按照理想状态,这里的count应该是1000,到那时我们来看一下实际结果:"+count);
	}
	
	
	
	public  static void  addOne() {
		 count++;
	}
	
	
	
	
}

     运行结果:有0,349,1000等等,明显不符合我们的期望。为什么会出现这种情况呢? 因为,A线程在修改的过程中,B线程也在修改最后导致结构紊乱。这就是数据共享在多线程中发生的问题。

2.互斥性

  数据互斥是指在同时只允许对数据操作。如果一个数据为全局不可变的数据(比如常量),那么就无需考虑互斥了,因为任何操作不会导致改数据改变。其次如果操作对于某一个数据只有读操作,没有其他任何修改的操作,也可以不考虑互斥性(严格意义上说,这种情况也属于第一种情况),Java对于互斥性体现为解决方案(使用锁关键字)

package project;

public class test {

	/**
	 * 共享数据
	 */
	public  static Integer  count = 0;
	
	
	public static void main(String[] args) {
		
		//启用10个线程对共享数据 count 都进行100次自增
		for(int i =0 ;i < 10 ;  i++ ) {
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					
					
					try {
                        //进入的时候暂停1毫秒,增加并发问题出现的几率
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
					
					for(int j =0 ; j< 100 ;j++) {
						addOne();
					}
					
				}
			}).start();
		}

		 try {
	            //主程序暂停3秒,以保证上面的程序执行完成
	            Thread.sleep(3000);
	        } catch (InterruptedException e) {
	            e.printStackTrace();
	        }
		
		System.out.println("按照理想状态,这里的count应该是1000,到那时我们来看一下实际结果:"+count);
	}
	
	
	
	public  synchronized static void  addOne() {
		 count++;
	}
	
	
	
	
}

3.原子性

  原子性就是对数据的操作是一个不可中断的过程,在一个线程操作中,对数据的操作有多个步骤,其中每一个步骤都要会写到内存中,这样多个线程之间机会产生问题:

4.可见性 

  可见性实则是针对jvm虚拟机而言的,在文章的一开头,我们就谈到多线程在JVM中运行模型,不多说。

 

四:多线程数据问题的解决纲领

  对于上述的多线程几大问题产生的解决方案一般分为2中:乐观锁和悲观锁

1.悲观锁

  默认所有的操作都会互相影响,屏蔽一切违反数据完整性的操作。这时候就要用到java中的锁的关键字。

2.乐观锁

  默认所有的线程操作多不会影响,只在数据提交时检测数据是否违反完整性。这种解决方案体现在CAS思想,即每一个线程修改时先去查询数据与之前去到的数据是否一致。但是这种做法有一个重大弊端:比如另一线程先修改了某个值,然后再改回原来值,这种情况下,CAS是无法判断的。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值