- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章解析Java线程编程中的线程安全与synchronized的使用由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
一.什么时候会出现线程安全问题?
在单线程中不会出现线程安全问题,而在多线程编程中,有可能会出现同时访问同一个资源的情况,这种资源可以是各种类型的的资源:一个变量、一个对象、一个文件、一个数据库表等,而当多个线程同时访问同一个资源的时候,就会存在一个问题:
由于每个线程执行的过程是不可控的,所以很可能导致最终的结果与实际上的愿望相违背或者直接导致程序出错.
举个简单的例子:
现在有两个线程分别从网络上读取数据,然后插入一张数据库表中,要求不能插入重复的数据.
那么必然在插入数据的过程中存在两个操作:
1)检查数据库中是否存在该条数据; 。
2)如果存在,则不插入;如果不存在,则插入到数据库中.
假如两个线程分别用thread-1和thread-2表示,某一时刻,thread-1和thread-2都读取到了数据X,那么可能会发生这种情况:
thread-1去检查数据库中是否存在数据X,然后thread-2也接着去检查数据库中是否存在数据X.
结果两个线程检查的结果都是数据库中不存在数据X,那么两个线程都分别将数据X插入数据库表当中.
这个就是线程安全问题,即多个线程同时访问一个资源时,会导致程序运行结果并不是想看到的结果.
这里面,这个资源被称为:临界资源(也有称为共享资源).
也就是说,当多个线程同时访问临界资源(一个对象,对象中的属性,一个文件,一个数据库等)时,就可能会产生线程安全问题.
不过,当多个线程执行一个方法,方法内部的局部变量并不是临界资源,因为方法是在栈上执行的,而Java栈是线程私有的,因此不会产生线程安全问题.
二.如何解决线程安全问题?
那么一般来说,是如何解决线程安全问题的呢?
基本上所有的并发模式在解决线程安全问题时,都采用“序列化访问临界资源”的方案,即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问.
通常来说,是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问.
在Java中,提供了两种方式来实现同步互斥访问:synchronized和Lock.
本文主要讲述synchronized的使用方法,Lock的使用方法在下一篇博文中讲述.
三.synchronized同步方法或者同步块 。
在了解synchronized关键字的使用方法之前,我们先来看一个概念:互斥锁,顾名思义:能到达到互斥访问目的的锁.
举个简单的例子:如果对临界资源加上互斥锁,当一个线程在访问该临界资源时,其他线程便只能等待.
在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问.
在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块.
下面通过几个简单的例子来说明synchronized关键字的使用:
1.synchronized方法 。
下面这段代码中两个线程分别调用insertData对象插入数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public
class
Test {
public
static
void
main(String[] args) {
final
InsertData insertData =
new
InsertData();
new
Thread() {
public
void
run() {
insertData.insert(Thread.currentThread());
};
}.start();
new
Thread() {
public
void
run() {
insertData.insert(Thread.currentThread());
};
}.start();
}
}
class
InsertData {
private
ArrayList<Integer> arrayList =
new
ArrayList<Integer>();
public
void
insert(Thread thread){
for
(
int
i=
0
;i<
5
;i++){
System.out.println(thread.getName()+
"在插入数据"
+i);
arrayList.add(i);
}
}
}
|
此时程序的输出结果为:
说明两个线程在同时执行insert方法.
而如果在insert方法前面加上关键字synchronized的话,运行结果为:
1
2
3
4
5
6
7
8
9
10
|
class
InsertData {
private
ArrayList<Integer> arrayList =
new
ArrayList<Integer>();
public
synchronized
void
insert(Thread thread){
for
(
int
i=
0
;i<
5
;i++){
System.out.println(thread.getName()+
"在插入数据"
+i);
arrayList.add(i);
}
}
}
|
从上输出结果说明,Thread-1插入数据是等Thread-0插入完数据之后才进行的。说明Thread-0和Thread-1是顺序执行insert方法的.
这就是synchronized方法.
不过有几点需要注意:
1)当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法。这个原因很简单,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法.
2)当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。这个原因很简单,访问非synchronized方法不需要获得该对象的锁,假如一个方法没用synchronized关键字修饰,说明它不会使用到临界资源,那么其他线程是可以访问这个方法的, 。
3)如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型),也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题.
2.synchronized代码块 。
synchronized代码块类似于以下这种形式:
synchronized(synObject) { } 当在某个线程中执行这段代码块,该线程会获取对象synObject的锁,从而使得其他线程无法同时访问该代码块.
synObject可以是this,代表获取当前对象的锁,也可以是类中的一个属性,代表获取该属性的锁.
比如上面的insert方法可以改成以下两种形式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
class
InsertData {
private
ArrayList<Integer> arrayList =
new
ArrayList<Integer>();
public
void
insert(Thread thread){
synchronized
(
this
) {
for
(
int
i=
0
;i<
100
;i++){
System.out.println(thread.getName()+
"在插入数据"
+i);
arrayList.add(i);
}
}
}
}
class
InsertData {
private
ArrayList<Integer> arrayList =
new
ArrayList<Integer>();
private
Object object =
new
Object();
public
void
insert(Thread thread){
synchronized
(object) {
for
(
int
i=
0
;i<
100
;i++){
System.out.println(thread.getName()+
"在插入数据"
+i);
arrayList.add(i);
}
}
}
}
|
从上面可以看出,synchronized代码块使用起来比synchronized方法要灵活得多。因为也许一个方法中只有一部分代码只需要同步,如果此时对整个方法用synchronized进行同步,会影响程序执行效率。而使用synchronized代码块就可以避免这个问题,synchronized代码块可以实现只对需要同步的地方进行同步.
另外,每个类也会有一个锁,它可以用来控制对static数据成员的并发访问.
并且如果一个线程执行一个对象的非static synchronized方法,另外一个线程需要执行这个对象所属类的static synchronized方法,此时不会发生互斥现象,因为访问static synchronized方法占用的是类锁,而访问非static synchronized方法占用的是对象锁,所以不存在互斥现象.
看下面这段代码就明白了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
public
class
Test {
public
static
void
main(String[] args) {
final
InsertData insertData =
new
InsertData();
new
Thread(){
@Override
public
void
run() {
insertData.insert();
}
}.start();
new
Thread(){
@Override
public
void
run() {
insertData.insert1();
}
}.start();
}
}
class
InsertData {
public
synchronized
void
insert(){
System.out.println(
"执行insert"
);
try
{
Thread.sleep(
5000
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
System.out.println(
"执行insert完毕"
);
}
public
synchronized
static
void
insert1() {
System.out.println(
"执行insert1"
);
System.out.println(
"执行insert1完毕"
);
}
}
|
执行结果,
第一个线程里面执行的是insert方法,不会导致第二个线程执行insert1方法发生阻塞现象.
下面我们看一下synchronized关键字到底做了什么事情,我们来反编译它的字节码看一下,下面这段代码反编译后的字节码为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public
class
InsertData {
private
Object object =
new
Object();
public
void
insert(Thread thread){
synchronized
(object) {
}
}
public
synchronized
void
insert1(Thread thread){
}
public
void
insert2(Thread thread){
}
}
|
从反编译获得的字节码可以看出,synchronized代码块实际上多了monitorenter和monitorexit两条指令。monitorenter指令执行时会让对象的锁计数加1,而monitorexit指令执行时会让对象的锁计数减1,其实这个与操作系统里面的PV操作很像,操作系统里面的PV操作就是用来控制多个线程对临界资源的访问。对于synchronized方法,执行中的线程识别该方法的 method_info 结构是否有 ACC_SYNCHRONIZED 标记设置,然后它自动获取对象的锁,调用方法,最后释放锁。如果有异常发生,线程自动释放锁.
有一点要注意:对于synchronized方法或者synchronized代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象.
三.关于synchronized的其他一些值得注意的地方 。
1.synchronized与static synchronized 的区别 synchronized是对类的当前实例进行加锁,防止其他线程同时访问该类的该实例的所有synchronized块,注意这里是“类的当前实例”,类的两个不同实例就没有这种约束了。那么static synchronized恰好就是要控制类的所有实例的访问了,static synchronized是限制线程同时访问jvm中该类的所有实例同时访问对应的代码快。实际上,在类中某方法或某代码块中有synchronized,那么在生成一个该类实例后,该类也就有一个监视快,放置线程并发访问改实例synchronized保护快,而static synchronized则是所有该类的实例公用一个监视快了,也就是两个的区别了,也就是synchronized相当于this.synchronized,而 static synchronized相当于Something.synchronized. 一个日本作者-结成浩的《java多线程设计模式》有这样的一个列子: 。
1
2
3
4
5
6
|
pulbic
class
Something(){
public
synchronized
void
isSyncA(){}
public
synchronized
void
isSyncB(){}
public
static
synchronized
void
cSyncA(){}
public
static
synchronized
void
cSyncB(){}
}
|
那么,加入有Something类的两个实例a与b,那么下列组方法何以被1个以上线程同时访问呢 a. x.isSyncA()与x.isSyncB() b. x.isSyncA()与y.isSyncA() c. x.cSyncA()与y.cSyncB() d. x.isSyncA()与Something.cSyncA() 这里,很清楚的可以判断: a,都是对同一个实例的synchronized域访问,因此不能被同时访问 b,是针对不同实例的,因此可以同时被访问 c,因为是static synchronized,所以不同实例之间仍然会被限制,相当于Something.isSyncA()与 Something.isSyncB()了,因此不能被同时访问。 那么,第d呢?,书上的 答案是可以被同时访问的,答案理由是synchronzied的是实例方法与synchronzied的类方法由于锁定(lock)不同的原因。 个人分析也就是synchronized 与static synchronized 相当于两帮派,各自管各自,相互之间就无约束了,可以被同时访问。目前还不是分清楚java内部设计synchronzied是怎么样实现的。 结论:A: synchronized static是某个类的范围,synchronized static cSync{}防止多个线程同时访问这个 类中的synchronized static 方法。它可以对类的所有对象实例起作用。 B: synchronized 是某实例的范围,synchronized isSync(){}防止多个线程同时访问这个实例中的synchronized 方法。 2.synchronized方法与synchronized代码快的区别 synchronized methods(){} 与synchronized(this){}之间没有什么区别,只是 synchronized methods(){} 便于阅读理解,而synchronized(this){}可以更精确的控制冲突限制访问区域,有时候表现更高效率。 3.synchronized关键字是不能继承的 。
最后此篇关于解析Java线程编程中的线程安全与synchronized的使用的文章就讲到这里了,如果你想了解更多关于解析Java线程编程中的线程安全与synchronized的使用的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我在网上搜索但没有找到任何合适的文章解释如何使用 javascript 使用 WCF 服务,尤其是 WebScriptEndpoint。 任何人都可以对此给出任何指导吗? 谢谢 最佳答案 这是一篇关于
我正在编写一个将运行 Linux 命令的 C 程序,例如: cat/etc/passwd | grep 列表 |剪切-c 1-5 我没有任何结果 *这里 parent 等待第一个 child (chi
所以我正在尝试处理文件上传,然后将该文件作为二进制文件存储到数据库中。在我存储它之后,我尝试在给定的 URL 上提供文件。我似乎找不到适合这里的方法。我需要使用数据库,因为我使用 Google 应用引
我正在尝试制作一个宏,将下面的公式添加到单元格中,然后将其拖到整个列中并在 H 列中复制相同的公式 我想在 F 和 H 列中输入公式的数据 Range("F1").formula = "=IF(ISE
问题类似于this one ,但我想使用 OperatorPrecedenceParser 解析带有函数应用程序的表达式在 FParsec . 这是我的 AST: type Expression =
我想通过使用 sequelize 和 node.js 将这个查询更改为代码取决于在哪里 select COUNT(gender) as genderCount from customers where
我正在使用GNU bash,版本5.0.3(1)-发行版(x86_64-pc-linux-gnu),我想知道为什么简单的赋值语句会出现语法错误: #/bin/bash var1=/tmp
这里,为什么我的代码在 IE 中不起作用。我的代码适用于所有浏览器。没有问题。但是当我在 IE 上运行我的项目时,它发现错误。 而且我的 jquery 类和 insertadjacentHTMl 也不
我正在尝试更改标签的innerHTML。我无权访问该表单,因此无法编辑 HTML。标签具有的唯一标识符是“for”属性。 这是输入和标签的结构:
我有一个页面,我可以在其中返回用户帖子,可以使用一些 jquery 代码对这些帖子进行即时评论,在发布新评论后,我在帖子下插入新评论以及删除 按钮。问题是 Delete 按钮在新插入的元素上不起作用,
我有一个大约有 20 列的“管道分隔”文件。我只想使用 sha1sum 散列第一列,它是一个数字,如帐号,并按原样返回其余列。 使用 awk 或 sed 执行此操作的最佳方法是什么? Accounti
我需要将以下内容插入到我的表中...我的用户表有五列 id、用户名、密码、名称、条目。 (我还没有提交任何东西到条目中,我稍后会使用 php 来做)但由于某种原因我不断收到这个错误:#1054 - U
所以我试图有一个输入字段,我可以在其中输入任何字符,但然后将输入的值小写,删除任何非字母数字字符,留下“。”而不是空格。 例如,如果我输入: 地球的 70% 是水,-!*#$^^ & 30% 土地 输
我正在尝试做一些我认为非常简单的事情,但出于某种原因我没有得到想要的结果?我是 javascript 的新手,但对 java 有经验,所以我相信我没有使用某种正确的规则。 这是一个获取输入值、检查选择
我想使用 angularjs 从 mysql 数据库加载数据。 这就是应用程序的工作原理;用户登录,他们的用户名存储在 cookie 中。该用户名显示在主页上 我想获取这个值并通过 angularjs
我正在使用 autoLayout,我想在 UITableViewCell 上放置一个 UIlabel,它应该始终位于单元格的右侧和右侧的中心。 这就是我想要实现的目标 所以在这里你可以看到我正在谈论的
我需要与 MySql 等效的 elasticsearch 查询。我的 sql 查询: SELECT DISTINCT t.product_id AS id FROM tbl_sup_price t
我正在实现代码以使用 JSON。 func setup() { if let flickrURL = NSURL(string: "https://api.flickr.com/
我尝试使用for循环声明变量,然后测试cols和rols是否相同。如果是,它将运行递归函数。但是,我在 javascript 中执行 do 时遇到问题。有人可以帮忙吗? 现在,在比较 col.1 和
我举了一个我正在处理的问题的简短示例。 HTML代码: 1 2 3 CSS 代码: .BB a:hover{ color: #000; } .BB > li:after {
我是一名优秀的程序员,十分优秀!