引自: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. JavaSE综合项目演练

    光阴似箭日月如梭,大家学习已经有了一段时间了,转眼间,从刚开始如何配置JDK已经到了现在快学完网络编程了.学了这么多,眼看就要进入下一个阶段了,数据库编程了,那么在进入下个阶段前,我们来完成一个综合性 ...

  2. 洛谷2019 3月月赛 T3

    题干 唯一AC T3 的大巨佬%%% 这题就是个大模拟吧. 题解

  3. NS2学习笔记(二)

    Tcl语言 变量和变量赋值 set a "Hello World!" #将字符串赋值给变量a puts "NS2 say $a" #输出字符串的内容,其中$a表 ...

  4. Python 如何在csv中定位非数字和字母的符号

    在数据清洗过程中,有时不仅希望去掉脏数据,更希望定位脏数据的位置,例如从csv里面定位非数字和字母单元格的位置,在使用isdigit().isalpha().isalnum()时无法判断浮点数,会将浮 ...

  5. 2 我们的C#学习方法

    在这里我们借鉴了一种行之有效的学习编程语言的方法,并在此基础上进行了相应的改良.我们在培训新人中使用后,发现这种学习方法是非常有效的. 你通过做以下几个步骤来一步步学习C#语言. 1. 搞懂每一个我们 ...

  6. [ BZOJ 4318 & 3450 / CodeForces 235 B ] OSU!

    \(\\\) \(Description\) 一共进行\(N\)次操作,生成一个长度为\(N\)的\(01\)序列,成功对应\(1\),失败对应\(0\),已知每一次操作的成功率\(p_i\). 在这 ...

  7. FFmpeg编译Android版本

    FFmpeg是很好用的一个音视频库,功能强大,但是用起来并不是很方便.之前一直不想用FFmpeg,因为感觉编译太麻烦,但是到了不得不用的时候了,没办法,参考了网上大神的方法,在这里自己也记录一下方便以 ...

  8. Android O Bitmap 内存分配

      我们知道,一般认为在Android进程的内存模型中,heap分为两部分,一部分是native heap,一部分是Dalvik heap(实际上也是native heap的一部分).   Andro ...

  9. Leetcode0006--ZigZag Conversion

    [转载请注明]https://www.cnblogs.com/igoslly/p/9017638.html 来看一下题目: The string "PAYPALISHIRING" ...

  10. Jupyter(Ipython) Notebook 入门

    upyter Notebook(此前被称为 IPython notebook)是一个交互式笔记本,支持运行 40 多种编程语言. 一般用来编写漂亮的交互式文档. 文学编程的读者不是机器,而是人. 我们 ...