zoukankan      html  css  js  c++  java
  • Java多线程之非线程安全

       在Java多线程中我会重点总结五个如下的技术点:

       1、非线程安全是如何出现的

       2、synchronized对象监视器为Objec时的使用

       3、synchronized对象监视器为Class时的使用

       4、关键字volatile的主要作用

       今天我先说一说第一个问题,非线程安全是如何出现的。“非线程安全”会在多个线程对同一个对象中的实例变量进行并发访问的时候发生,产生的后果就是“脏读”,也就是读取到的数其实是已经被更改过的了。下面我分别实现“线程安全”和“非线程安全”的两个例子,让大家来体会一下对实例变量更改是怎么发生的。但是多线程的结果会有很多输出结果,希望大家还是亲自敲代码才会理解的更深一些,下面先说一下“线程安全”的例子。如下:

    	public static void main(String[] args) {
    
    		PrivateNum num = new PrivateNum();
    		MyThreadA a = new MyThreadA(num);
    		MyThreadB b = new MyThreadB(num);
    		a.start();
    		b.start();
    
    	}
    
    	public static class PrivateNum {
    
    		public void addnum(String name) {
                            //重点是num变量是addnum这个方法的局部变量
    			int num = 0;
    			try {
    				if ("a".equals(name)) {
    					num = 100;
    					System.out.println("a ser over!");
    					Thread.sleep(2000);
    				} else {
    					num = 200;
    					System.out.println("b set over!");
    				}
    				System.out.println("线程" + name + "的num=" + num);
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    
    		}
    
    	}
    
    	public static class MyThreadA extends Thread {
    
    		private PrivateNum privateNum;
    
    		public MyThreadA(PrivateNum privateNum) {
    			super();
    			this.privateNum = privateNum;
    		}
    
    		@Override
    		public void run() {
    
    			super.run();
    			privateNum.addnum("a");
    
    		}
    	}
    
    	public static class MyThreadB extends Thread {
    
    		private PrivateNum privateNum;
    
    		public MyThreadB(PrivateNum privateNum) {
    			super();
    			this.privateNum = privateNum;
    		}
    
    		@Override
    		public void run() {
    
    			super.run();
    			privateNum.addnum("b");
    
    		}
    	}
    
         运行结果如下:无论输出顺序是怎样的,但是这段代码执行的结果一定是线程安全的,原因就在于num的这个变量是addnum方法私有的局部变量,PrivateNum的对象访问不到num这个变量,所以就没有多num这个变量改变的这个说,自然就不会出现“非线程安全”的状况。 

          

      下面在说一个“非线程安全”的例子,如       public static void main(String[] args) {		GetNum num = new GetNum();
    		ThreadA a = new ThreadA(num);
    		a.start();
    		ThreadB b = new ThreadB(num);
    		b.start();
    
    	}
    
    	public static class GetNum {
                    //之所以会出现“非线程安全”的现象,是因为num这个变量是GetNUm类的全局变量
    		private int num = 0;
    
    		public void getNum(String name) {
    			try {
    
    				if ("a".equals(name)) {
    					num = 100;
    					System.out.println("a set over");

    执行到这里的时候,线程a休眠了,此时num=100,接着线程b执行了,执行的结果把num重新赋值为num=200了,线程b执行完了之后,休眠的2000毫秒也过了,
    线程a会接着执行,而不是重新执行,所以这个时候的num仍然是200,发生了“非线程安全问题”
    Thread.sleep(2000); } else { num = 200; System.out.println("b set over"); } System.out.println("线程" + name + "的num=" + num); } catch (Exception e) { e.printStackTrace(); } } } public static class ThreadA extends Thread { private GetNum num; public ThreadA(GetNum num) { super(); this.num = num; } @Override public void run() { super.run(); num.getNum("a"); } } public static class ThreadB extends Thread { private GetNum num; public ThreadB(GetNum num) { super(); this.num = num; } @Override public void run() { super.run(); num.getNum("b"); } }

         运行结果如下:线程a的num=200,出现了“非线程安全”的现象了,出现的原因就是线程a和线程b都对PrivateNum类下的num对象作出改变了,具体的改变过程在程序中已经分析过了。而解决“非线程安全”的问题就是同步,下面的几节会说。

       

            在上述例子中,是在给num赋值的时候出现没有保持线程同步,出现“脏读”,但是在取值的时候也可能出现“脏读”,发生“脏读”是因为在读取实例变量时,这个值已经被修改过了。下面看一个取值发生“脏读”的例子,如下:

           public static void main(String[] args) {
    
    		try {
    			PublicVar publicVar = new PublicVar();
    			ThreadA threadA = new ThreadA(publicVar);
    			threadA.start();
    			// 睡眠时间影响输出结果
    			Thread.sleep(200);
    			publicVar.getValue();
    
    		} catch (InterruptedException e) {
    
    			e.printStackTrace();
    		}
    	}
    
    	public static class PublicVar {
    
    		public String name = "A";
    		public String password = "a";
    
    		synchronized public void setValues(String name, String password) {
    
    			try {
    				this.name = name;
    				Thread.sleep(5000);
    				this.password = password;
    				System.out.println("setValue method thread name="
    						+ Thread.currentThread().getName());
    				System.out.println("setValue name=" + name);
    				System.out.println("setValue password=" + password);
    			} catch (InterruptedException e) {
    
    				e.printStackTrace();
    			}
    		}
    
    		public void getValue() {
    
    			System.out.println("getValue method thread name="
    					+ Thread.currentThread().getName());
    			System.out.println("getValue  name=" + name);
    			System.out.println("getValue  password=" + password);
    		}
    	}
    
    	public static class ThreadA extends Thread {
    
    		private PublicVar publicVar;
    
    		public ThreadA(PublicVar publicVar) {
    			super();
    			this.publicVar = publicVar;
    		}
    
    		@Override
    		public void run() {
    			super.run();
    			publicVar.setValues("B", "b");
    		}
    	}
    

      运行结果如下:这里边出现“脏读”是因为setValues()方法没有运行完,但是name已经赋值为B,password还没来得赋值就休眠了5000毫秒,所以是main线程先输出的name="B",password="a"。等到5000毫秒到了,子线程又在原来被打断的地方接着运行了,也就是开始给password赋值为b,所以第二次是Thread-0线程输出name="B",password="b"。这里我说过主线程的休眠时间会影响输出结果,如果主线程的休眠时间比子线程的时间长,能够保证子线程执行完,就不会发生“脏读”的情况了。解决脏读就要通过给getValue()方法添加synchronized关键字。

     

         

           

          

       

  • 相关阅读:
    oracle数据库版本进化的关键节点
    到底什么是数据库呢?
    迁移数据之后,读取数据库变得很慢
    为什么越来越多的人使用python呢?
    20135306 2.4 ELF文件格式分析
    20135306 2.3程序破解实践
    20135306黄韧模块实践报告
    Linux内核学习总结
    Linux内核分析期中知识点总结
    LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程
  • 原文地址:https://www.cnblogs.com/chentong/p/5650137.html
Copyright © 2011-2022 走看看