(四)多线程说学逗唱:线程险恶,变量和线程安全不得不防

不共享数据与共享数据
  • 在多线程环境中,实例变量针对其他线程存在共享和不共享之分,他们的情况如下图:
    不共享数据与不共享数据
  • 首先来看一看不共享数据,它的特点就是存在多个数据源,每一个线程都使用针对的数据,线程之间的占用的资源不会互相影响,反观共享数据,只有一个数据源,多个线程之间均使用的是同一个数据。在接下来的代码示例中,我将会用两个例子向大家展示这两种数据形式的差别。
package day01;
/**
 * 
* @ClassName: NoShareInstanceVariable
* @Description: 不共享实例变量学习
* @author Jian.Wang
* @date 2018年12月6日
*
 */
public class NoShareInstanceVariable {

	public static void main(String[] args) {
		try {
			NoShareVariable noShareVariable_a = new NoShareVariable("A");
			NoShareVariable noShareVariable_b = new NoShareVariable("B");
			NoShareVariable noShareVariable_c = new NoShareVariable("C");
			noShareVariable_a.start();
			noShareVariable_b.start();
			noShareVariable_c.start();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

/**
 * 
* @ClassName: NoShareVariable
* @Description: 不共享实例变量测试类
* @author Jian.Wang
* @date 2018年12月6日
*
* synchronized 标识为“互斥区”或“临界区”,区中的代码意思上为加上了锁,任何线程访问临界区的代码都需要先拿到锁.....
 */
class NoShareVariable extends Thread{
	private int count = 5;
	public NoShareVariable(String name) {
		super();
		this.setName(name);
	}
	@SuppressWarnings("static-access")
	@Override
	public void run() {
		while (count > 0) {
			count--;
			System.out.println("由:" + this.currentThread().getName() + "计算,count=" + count);
		}
	}
}

  • NoShareVariable 类继承了Thread类,重写了run方法,无疑他是一个线程类对象,在run()方法中对于count每一次循环减一,在输出当前的值。NoShareInstanceVariable 类中创建了三个NoShareVariable对象的实体,并且开启了三个线程。很明显这三个线程都有各自的count变量,自己减少自己的count值,这样的情况就是变量不共享,各自为政,自立山头,他的输出结果为:
    在这里插入图片描述
package day01;
/**
 * 
* @ClassName: NoShareInstanceVariable
* @Description: 不共享实例变量学习
* @author Jian.Wang
* @date 2018年12月6日
*
 */
public class NoShareInstanceVariable {

	public static void main(String[] args) {
		try {
			ShareVariable shareVariable = new ShareVariable();
			Thread shareVariable_a = new Thread(shareVariable,"A");
			Thread shareVariable_b = new Thread(shareVariable,"B");
			Thread shareVariable_c = new Thread(shareVariable,"C");
			Thread shareVariable_d = new Thread(shareVariable,"D");
			Thread shareVariable_e = new Thread(shareVariable,"E");
			Thread shareVariable_f = new Thread(shareVariable,"F");
			shareVariable_a.start();
			shareVariable_b.start();
			shareVariable_c.start();
			shareVariable_d.start();
			shareVariable_e.start();
			shareVariable_f.start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

/**
 * 
* @ClassName: ShareVariable
* @Description: 共享实例变量类
* @author Jian.Wang
* @date 2018年12月6日
*
 */
class ShareVariable extends Thread{
	private int count = 5;
	@SuppressWarnings("static-access")
	@Override
	 public void run() {
		super.run();
		count--;
		System.out.println("由:" + this.currentThread().getName() + "计算,count=" + count);
	}	
}

  • ShareVariable类中定义了一个私有变量count,在run方法中不断自减,在NoShareInstanceVariable 中创建了一个实体,并作为参数传递到Thread中,创建了若干个线程。此时count数据就被创建的所有线程共享,其最后编码的结果为:
    在这里插入图片描述
  • 上面的输出结果分别递减,也就是按照猜想所有线程共享一个count。但是细心的朋友会发现,F和E线程都访问了-1这个值,这又是为什么呢?原因就是CPU的速度是非常快的,这两个线程在几乎同一个时间访问了count这个值,在某一个线程还没有执行减操作是就也进行了访问,因此造成了同时输出两个一样的值,那么这种情况该怎么解决呢?这就要用到我们接下来就要讲到的东西,处理线程安全问题。
synchronized处理线程安全
  • 上面的情况要解决,只需要在run方法前加上synchronized关键字即可。
    在这里插入图片描述
  • 通过run方法前加synchronized关键字,使得多个线程在执行run方法的时候,以排队的方式进行处理。当一个线程调用run方法前,先判断run方法有没有被上锁,如果上锁,说明有其他的线程正在调用run方法,必须等待其他线程对run方法地哦啊用结束以后才可以执行run方法。这样也就实现了排队调用run方法的目的。synchronized可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。当一个线程想要执行同步方法中的代码时,线程必须首先尝试拿到这把锁,如果能够拿到这把锁就能够执行synchronized里面的代码。如果拿不到这把锁,那么线程就会不断地尝试拿这把锁,知道拿到为止,而且是同时有多个线程同时去争抢这把锁。下面就通过一个简单的例子来展示这一场景,写一个LoginServlet类,创建doPost方法模拟登陆业务。A和B两个登录员分别为两个不同的线程,调用doPost方法,看看在非线程安全和线程安全下展示出来不同的现象。
package day01;

import java.util.Objects;
/**
 * 
* @ClassName: NonThreadSafe
* @Description: 模拟非线程安全,并解决
* @author Jian.Wang
* @date 2018年12月6日
*
 */
public class NonThreadSafe {

	public static void main(String[] args) {
		ALogin aLogin = new ALogin();
		aLogin.start();
		BLogin bLogin = new BLogin();
		bLogin.start();
	}
}

/**
 * 
* @ClassName: LoginServlet
* @Description: 模拟登陆类,包含登陆方法
* @author Jian.Wang
* @date 2018年12月6日
*
 */
class LoginServlet {
	private static String userNameRef;
	private static String passwordRef;
	
	public static void doPost(String userName, String password) {
		try {
			userNameRef = userName;
			if (Objects.equals(userName, "a")) {				
				Thread.sleep(3000);
			}
			passwordRef = password;
			System.out.println("userName=" + userNameRef + ", password=" + passwordRef);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

/**
 * 
* @ClassName: ALogin
* @Description: A登陆员
* @author Jian.Wang
* @date 2018年12月6日
*
 */
class ALogin extends Thread {

	@Override
	public void run() {
		super.run();
		LoginServlet.doPost("a", "aa");
	}
}

/**
 * 
* @ClassName: BLogin
* @Description: B登陆员
* @author Jian.Wang
* @date 2018年12月6日
*
 */
class BLogin extends Thread {

	@Override
	public void run() {
		super.run();
		LoginServlet.doPost("b", "bb");
	}
	
}

  • doPost方法前没有加入synchronized 关键字时,出现了结果混乱的情况。
    在这里插入图片描述
  • 因此我们将synchronized 加到doPost方法上之后,结果就正确了。
    在这里插入图片描述
多说一句 synchronized 在很多常用类中基本都用到了
  • 数据安全是应用程序中的重中之重,因此在实际的开发过程中,往往对于重要数据的安全,特别是共享数据的安全维护是必然的,因此一些提供的常用类中很多方法都是线程安全的,比如StringBuffer等,虽然执行效率低点,但是能够保护数据,同志们在平时的学习过程中还是要多多注意,万一在面试的时候被面试官问到了答不出来岂不是很尴尬…

猜你喜欢

转载自blog.csdn.net/WJHelloWorld/article/details/84874999