Java多线程(二) 多线程的锁机制

当两条线程同时访问一个类的时候,可能会带来一些问题。并发线程重入可能会带来内存泄漏、程序不可控等等。不管是线程间的通讯还是线程共享数据都需要使用Java的锁机制控制并发代码产生的问题。本篇总结主要著名Java的锁机制,阐述多线程下如何使用锁机制进行并发线程沟通。

1、并发下的程序异常

  先看下下面两个代码,查看异常内容。

  异常1:单例模式

 1 package com.scl.thread;
 2
 3 public class SingletonException
 4 {
 5     public static void main(String[] args)
 6     {
 7         // 开启十条线程进行分别测试输出类的hashCode,测试是否申请到同一个类
 8         for (int i = 0; i < 10; i++)
 9         {
10             new Thread(new Runnable()
11             {
12                 @Override
13                 public void run()
14                 {
15                     try
16                     {
17                         Thread.sleep(100);
18                     }
19                     catch (InterruptedException e)
20                     {
21                         e.printStackTrace();
22                     }
23                     System.out.println(Thread.currentThread().getName() + "  " + MySingle.getInstance().hashCode());
24                 }
25             }).start();
26         }
27     }
28 }
29
30 class MySingle
31 {
32     private static MySingle mySingle = null;
33
34     private MySingle()
35     {
36     }
37
38     public static MySingle getInstance()
39     {
40         if (mySingle == null) { mySingle = new MySingle(); }
41         return mySingle;
42     }
43 }

view code

运行结果如下:

  由上述可见,Thread-7与其他结果不一致,证明了在多线程并发的情况下这种单例写法存在问题,问题就在第40行。多个线程同时进入了空值判断,线程创建了新的类。

  异常2:线程重入,引发程序错误

现在想模拟国企生产规则,每个月生产100件产品,然后当月消费20件,依次更替。模拟该工厂全年的生产与销售

备注:举这个实例是为后面的信号量和生产者消费者问题做铺垫。可以另外举例,如开辟十条线程,每条线程内的任务就是进行1-10的累加,每条线程输出的结果不一定是55(线程重入导致)

 1 package com.scl.thread;
 2
 3 //每次生产100件产品,每次消费20件产品,生产消费更替12轮
 4 public class ThreadCommunicateCopy
 5 {
 6     public static void main(String[] args)
 7     {
 8         final FactoryCopy factory = new FactoryCopy();
 9         new Thread(new Runnable()
10         {
11
12             @Override
13             public void run()
14             {
15                 try
16                 {
17                     Thread.sleep(2000);
18                 }
19                 catch (InterruptedException e)
20                 {
21                     e.printStackTrace();
22                 }
23
24                 for (int i = 1; i <= 12; i++)
25                 {
26                     factory.createProduct(i);
27                 }
28
29             }
30         }).start();
31
32         new Thread(new Runnable()
33         {
34
35             @Override
36             public void run()
37             {
38                 try
39                 {
40                     Thread.sleep(2000);
41                 }
42                 catch (InterruptedException e)
43                 {
44                     e.printStackTrace();
45                 }
46
47                 for (int i = 1; i <= 12; i++)
48                 {
49                     factory.sellProduct(i);
50                 }
51
52             }
53         }).start();
54
55     }
56 }
57
58 class FactoryCopy
59 {
60     //生产产品
61     public void createProduct(int i)
62     {
63
64         for (int j = 1; j <= 100; j++)
65         {
66             System.out.println("第" + i + "轮生产,产出" + j + "件");
67         }
68     }
69     //销售产品
70     public void sellProduct(int i)
71     {
72         for (int j = 1; j <= 20; j++)
73         {
74             System.out.println("第" + i + "轮销售,销售" + j + "件");
75         }
76
77     }
78 }

结果如下:

该结果不能把销售线程和生产线程的代码分隔开,如果需要分隔开。可以使用Java的锁机制。下面总结下如何处理以上两个问题。

2、使用多线程编程目的及一些Java多线程的基本知识

  使用多线程无非是期望程序能够更快地完成任务,这样并发编程就必须完成两件事情:线程同步及线程通信。

线程同步指的是:控制不同线程发生的先后顺序。

线程通信指的是:不同线程之间如何共享数据。

   Java线程的内存模型:每个线程拥有自己的栈,堆内存共享 [来源:Java并发编程艺术 ],如下图所示。 锁是线程间内存和信息沟通的载体,了解线程间通信会对线程锁有个比较深入的了解。后面也会详细总结Java是如何根据锁的信息进行两条线程之间的通信。

2、使用Java的锁机制

Java语音设计和数据库一样,同样存在着代码锁.实现Java代码锁比较简单,一般使用两个关键字对代码进行线程锁定。最常用的就是volatile和synchronized两个

2.1 synchronized

synchronized关键字修饰的代码相当于数据库上的互斥锁。确保多个线程在同一时刻只能由一个线程处于方法或同步块中,确保线程对变量访问的可见和排它,获得锁的对象在代码结束后,会对锁进行释放。

synchronzied使用方法有两个:①加在方法上面锁定方法,②定义synchronized块。

模拟生产销售循环,可以通过synchronized关键字控制线程同步。代码如下:

  1 package com.scl.thread;
  2
  3 //每次生产100件产品,每次消费20件产品,生产消费更替10轮
  4 public class ThreadCommunicate
  5 {
  6     public static void main(String[] args)
  7     {
  8         final FactoryCopy factory = new FactoryCopy();
  9         new Thread(new Runnable()
 10         {
 11
 12             @Override
 13             public void run()
 14             {
 15                 try
 16                 {
 17                     Thread.sleep(2000);
 18                 }
 19                 catch (InterruptedException e)
 20                 {
 21                     e.printStackTrace();
 22                 }
 23
 24                 for (int i = 1; i <= 12; i++)
 25                 {
 26                     factory.createProduct(i);
 27                 }
 28
 29             }
 30         }).start();
 31
 32         new Thread(new Runnable()
 33         {
 34
 35             @Override
 36             public void run()
 37             {
 38                 try
 39                 {
 40                     Thread.sleep(2000);
 41                 }
 42                 catch (InterruptedException e)
 43                 {
 44                     e.printStackTrace();
 45                 }
 46
 47                 for (int i = 1; i <= 12; i++)
 48                 {
 49                     factory.sellProduct(i);
 50                 }
 51
 52             }
 53         }).start();
 54
 55     }
 56 }
 57
 58 class Factory
 59 {
 60     private boolean isCreate = true;
 61
 62     public synchronized void createProduct(int i)
 63     {
 64         while (!isCreate)
 65         {
 66             try
 67             {
 68                 this.wait();
 69             }
 70             catch (InterruptedException e)
 71             {
 72                 e.printStackTrace();
 73             }
 74         }
 75
 76         for (int j = 1; j <= 100; j++)
 77         {
 78             System.out.println("第" + i + "轮生产,产出" + j + "件");
 79         }
 80         isCreate = false;
 81         this.notify();
 82     }
 83
 84     public synchronized void sellProduct(int i)
 85     {
 86         while (isCreate)
 87         {
 88             try
 89             {
 90                 this.wait();
 91             }
 92             catch (InterruptedException e)
 93             {
 94                 e.printStackTrace();
 95             }
 96         }
 97         for (int j = 1; j <= 20; j++)
 98         {
 99             System.out.println("第" + i + "轮销售,销售" + j + "件");
100         }
101         isCreate = true;
102         this.notify();
103     }
104 }

  上述代码通过synchronized关键字控制生产及销售方法每次只能1条线程进入。代码中使用了isCreate标志位控制生产及销售的顺序。

备注:默认的使用synchronized修饰方法, 关键字会以当前实例对象作为锁对象,对线程进行锁定。

单例模式的修改可以在getInstance方式中添加synchronized关键字进行约束,即可。

wait方法和notify方法将在第三篇线程总结中讲解。

2.2 volatile

  volatile关键字主要用来修饰变量,关键字不像synchronized一样,能够块状地对代码进行锁定。该关键字可以看做对修饰的变量进行了读或写的同步操作。

  如以下代码:

 1 package com.scl.thread;
 2
 3 public class NumberRange
 4 {
 5     private volatile int unSafeNum;
 6
 7     public int getUnSafeNum()
 8     {
 9         return unSafeNum;
10     }
11
12     public void setUnSafeNum(int unSafeNum)
13     {
14         this.unSafeNum = unSafeNum;
15     }
16
17     public int addVersion()
18     {
19         return this.unSafeNum++;
20     }
21 }

代码编译后功能如下:

 1 package com.scl.thread;
 2
 3 public class NumberRange
 4 {
 5     private volatile int unSafeNum;
 6
 7     public synchronized int getUnSafeNum()
 8     {
 9         return unSafeNum;
10     }
11
12     public synchronized void setUnSafeNum(int unSafeNum)
13     {
14         this.unSafeNum = unSafeNum;
15     }
16
17     public int addVersion()
18     {
19         int temp = getUnSafeNum();
20         temp = temp + 1;
21         setUnSafeNum(temp);
22         return temp;
23     }
24
25 }

由此可见,使用volatile变量进行自增或自减操作的时候,变量进行temp= temp+1这一步时,多条线程同时可能同时操作这一句代码,导致内容出差。线程代码内的原子性被破坏了。

以上是对Java锁机制的总结,如有问题,烦请指出纠正。代码及例子很大一部分参考了《Java 并发编程艺术》[方腾飞 著]

时间: 2024-10-10 21:20:21

Java多线程(二) 多线程的锁机制的相关文章

Java并发编程:Concurrent锁机制解析

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

java多线程技术之八(锁机制)

Lock是java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题,我们拿Java线程(二)中的一个例子简单的实现一下和sychronized一样的效果,代码如下: 1 public class LockTest { 2 public static void main(String[] args) { 3 final Outputter1 output = new O

Java多线程4:synchronized锁机制

脏读 一个常见的概念.在多线程中,难免会出现在多个线程中对同一个对象的实例变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数据其实是被更改过的. 多线程线程安全问题示例 看一段代码: public class ThreadDomain13 { private int num = 0; public void addNum(String userName) { try { if ("a".equals(userName)) {

深入理解java虚拟机二,内存管理机制

java 虚拟机自动内存管理. java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同区域 1 程序计数器 每个线程都有一个独立的计数器,用来指示需要执行的字节码的位置. 2 虚拟机栈 虚拟机栈是用来描述java方法执行的内存模型,每个方法被执行的时候都会同时创建一个栈帧用于储存局部变量表,操作栈,动态链接,方法出口等信息. 每一个方法被调用直至执行完成的过程,就对应着一个栈帧从虚拟机栈中从入栈到出栈的过程. 虚拟机栈线程私有,声明周期和线程一样. 局部变量表所需的内存空间在

【转载】Java中的锁机制 synchronized &amp; Lock

参考文章: http://blog.csdn.net/chen77716/article/details/6618779 目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug Lea.本文并不比较synchronized与Lock孰优孰劣,只是介绍二者的实现原理. 数据同步需要依赖锁,那锁的同步又依赖谁?synchronized给出的答案是在软件层面依赖JVM,而Lock给出的方案是在硬件层面依赖特殊的

Android-Java-synchronized同步锁机制&amp;利与弊

synchronized同步锁机制 定义锁??的方式一: package android.java.thread09; public class Test implements Runnable { @Override public void run() { /** * 定义一个锁??,这个锁是Jav内部要去判断用的,属于隐士的 看不到的 * Java内部的实现属于 ??锁机制 */ Object lock = new Object(); synchronized (lock) { /** *

MySQL基础篇(06):事务管理,锁机制案例详解

本文源码:GitHub·点这里 || GitEE·点这里 一.锁概念简介 1.基础描述 锁机制核心功能是用来协调多个会话中多线程并发访问相同资源时,资源的占用问题.锁机制是一个非常大的模块,贯彻MySQL的几大核心难点模块:索引,锁机制,事务.这里是基于MySQL5.6演示的几种典型场景,对面MySQL这几块问题时,有分析流程和思路是比较关键的.在MySQL中常见这些锁概念:共享读锁.排它写锁 ; 表锁.行锁.间隙锁. 2.存储引擎和锁 MyISAM引擎:基于读写两种模式,支持表级锁 ; Inn

Mysql中那些锁机制之InnoDB

我们知道mysql在曾经.存储引擎默认是MyISAM.可是随着对事务和并发的要求越来越高,便引入了InnoDB引擎.它具有支持事务安全等一系列特性. InnoDB锁模式 InnoDB实现了两种类型的行锁. 共享锁(S):同意一个事务去读一行,阻止其它事务获得同样的数据集的排他锁. 排他锁(X):同意获得排他锁的事务更新数据,可是组织其它事务获得同样数据集的共享锁和排他锁. 能够这么理解: 共享锁就是我读的时候,你能够读,可是不能写.排他锁就是我写的时候.你不能读也不能写.事实上就是MyISAM的

[转载]深入JVM锁机制-synchronized

转自:http://blog.csdn.net/chen77716/article/details/6618779,并加上少量自己的理解 目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug Lea.本文并不比较synchronized与Lock孰优孰劣,只是介绍二者的实现原理. 数据同步需要依赖锁,那锁的同步又依赖谁?synchronized给出的答案是在软件层面依赖JVM,而Lock给出的方案是在硬