实例变量的不共享


自定义线程类中的实例对于其他线程来说可以有共享与不共享之分,这是多线程的核心知识点之一。

不共享情景如下所示

实例变量与线程安全 - 图1

创建代码 test5.java

下面通过一个示例看下数据的不共享情况,创建 test5.java 类,并输入如下代码:

  1. class MyThread extends Thread{
  2. private int count = 5;
  3. public MyThread(String name){
  4. super();
  5. this.setName(name);
  6. }
  7. @Override
  8. public void run(){
  9. while(count > 0){
  10. count --;
  11. System.out.println(Thread.currentThread().getName() + ":.." + count);
  12. }
  13. }
  14. }
  15. public class test5{
  16. public static void main(String[] args){
  17. Thread myThread_A = new MyThread("A");
  18. Thread myThread_B = new MyThread("B");
  19. Thread myThread_C = new MyThread("C");
  20. Thread myThread_D = new MyThread("D");
  21. Thread myThread_E = new MyThread("E");
  22. myThread_A.start();
  23. myThread_B.start();
  24. myThread_C.start();
  25. myThread_D.start();
  26. myThread_E.start();
  27. }
  28. }

class MyThread extends Thread{ private int count = 5; public MyThread(String name){ super(); this.setName(name); } @Override public void run(){ while(count > 0){ count —; System.out.println(Thread.currentThread().getName() + “:..” + count); } } } public class test5{ public static void main(String[] args){ Thread myThread_A = new MyThread(“A”); Thread myThread_B = new MyThread(“B”); Thread myThread_C = new MyThread(“C”); Thread myThread_D = new MyThread(“D”); Thread myThread_E = new MyThread(“E”); myThread_A.start(); myThread_B.start(); myThread_C.start(); myThread_D.start(); myThread_E.start(); } }

test5 运行结果

运行 test5 实例数据不共享情况的测试之后,输出结果如下所示:
实例变量与线程安全 - 图2
可以看到,一共创建了 5 个线程,每个线程都有各自的 count 变量,自己减少自己的 count 值,这样的情况便是实例变量不共享,此示例不存在多个线程访问同一个变量的情况。

实例变量的共享

共享情景如下所示

实例变量与线程安全 - 图3

创建代码 test6.java

下面通过一个示例看下数据的共享情况,创建 test6.java 类,并输入如下代码:

  1. class MyThread extends Thread{
  2. private int count = 5;
  3. @Override
  4. public void run(){
  5. count --;
  6. System.out.println(Thread.currentThread().getName() + ":.." + count);
  7. }
  8. }
  9. public class test6{
  10. public static void main(String[] args){
  11. MyThread myThread = new MyThread();
  12. Thread thread_A = new Thread(myThread,"A");
  13. Thread thread_B = new Thread(myThread,"B");
  14. Thread thread_C = new Thread(myThread,"C");
  15. Thread thread_D = new Thread(myThread,"D");
  16. Thread thread_E = new Thread(myThread,"E");
  17. thread_A.start();
  18. thread_B.start();
  19. thread_C.start();
  20. thread_D.start();
  21. thread_E.start();
  22. }
  23. }

class MyThread extends Thread{ private int count = 5; @Override public void run(){ count —; System.out.println(Thread.currentThread().getName() + “:..” + count); } } public class test6{ public static void main(String[] args){ MyThread myThread = new MyThread(); Thread thread_A = new Thread(myThread,”A”); Thread thread_B = new Thread(myThread,”B”); Thread thread_C = new Thread(myThread,”C”); Thread thread_D = new Thread(myThread,”D”); Thread thread_E = new Thread(myThread,”E”); thread_A.start(); thread_B.start(); thread_C.start(); thread_D.start(); thread_E.start(); } }

test6 运行结果

第一次运行 test6 测试结果如下所示

实例变量与线程安全 - 图4
如图所示我们所创建的 5 个线程,分别对 count 数值进行了 — 操作,此时这 5 个线程即为共享实例变量。

第二次运行 test6 测试结果如下所示

实例变量与线程安全 - 图5
因为多线程之间是不断争夺 CPU 资源的,所以会导致上面这种不安全的情况产生。每一次多线程的测试结果都不会相同,但是此时由于我们新建的线程数量太少,效果还不太明显,具体可看下面 test7 的例子。

感受实例变量共享的安全问题


从 test6 的测试结果我们可以初步看出,多线程在并发执行的时候,可能会重复删除数据、或未删除数据,等等情况产生,本小节将对多线程实例变量共享的安全问题进行放大,让读者更清晰的感受。

创建代码 test7.java

test7 中将 count 改为 50,并且用循环的方式连续创建了 30 个线程。

  1. class MyThread extends Thread{
  2. private int count = 50;
  3. @Override
  4. public void run(){
  5. count --;
  6. System.out.println(Thread.currentThread().getName() + " :.." + count);
  7. }
  8. }
  9. public class test7{
  10. public static void main(String[] args){
  11. MyThread myThread = new MyThread();
  12. for(int i = 0; i < 30; i++){
  13. Thread thread = new Thread(myThread, "i+" + i);
  14. thread.start();
  15. }
  16. }
  17. }

class MyThread extends Thread{ private int count = 50; @Override public void run(){ count —; System.out.println(Thread.currentThread().getName() + “ :..” + count); } } public class test7{ public static void main(String[] args){ MyThread myThread = new MyThread(); for(int i = 0; i < 30; i++){ Thread thread = new Thread(myThread, “i+” + i); thread.start(); } } }

test7 运行结果

第一次运行 test7 测试结果如下所示

实例变量与线程安全 - 图6
如上所示 32 连续被减少两次,最后结果却是正确的。

第二次运行 test7 测试结果如下所示

实例变量与线程安全 - 图7
test7 第二次运行的时候最后结果是 21.
36 35 34 33 34 32 31 这几个数值出现的时候,线程产生了混乱,已经没办法正确 -1 了。

下一步

初次解决实例变量共享的安全问题

我们将利用 synchronized 关键字来初次解决实例变量共享的安全问题。

初步认识 synchronized 关键字

在 test8 的测试之中,通过在 run 方法前加入 synchronized 关键字,使多个线程在执行 run 方法时,以排队的方式进行处理。
当一个线程想要执行同步方法里面的代码之前,会先判断 run 方法有没有被上锁,如果被上锁,说明有其他线程正在调用 run 方法,必须等其它线程对 run 方法调用结束后才可以执行 run 方法。这样也就实现了排队调用 run 方法的目的,也就达到了解决实例变量共享的安全问题的目的。
synchronized 可以在任意对象及方法上加锁,而加锁的这段代码被称之为“互斥区”或“临界区”。

创建代码 test8.java

  1. class MyThread extends Thread{
  2. private int count = 50;
  3. @Override
  4. synchronized public void run(){
  5. count --;
  6. System.out.println(Thread.currentThread().getName() + " :.." + count);
  7. }
  8. }
  9. public class test8{
  10. public static void main(String[] args){
  11. MyThread myThread = new MyThread();
  12. for(int i = 0; i < 30; i++){
  13. Thread thread = new Thread(myThread, "i+" + i);
  14. thread.start();
  15. }
  16. }
  17. }

class MyThread extends Thread{ private int count = 50; @Override synchronized public void run(){ count —; System.out.println(Thread.currentThread().getName() + “ :..” + count); } } public class test8{ public static void main(String[] args){ MyThread myThread = new MyThread(); for(int i = 0; i < 30; i++){ Thread thread = new Thread(myThread, “i+” + i); thread.start(); } } }

test7 运行结果

实例变量与线程安全 - 图8