Java多线程(二) 多线程的锁机制
当两条线程同时访问一个类的时候,可能会带来一些问题。并发线程重入可能会带来内存泄漏、程序不可控等等。不管是线程间的通讯还是线程共享数据都需要使用Java的锁机制控制并发代码产生的问题。本篇总结主要著名Java的锁机制,阐述多线程下如何使用锁机制进行并发线程沟通。
1、并发下的程序异常
先看下下面两个代码,查看异常内容。
异常1:单例模式
package com.scl.thread; public class SingletonException
{
public static void main(String[] args)
{
// 开启十条线程进行分别测试输出类的hashCode,测试是否申请到同一个类
for (int i = 0; i < 10; i++)
{
new Thread(new Runnable()
{
@Override
public void run()
{
try
{
Thread.sleep(100);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + MySingle.getInstance().hashCode());
}
}).start();
}
}
} class MySingle
{
private static MySingle mySingle = null; private MySingle()
{
} public static MySingle getInstance()
{
if (mySingle == null) { mySingle = new MySingle(); }
return mySingle;
}
}
view code
运行结果如下:
由上述可见,Thread-7与其他结果不一致,证明了在多线程并发的情况下这种单例写法存在问题,问题就在第40行。多个线程同时进入了空值判断,线程创建了新的类。
异常2:线程重入,引发程序错误
现在想模拟国企生产规则,每个月生产100件产品,然后当月消费20件,依次更替。模拟该工厂全年的生产与销售
备注:举这个实例是为后面的信号量和生产者消费者问题做铺垫。可以另外举例,如开辟十条线程,每条线程内的任务就是进行1-10的累加,每条线程输出的结果不一定是55(线程重入导致)
package com.scl.thread; //每次生产100件产品,每次消费20件产品,生产消费更替12轮
public class ThreadCommunicateCopy
{
public static void main(String[] args)
{
final FactoryCopy factory = new FactoryCopy();
new Thread(new Runnable()
{ @Override
public void run()
{
try
{
Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
} for (int i = 1; i <= 12; i++)
{
factory.createProduct(i);
} }
}).start(); new Thread(new Runnable()
{ @Override
public void run()
{
try
{
Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
} for (int i = 1; i <= 12; i++)
{
factory.sellProduct(i);
} }
}).start(); }
} class FactoryCopy
{
//生产产品
public void createProduct(int i)
{ for (int j = 1; j <= 100; j++)
{
System.out.println("第" + i + "轮生产,产出" + j + "件");
}
}
//销售产品
public void sellProduct(int i)
{
for (int j = 1; j <= 20; j++)
{
System.out.println("第" + i + "轮销售,销售" + j + "件");
} }
}
结果如下:
该结果不能把销售线程和生产线程的代码分隔开,如果需要分隔开。可以使用Java的锁机制。下面总结下如何处理以上两个问题。
2、使用多线程编程目的及一些Java多线程的基本知识
使用多线程无非是期望程序能够更快地完成任务,这样并发编程就必须完成两件事情:线程同步及线程通信。
线程同步指的是:控制不同线程发生的先后顺序。
线程通信指的是:不同线程之间如何共享数据。
Java线程的内存模型:每个线程拥有自己的栈,堆内存共享 [来源:Java并发编程艺术 ],如下图所示。 锁是线程间内存和信息沟通的载体,了解线程间通信会对线程锁有个比较深入的了解。后面也会详细总结Java是如何根据锁的信息进行两条线程之间的通信。
2、使用Java的锁机制
Java语音设计和数据库一样,同样存在着代码锁.实现Java代码锁比较简单,一般使用两个关键字对代码进行线程锁定。最常用的就是volatile和synchronized两个
2.1 synchronized
synchronized关键字修饰的代码相当于数据库上的互斥锁。确保多个线程在同一时刻只能由一个线程处于方法或同步块中,确保线程对变量访问的可见和排它,获得锁的对象在代码结束后,会对锁进行释放。
synchronzied使用方法有两个:①加在方法上面锁定方法,②定义synchronized块。
模拟生产销售循环,可以通过synchronized关键字控制线程同步。代码如下:
package com.scl.thread; //每次生产100件产品,每次消费20件产品,生产消费更替10轮
public class ThreadCommunicate
{
public static void main(String[] args)
{
final FactoryCopy factory = new FactoryCopy();
new Thread(new Runnable()
{ @Override
public void run()
{
try
{
Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
} for (int i = 1; i <= 12; i++)
{
factory.createProduct(i);
} }
}).start(); new Thread(new Runnable()
{ @Override
public void run()
{
try
{
Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
} for (int i = 1; i <= 12; i++)
{
factory.sellProduct(i);
} }
}).start(); }
} class Factory
{
private boolean isCreate = true; public synchronized void createProduct(int i)
{
while (!isCreate)
{
try
{
this.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
} for (int j = 1; j <= 100; j++)
{
System.out.println("第" + i + "轮生产,产出" + j + "件");
}
isCreate = false;
this.notify();
} public synchronized void sellProduct(int i)
{
while (isCreate)
{
try
{
this.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
for (int j = 1; j <= 20; j++)
{
System.out.println("第" + i + "轮销售,销售" + j + "件");
}
isCreate = true;
this.notify();
}
}
上述代码通过synchronized关键字控制生产及销售方法每次只能1条线程进入。代码中使用了isCreate标志位控制生产及销售的顺序。
注意:即使代码不使用isCreate标志位进行控制,代码只会出现 :thread-0 生产---thread-0 生产--- thread-0 生产(生产完毕) ---thread-1 销售...这种情况,不会出现生产跟销售交替。原因:使用Synchronized关键字对方法进行约束,默认锁定的是对一个的object类,直到代码结束,才会把锁给释放。因此使用该关键字进行限制时不会出现线程交叠现象。
备注:默认的使用synchronized修饰方法, 关键字会以当前实例对象作为锁对象,对线程进行锁定。
单例模式的修改可以在getInstance方式中添加synchronized关键字进行约束,即可。
wait方法和notify方法将在第三篇线程总结中讲解。
2.2 volatile
volatile关键字主要用来修饰变量,关键字不像synchronized一样,能够块状地对代码进行锁定。该关键字可以看做对修饰的变量进行了读或写的同步操作。
如以下代码:
package com.scl.thread; public class NumberRange
{
private volatile int unSafeNum; public int getUnSafeNum()
{
return unSafeNum;
} public void setUnSafeNum(int unSafeNum)
{
this.unSafeNum = unSafeNum;
} public int addVersion()
{
return this.unSafeNum++;
}
}
代码编译后功能如下:
package com.scl.thread; public class NumberRange
{
private volatile int unSafeNum; public synchronized int getUnSafeNum()
{
return unSafeNum;
} public synchronized void setUnSafeNum(int unSafeNum)
{
this.unSafeNum = unSafeNum;
} public int addVersion()
{
int temp = getUnSafeNum();
temp = temp + 1;
setUnSafeNum(temp);
return temp;
} }
由此可见,使用volatile变量进行自增或自减操作的时候,变量进行temp= temp+1这一步时,多条线程同时可能同时操作这一句代码,导致内容出差。线程代码内的原子性被破坏了。
单纯使用volatile来控制boolean或者某一个int类型的时候,感觉不出太大的作用。但当volatile在修饰一个对象的时候,对象必须按照步骤进行。在单线程的情况下new一个对象必须进行三步操作:①开辟存储空间 ②初始化 ③使用变量指向该内存。在并发的情况下,虚拟机创建对象可能依据这三步依次执行。可能③在②之前执行,那么就可能会导致程序抛出空指针异常。这时候可以使用volatile保证对象初始化原子性。
以上是对Java锁机制的总结,如有问题,烦请指出纠正。代码及例子很大一部分参考了《Java 并发编程艺术》[方腾飞 著]
Java多线程(二) 多线程的锁机制的更多相关文章
- Java多线程5:Synchronized锁机制
一.前言 在多线程中,有时会出现多个线程对同一个对象的变量进行并发访问的情形,如果不做正确的同步处理,那么产生的后果就是“脏读”,也就是获取到的数据其实是被修改过的. 二.引入Synchronized ...
- Java并发编程:Concurrent锁机制解析
Java并发编程:Concurrent锁机制解析 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: # ...
- java 多线程总结篇4——锁机制
在开发Java多线程应用程序中,各个线程之间由于要共享资源,必须用到锁机制.Java提供了多种多线程锁机制的实现方式,常见的有synchronized.ReentrantLock.Semaphore. ...
- java多线程技术之八(锁机制)
Lock是java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题,我 ...
- Java多线程4:synchronized锁机制
脏读 一个常见的概念.在多线程中,难免会出现在多个线程中对同一个对象的实例变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数据其实是被更改过 ...
- java 多线程10:synchronized锁机制 之 锁定类静态方法 和锁定类.Class 和 数据String的常量池特性
同步静态方法 synchronized还可以应用在静态方法上,如果这么写,则代表的是对当前.java文件对应的Class类加锁.看一下例子,注意一下printC()并不是一个静态方法: public ...
- Java 并发和多线程(二) 多线程的优点 [转]
原文:http://tutorials.jenkov.com/java-concurrency/benefits.html 作者:Jakob Jenkov 翻译:古圣昌 ...
- Java多线程学习——synchronized锁机制
Java在多线程中使用同步锁机制时,一定要注意锁对对象,下面的例子就是没锁对对象(每个线程使用一个被锁住的对象时,得先看该对象的被锁住部分是否有人在使用) 例子:两个人操作同一个银行账户,丈夫在ATM ...
- 【转载】Java中的锁机制 synchronized & 偏向锁 & 轻量级锁 & 重量级锁 & 各自优缺点及场景 & AtomicReference
参考文章: http://blog.csdn.net/chen77716/article/details/6618779 目前在Java中存在两种锁机制:synchronized和Lock,Lock接 ...
随机推荐
- Scrapy 对不同的Item进行分开存储
在Piperlines里面进行对象的判断, def process_item(self, item, spider): if item.__class__ == BaseItem : #savexxx ...
- oc-01
//#ifndef __OCDay01__aa__ //#define __OCDay01__aa__ //这2行是预编译指令,防止include的时候重复包含操作(a包含b,b又包含了a) #inc ...
- C 语言 .h文件的作用
C语言头文件的作用 最近在工作当中遇到了一点小问题,关于C语言头文件的应用问题,主要还是关于全局变量的定义和声明问题.学习C语言已经有好几年了,工作使用也近半年了,但是对于这部分的东西的确还没有深入的 ...
- Linux环境变量的修改(永久,暂时)
Linux修改环境变量,很简单但很重要 一.Linux的变量种类 按变量的生存周期来划分,Linux变量可分为两类: 1. 永久的:需要修改配置文件,变量永久生效. 2. 临时的:使用export命令 ...
- Branch and Bound:分支限界算法
http://blog.sciencenet.cn/blog-509534-728984.html 分支定界 (branch and bound) 算法是一种在问题的解空间树上搜索问题的解的方法.但与 ...
- Hidden Markov Model
Markov Chain 马尔科夫链(Markov chain)是一个具有马氏性的随机过程,其时间和状态参数都是离散的.马尔科夫链可用于描述系统在状态空间中的各种状态之间的转移情况,其中下一个状态仅依 ...
- 实例源码--Android理财工具源码
下载源码 技术要点: 1.Sqlite数据库的综合使用 2.控件的综合使用 3. 源码带详细的中文注释 ...... 详细介绍: 1. Sqlite数据库的综合使用 本套源码采用了Sqlite ...
- 最新搭建GIT服务器仓库
新开了一个项目,现在需要将代码放在公司GIT服务器上面.所以这里需要了一些问题..记录一下.因为原来公司这边的服务器的git用户都是创建好的.这里没有创建.需要的可以看看:http://www.cnb ...
- IIS 之 添加绑定域名 或 设置输入IP直接访问网站
1.打开IIS,右键站点 → 编辑绑定,弹出“网站绑定”窗口,如下图: 2.点击“添加”,弹出“添加网站绑定”窗口,如下图: 注意:若想输入 IP 地址直接访问,则可以有以下两种设置任一均可: ...
- shell判断一个变量是否为空
判断一个变量是否为空 . 1. 变量通过" "引号引起来 如下所示:,可以得到结果为 IS NULL. #!/bin/sh para1= if [ ! -n "$para ...