引自:http://blog.csdn.net/aaa1117a8w5s6d/article/details/8295527http://m.blog.csdn.net/blog/undoner/12849661

静态变量:线程非安全。

静态变量即类变量,位于方法区,为所有对象共享,共享一份内存,一旦静态变量被修改,其他对象均对修改可见,故线程非安全。

实例变量:单例模式(只有一个对象实例存在)线程非安全,非单例线程安全。

实例变量为对象实例私有,在虚拟机的堆中分配,若在系统中只存在一个此对象的实例,在多线程环境下,“犹如”静态变量那样,被某个线程修改后,其他线程对修改均可见,故线程非安全;如果每个线程执行都是在不同的对象中,那对象与对象之间的实例变量的修改将互不影响,故线程安全。

局部变量:线程安全。

每个线程执行时将会把局部变量放在各自栈帧的工作内存中,线程间不共享,故不存在线程安全问题。

静态变量线程安全问题模拟:

----------------------------------------------------------------------------------

  1. /**
  2. * 线程安全问题模拟执行
  3. *  ------------------------------
  4. *       线程1      |    线程2
  5. *  ------------------------------
  6. *   static_i = 4;  | 等待
  7. *   static_i = 10; | 等待
  8. *    等待          | static_i = 4;
  9. *   static_i * 2;  | 等待
  10. *  -----------------------------
  11. * */
  12. public class Test implements Runnable
  13. {
  14. private static int static_i;//静态变量
  15. public void run()
  16. {
  17. static_i = 4;
  18. System.out.println("[" + Thread.currentThread().getName()
  19. + "]获取static_i 的值:" + static_i);
  20. static_i = 10;
  21. System.out.println("[" + Thread.currentThread().getName()
  22. + "]获取static_i*3的值:" + static_i * 2);
  23. }
  24. public static void main(String[] args)
  25. {
  26. Test t = new Test();
  27. //启动尽量多的线程才能很容易的模拟问题
  28. for (int i = 0; i < 3000; i++)
  29. {
  30. //t可以换成new Test(),保证每个线程都在不同的对象中执行,结果一样
  31. new Thread(t, "线程" + i).start();
  32. }
  33. }
  34. }

根据代码注释中模拟的情况,当线程1执行了static_i = 4;  static_i = 10; 后,线程2获得执行权,static_i = 4; 然后当线程1获得执行权执行static_i * 2;  必然输出结果4*2=8,按照这个模拟,我们可能会在控制台看到输出为8的结果。

写道
[线程27]获取static_i 的值:4 
[线程22]获取static_i*2的值:20 
[线程28]获取static_i 的值:4 
[线程23]获取static_i*2的值:8 
[线程29]获取static_i 的值:4 
[线程30]获取static_i 的值:4 
[线程31]获取static_i 的值:4 
[线程24]获取static_i*2的值:20

看红色标注的部分,确实出现了我们的预想,同样也证明了我们的结论。

实例变量线程安全问题模拟:

----------------------------------------------------------------------------------

  1. public class Test implements Runnable
  2. {
  3. private int instance_i;//实例变量
  4. public void run()
  5. {
  6. instance_i = 4;
  7. System.out.println("[" + Thread.currentThread().getName()
  8. + "]获取instance_i 的值:" + instance_i);
  9. instance_i = 10;
  10. System.out.println("[" + Thread.currentThread().getName()
  11. + "]获取instance_i*3的值:" + instance_i * 2);
  12. }
  13. public static void main(String[] args)
  14. {
  15. Test t = new Test();
  16. //启动尽量多的线程才能很容易的模拟问题
  17. for (int i = 0; i < 3000; i++)
  18. {
  19. //每个线程对在对象t中运行,模拟单例情况
  20. new Thread(t, "线程" + i).start();
  21. }
  22. }
  23. }

按照本文开头的分析,犹如静态变量那样,每个线程都在修改同一个对象的实例变量,肯定会出现线程安全问题。

写道

[线程66]获取instance_i 的值:10 
[线程33]获取instance_i*2的值:20 
[线程67]获取instance_i 的值:4 
[线程34]获取instance_i*2的值:8 
[线程35]获取instance_i*2的值:20 
[线程68]获取instance_i 的值:4

看红色字体,可知单例情况下,实例变量线程非安全。

将new Thread(t, "线程" + i).start();改成new Thread(new Test(), "线程" + i).start();模拟非单例情况,会发现不存在线程安全问题。

局部变量线程安全问题模拟:

----------------------------------------------------------------------------------

  1. public class Test implements Runnable
  2. {
  3. public void run()
  4. {
  5. int local_i = 4;
  6. System.out.println("[" + Thread.currentThread().getName()
  7. + "]获取local_i 的值:" + local_i);
  8. local_i = 10;
  9. System.out.println("[" + Thread.currentThread().getName()
  10. + "]获取local_i*2的值:" + local_i * 2);
  11. }
  12. public static void main(String[] args)
  13. {
  14. Test t = new Test();
  15. //启动尽量多的线程才能很容易的模拟问题
  16. for (int i = 0; i < 3000; i++)
  17. {
  18. //每个线程对在对象t中运行,模拟单例情况
  19. new Thread(t, "线程" + i).start();
  20. }
  21. }
  22. }

控制台没有出现异常数据。

**************************************************************

为什么要线程同步?

  说到线程同步,大部分情况下, 我们是在针对“单对象多线程”的情况进行讨论,一般会将其分成两部分,一部分是关于“共享变量”,一部分关于“执行步骤”。

如何控制线程同步

  既然线程同步有上述问题,那么我们应该如何去解决呢?针对不同原因造成的同步问题,我们可以采取不同的策略。

1.控制共享变量

  我们可以采取3种方式来控制共享变量。

  (1)将“单对象多线程”修改成“多对象多线程”  

  上文提及,同步问题一般发生在“单对象多线程”的场景中,那么最简单的处理方式就是将运行模型修改成“多对象多线程”的样子,针对上面示例中的同步问题,修改后的代码如下:

 1 private static void sharedVaribleTest2() throws InterruptedException
2 {
3 Thread thread1 = new Thread(new MyRunner());
4 Thread thread2 = new Thread(new MyRunner());
5 thread1.setDaemon(true);
6 thread2.setDaemon(true);
7 thread1.start();
8 thread2.start();
9 thread1.join();
10 thread2.join();
11 }

  我们可以看到,上述代码中两个线程使用了两个不同的Runnable实例,它们在运行过程中,就不会去访问同一个全局变量。

  (2)将“全局变量”降级为“局部变量”

  既然是共享变量造成的问题,那么我们可以将共享变量改为“不共享”,即将其修改为局部变量。这样也可以解决问题,同样针对上面的示例,这种解决方式的代码如下:

 1 class MyRunner2 implements Runnable
2 {
3 public void run()
4 {
5 System.out.println(Thread.currentThread().getName() + " Start.");
6 int sum = 0;
7 for (int i = 1; i <= 100; i++)
8 {
9 sum += i;
10 }
11 try {
12 Thread.sleep(500);
13 } catch (InterruptedException e) {
14 e.printStackTrace();
15 }
16 System.out.println(Thread.currentThread().getName() + " --- The value of sum is " + sum);
17 System.out.println(Thread.currentThread().getName() + " End.");
18 }
19 }
20
21
22 private static void sharedVaribleTest3() throws InterruptedException
23 {
24 MyRunner2 runner = new MyRunner2();
25 Thread thread1 = new Thread(runner);
26 Thread thread2 = new Thread(runner);
27 thread1.setDaemon(true);
28 thread2.setDaemon(true);
29 thread1.start();
30 thread2.start();
31 thread1.join();
32 thread2.join();
33 }

  我们可以看出,sum变量已经由全局变量变为run方法内部的局部变量了。

  (3)使用ThreadLocal机制

  ThreadLocal是JDK引入的一种机制,它用于解决线程间共享变量,使用ThreadLocal声明的变量,即使在线程中属于全局变量,针对每个线程来讲,这个变量也是独立的。

  我们可以用这种方式来改造上面的代码,如下所示:

 1 class MyRunner3 implements Runnable
2 {
3 public ThreadLocal<Integer> tl = new ThreadLocal<Integer>();
4
5 public void run()
6 {
7 System.out.println(Thread.currentThread().getName() + " Start.");
8 for (int i = 0; i <= 100; i++)
9 {
10 if (tl.get() == null)
11 {
12 tl.set(new Integer(0));
13 }
14 int sum = ((Integer)tl.get()).intValue();
15 sum+= i;
16 tl.set(new Integer(sum));
17 try {
18 Thread.sleep(10);
19 } catch (InterruptedException e) {
20 e.printStackTrace();
21 }
22 }
23
24 System.out.println(Thread.currentThread().getName() + " --- The value of sum is " + ((Integer)tl.get()).intValue());
25 System.out.println(Thread.currentThread().getName() + " End.");
26 }
27 }
28
29
30 private static void sharedVaribleTest4() throws InterruptedException
31 {
32 MyRunner3 runner = new MyRunner3();
33 Thread thread1 = new Thread(runner);
34 Thread thread2 = new Thread(runner);
35 thread1.setDaemon(true);
36 thread2.setDaemon(true);
37 thread1.start();
38 thread2.start();
39 thread1.join();
40 thread2.join();
41 }

  综上三种方案,第一种方案会降低多线程执行的效率,因此,我们推荐使用第二种或者第三种方案。

2.控制执行步骤

  说到执行步骤,我们可以使用synchronized关键字来解决它。

 1 class MySyncRunner implements Runnable
2 {
3 public void run() {
4 synchronized(this)
5 {
6 System.out.println(Thread.currentThread().getName() + " Start.");
7 for(int i = 1; i <= 5; i++)
8 {
9 System.out.println(Thread.currentThread().getName() + " Running step " + i);
10 try
11 {
12 Thread.sleep(50);
13 }
14 catch(InterruptedException ex)
15 {
16 ex.printStackTrace();
17 }
18 }
19 System.out.println(Thread.currentThread().getName() + " End.");
20 }
21 }
22 }
23
24
25 private static void syncTest2() throws InterruptedException
26 {
27 MySyncRunner runner = new MySyncRunner();
28 Thread thread1 = new Thread(runner);
29 Thread thread2 = new Thread(runner);
30 thread1.setDaemon(true);
31 thread2.setDaemon(true);
32 thread1.start();
33 thread2.start();
34 thread1.join();
35 thread2.join();
36 }

  在线程同步的话题上,synchronized是一个非常重要的关键字。它的原理和数据库中事务锁的原理类似。我们在使用过程中,应该尽量缩减synchronized覆盖的范围,原因有二:1)被它覆盖的范围是串行的,效率低;2)容易产生死锁。我们来看下面的示例:

Java中处理线程同步的更多相关文章

  1. Java中的线程同步

    Java 中的线程同步问题: 1. 线程同步: 对于访问同一份资源的多个线程之间, 来进行协调的这个东西. 2. 同步方法: 当某个对象调用了同步方法时, 该对象上的其它同步方法必须等待该同步方法执行 ...

  2. java中实现线程同步

    为何要使用同步? java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他 ...

  3. Java中实现线程同步的三种方法

    实现同步的三种方法 多线程共享数据时,会发生线程不安全的情况,多线程共享数据必须同步. 实现同步的三种方法: 使用同步代码块 使用同步方法 使用互斥锁ReetrantLock(更灵活的代码控制) 代码 ...

  4. Java中的线程同步机制

    一.首先为什么线程需要同步? 1.多线程安全问题的原因      A:有多线程环境      B:有共享数据      C:有多条语句操作共享数据 2. //未完待续后面会继续更新

  5. 浅谈利用同步机制解决Java中的线程安全问题

    我们知道大多数程序都不会是单线程程序,单线程程序的功能非常有限,我们假设一下所有的程序都是单线程程序,那么会带来怎样的结果呢?假如淘宝是单线程程序,一直都只能一个一个用户去访问,你要在网上买东西还得等 ...

  6. 关于Java中的线程安全(线程同步)

    java中的线程安全是什么: 就是线程同步的意思,就是当一个程序对一个线程安全的方法或者语句进行访问的时候,其他的不能再对他进行操作了,必须等到这次访问结束以后才能对这个线程安全的方法进行访问 什么叫 ...

  7. Java中的线程Thread总结

    首先来看一张图,下面这张图很清晰的说明了线程的状态与Thread中的各个方法之间的关系,很经典的! 在Java中创建线程有两种方法:使用Thread类和使用Runnable接口. 要注意的是Threa ...

  8. 关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)

    Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文) 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其他博客主的博客,并且将我个人的理解也分享 ...

  9. Java多线程与线程同步

    六.多线程,线程,同步 ①概念: 并行:指两个或多个在时间同一时刻发生(同时发生) 并发:指两个或多个事件在同一时间段内发生 具体概念: 在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多 ...

随机推荐

  1. [Swift通天遁地]七、数据与安全-(5)高效操作SQLite数据库

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...

  2. $P2126 Mzc家中的男家丁$

    problem #ifdef Dubug #endif #include <bits/stdc++.h> using namespace std; typedef long long LL ...

  3. 【Codeforces】383.DIV2

    昨天一场CF发挥不好.抽点时间总结一下,然后顺带算是做个题解. 第一题水题 第二题思路很清晰,大概十分钟就想出来规模100000明显复杂度最多nlog所以只能一遍loop然后里利用map统计得到后面的 ...

  4. Coursera公开课-Machine_learing:编程作业2

    第三周编程作业:Logistic Regression 代码包在gitlab上:https://gitlab.com/luntai/Machine_Learning

  5. Zookeeper概念学习系列之zookeeper的节点

    znode有两种类型:  临时节点(ephemeral  node) 和 持久节点(persistent node). znode的类型在创建时确定并且之后不能再修改. 短暂znode的客户端会话结束 ...

  6. Android RecyclerView notifyDataSetChanged不起作用

    一般listview设置完data后调用notifyDataSetChanged便可刷新布局界面,然而recycleview调用这个方法却没有任何反应.对于很多不熟悉recycleview的话很容易躺 ...

  7. MariaDB常用命令手记

    创建用户命令 mysql>create user username@localhost identified by 'password'; 直接创建用户并授权的命令 mysql>grant ...

  8. 11、scala函数式编程

    1.将函数赋值给变量 2.匿名函数 3.高阶函数 4.高阶函数的类型推断 5.Scala的常用高级函数 6.闭包 7.SAM转换 8.Currying函数 9.return 1.将函数赋值给变量 Sc ...

  9. 关于Qt 报QDomDocument: No such file or directory错误解决办法

    肯定是没有找到相关的路径,这时候只需要在.pro文件中加入便好了,比如我要用到读写xml的一些头文件,则需要在.pro中加入如下代码: 就可以正常引用了.

  10. 2016.01.07 DOM笔记(二) DOM节点

    node节点属性 nodeName属性 oneBox= document.getElementsById('box');var s = oneBox.nodeName;  //nodeName与tag ...