- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章五种Java多线程同步的方法由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
为什么要线程同步 。
因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举 个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果 呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.那到底是哪个呢?很难说清楚。因此多线程同步就是要解决这个问题.
1、不同步时的代码 。
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
Bank.java
package
threadTest;
/**
* @author ww
*
*/
public
class
Bank {
private
int
count =
0
;
//账户余额
//存钱
public
void
addMoney(
int
money){
count +=money;
System.out.println(System.currentTimeMillis()+
"存进:"
+money);
}
//取钱
public
void
subMoney(
int
money){
if
(count-money <
0
){
System.out.println(
"余额不足"
);
return
;
}
count -=money;
System.out.println(+System.currentTimeMillis()+
"取出:"
+money);
}
//查询
public
void
lookMoney(){
System.out.println(
"账户余额:"
+count);
}
}
SyncThreadTest.java
package
threadTest;
public
class
SyncThreadTest {
public
static
void
main(String args[]){
final
Bank bank=
new
Bank();
Thread tadd=
new
Thread(
new
Runnable() {
@Override
public
void
run() {
// TODO Auto-generated method stub
while
(
true
){
try
{
Thread.sleep(
1000
);
}
catch
(InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
bank.addMoney(
100
);
bank.lookMoney();
System.out.println(
"\n"
);
}
}
});
Thread tsub =
new
Thread(
new
Runnable() {
@Override
public
void
run() {
// TODO Auto-generated method stub
while
(
true
){
bank.subMoney(
100
);
bank.lookMoney();
System.out.println(
"\n"
);
try
{
Thread.sleep(
1000
);
}
catch
(InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
tsub.start();
tadd.start();
}
}
|
代码很简单,我就不解释了,看看运行结果怎样呢?截取了其中的一部分,是不是很乱,有写看不懂.
余额不足 账户余额:0 。
余额不足 账户余额:100 。
1441790503354存进:100 账户余额:100 。
1441790504354存进:100 账户余额:100 。
1441790504354取出:100 账户余额:100 。
1441790505355存进:100 账户余额:100 。
1441790505355取出:100 账户余额:100 。
2、使用同步时的代码 。
(1)同步方法:
即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态.
修改后的Bank.java 。
再看看运行结果:
余额不足 账户余额:0 。
余额不足 账户余额:0 。
1441790837380存进:100 账户余额:100 。
1441790838380取出:100 账户余额:0 1441790838380存进:100 账户余额:100 。
1441790839381取出:100 账户余额:0 。
瞬间感觉可以理解了吧.
注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类 。
(2)同步代码块 。
即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步 。
Bank.java代码如下:
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
36
37
|
package
threadTest;
/**
* @author ww
*
*/
public
class
Bank {
private
int
count =
0
;
//账户余额
//存钱
public
void
addMoney(
int
money){
synchronized
(
this
) {
count +=money;
}
System.out.println(System.currentTimeMillis()+
"存进:"
+money);
}
//取钱
public
void
subMoney(
int
money){
synchronized
(
this
) {
if
(count-money <
0
){
System.out.println(
"余额不足"
);
return
;
}
count -=money;
}
System.out.println(+System.currentTimeMillis()+
"取出:"
+money);
}
//查询
public
void
lookMoney(){
System.out.println(
"账户余额:"
+count);
}
}
|
运行结果如下:
余额不足 账户余额:0 。
1441791806699存进:100 账户余额:100 。
1441791806700取出:100 账户余额:0 。
1441791807699存进:100 账户余额:100 。
效果和方法一差不多.
注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可.
(3)使用特殊域变量(Volatile)实现线程同步 。
a.volatile关键字为域变量的访问提供了一种免锁机制 b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新 c.因此每次使用该域就要重新计算,而不是使用寄存器中的值 d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量 。
Bank.java代码如下:
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
|
package
threadTest;
/**
* @author ww
*
*/
public
class
Bank {
private
volatile
int
count =
0
;
// 账户余额
// 存钱
public
void
addMoney(
int
money) {
count += money;
System.out.println(System.currentTimeMillis() +
"存进:"
+ money);
}
// 取钱
public
void
subMoney(
int
money) {
if
(count - money <
0
) {
System.out.println(
"余额不足"
);
return
;
}
count -= money;
System.out.println(+System.currentTimeMillis() +
"取出:"
+ money);
}
// 查询
public
void
lookMoney() {
System.out.println(
"账户余额:"
+ count);
}
}
|
运行效果怎样呢?
余额不足 账户余额:0 。
余额不足 账户余额:100 。
1441792010959存进:100 账户余额:100 。
1441792011960取出:100 账户余额:0 。
1441792011961存进:100 账户余额:100 。
是不是又看不懂了,又乱了。这是为什么呢?就是因为volatile不能保证原子操作导致的,因此volatile不能代替 synchronized。此外volatile会组织编译器对代码优化,因此能不使用它就不适用它吧。它的原理是每次要线程要访问volatile修饰 的变量时都是从内存中读取,而不是存缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步.
(4)使用重入锁实现线程同步 。
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。 ReenreantLock类的常用方法有: ReentrantLock() : 创建一个ReentrantLock实例 lock() : 获得锁 unlock() : 释放锁 注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用 Bank.java代码修改如下:
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
package
threadTest;
import
java.util.concurrent.locks.Lock;
import
java.util.concurrent.locks.ReentrantLock;
/**
* @author ww
*
*/
public
class
Bank {
private
int
count =
0
;
// 账户余额
//需要声明这个锁
private
Lock lock =
new
ReentrantLock();
// 存钱
public
void
addMoney(
int
money) {
lock.lock();
//上锁
try
{
count += money;
System.out.println(System.currentTimeMillis() +
"存进:"
+ money);
}
finally
{
lock.unlock();
//解锁
}
}
// 取钱
public
void
subMoney(
int
money) {
lock.lock();
try
{
if
(count - money <
0
) {
System.out.println(
"余额不足"
);
return
;
}
count -= money;
System.out.println(+System.currentTimeMillis() +
"取出:"
+ money);
}
finally
{
lock.unlock();
}
}
// 查询
public
void
lookMoney() {
System.out.println(
"账户余额:"
+ count);
}
}
|
运行效果怎么样呢?
余额不足 账户余额:0 。
余额不足 账户余额:0 。
1441792891934存进:100 账户余额:100 。
1441792892935存进:100 账户余额:200 。
1441792892954取出:100 账户余额:100 。
效果和前两种方法差不多.
如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 。如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 。
(5)使用局部变量实现线程同步 。
Bank.java代码如下:
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
36
37
38
39
40
|
package threadTest;
/**
* @author ww
*
*/
public class Bank {
private static ThreadLocal<
Integer
> count = new ThreadLocal<
Integer
>(){
@Override
protected Integer initialValue() {
// TODO Auto-generated method stub
return 0;
}
};
// 存钱
public void addMoney(int money) {
count.set(count.get()+money);
System.out.println(System.currentTimeMillis() + "存进:" + money);
}
// 取钱
public void subMoney(int money) {
if (count.get() - money < 0) {
System.out.println("余额不足");
return;
}
count.set(count.get()- money);
System.out.println(+System.currentTimeMillis() + "取出:" + money);
}
// 查询
public void lookMoney() {
System.out.println("账户余额:" + count.get());
}
}
|
运行效果:
余额不足 账户余额:0 。
余额不足 账户余额:0 。
1441794247939存进:100 账户余额:100 。
余额不足 1441794248940存进:100 账户余额:0 。
账户余额:200 。
余额不足 账户余额:0 。
1441794249941存进:100 账户余额:300 。
看了运行效果,一开始一头雾水,怎么只让存,不让取啊?看看ThreadLocal的原理:
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变 量副本,而不会对其他线程产生影响。现在明白了吧,原来每个线程运行的都是一个副本,也就是说存钱和取钱是两个账户,知识名字相同而已。所以就会发生上面 的效果.
ThreadLocal与同步机制 。
a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题 b.前者采用以”空间换时间”的方法,后者采用以”时间换空间”的方式 。
现在都明白了吧。各有优劣,各有适用场景,希望本文可以对大家更深入的了解Java 多线程同步有所帮助.
最后此篇关于五种Java多线程同步的方法的文章就讲到这里了,如果你想了解更多关于五种Java多线程同步的方法的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在编写一个具有以下签名的 Java 方法。 void Logger(Method method, Object[] args); 如果一个方法(例如 ABC() )调用此方法 Logger,它应该
我是 Java 新手。 我的问题是我的 Java 程序找不到我试图用作的图像文件一个 JButton。 (目前这段代码什么也没做,因为我只是得到了想要的外观第一的)。这是我的主课 代码: packag
好的,今天我在接受采访,我已经编写 Java 代码多年了。采访中说“Java 垃圾收集是一个棘手的问题,我有几个 friend 一直在努力弄清楚。你在这方面做得怎么样?”。她是想骗我吗?还是我的一生都
我的 friend 给了我一个谜语让我解开。它是这样的: There are 100 people. Each one of them, in his turn, does the following
如果我将使用 Java 5 代码的应用程序编译成字节码,生成的 .class 文件是否能够在 Java 1.4 下运行? 如果后者可以工作并且我正在尝试在我的 Java 1.4 应用程序中使用 Jav
有关于why Java doesn't support unsigned types的问题以及一些关于处理无符号类型的问题。我做了一些搜索,似乎 Scala 也不支持无符号数据类型。限制是Java和S
我只是想知道在一个 java 版本中生成的字节码是否可以在其他 java 版本上运行 最佳答案 通常,字节码无需修改即可在 较新 版本的 Java 上运行。它不会在旧版本上运行,除非您使用特殊参数 (
我有一个关于在命令提示符下执行 java 程序的基本问题。 在某些机器上我们需要指定 -cp 。 (类路径)同时执行java程序 (test为java文件名与.class文件存在于同一目录下) jav
我已经阅读 StackOverflow 有一段时间了,现在我才鼓起勇气提出问题。我今年 20 岁,目前在我的家乡(罗马尼亚克卢日-纳波卡)就读 IT 大学。足以介绍:D。 基本上,我有一家提供簿记应用
我有 public JSONObject parseXML(String xml) { JSONObject jsonObject = XML.toJSONObject(xml); r
我已经在 Java 中实现了带有动态类型的简单解释语言。不幸的是我遇到了以下问题。测试时如下代码: def main() { def ks = Map[[1, 2]].keySet()
一直提示输入 1 到 10 的数字 - 结果应将 st、rd、th 和 nd 添加到数字中。编写一个程序,提示用户输入 1 到 10 之间的任意整数,然后以序数形式显示该整数并附加后缀。 public
我有这个 DownloadFile.java 并按预期下载该文件: import java.io.*; import java.net.URL; public class DownloadFile {
我想在 GUI 上添加延迟。我放置了 2 个 for 循环,然后重新绘制了一个标签,但这 2 个 for 循环一个接一个地执行,并且标签被重新绘制到最后一个。 我能做什么? for(int i=0;
我正在对对象 Student 的列表项进行一些测试,但是我更喜欢在 java 类对象中创建硬编码列表,然后从那里提取数据,而不是连接到数据库并在结果集中选择记录。然而,自从我这样做以来已经很长时间了,
我知道对象创建分为三个部分: 声明 实例化 初始化 classA{} classB extends classA{} classA obj = new classB(1,1); 实例化 它必须使用
我有兴趣使用 GPRS 构建车辆跟踪系统。但是,我有一些问题要问以前做过此操作的人: GPRS 是最好的技术吗?人们意识到任何问题吗? 我计划使用 Java/Java EE - 有更好的技术吗? 如果
我可以通过递归方法反转数组,例如:数组={1,2,3,4,5} 数组结果={5,4,3,2,1}但我的结果是相同的数组,我不知道为什么,请帮助我。 public class Recursion { p
有这样的标准方式吗? 包括 Java源代码-测试代码- Ant 或 Maven联合单元持续集成(可能是巡航控制)ClearCase 版本控制工具部署到应用服务器 最后我希望有一个自动构建和集成环境。
我什至不知道这是否可能,我非常怀疑它是否可能,但如果可以,您能告诉我怎么做吗?我只是想知道如何从打印机打印一些文本。 有什么想法吗? 最佳答案 这里有更简单的事情。 import javax.swin
我是一名优秀的程序员,十分优秀!