不共享数据与共享数据
- 在多线程环境中,实例变量针对其他线程存在共享和不共享之分,他们的情况如下图:
- 首先来看一看不共享数据,它的特点就是存在多个数据源,每一个线程都使用针对的数据,线程之间的占用的资源不会互相影响,反观共享数据,只有一个数据源,多个线程之间均使用的是同一个数据。在接下来的代码示例中,我将会用两个例子向大家展示这两种数据形式的差别。
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等,虽然执行效率低点,但是能够保护数据,同志们在平时的学习过程中还是要多多注意,万一在面试的时候被面试官问到了答不出来岂不是很尴尬…