还不知道线程同步?请进!

还不知道线程同步? 来来来….

什么叫同步?

同步(英语:Synchronization):指对在一个系统中所发生的事件之间进行协调,在时间上出现一致性与统一化的现象。

为什么要线程同步?

当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各个线程之间要有先来后到,不能一窝蜂同时挤上去抢作一团。

线程同步其实就是相对于现实中的“排队”,几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。


线程同步是为了防止多个线程同时访问同一个数据对象时,对数据造成破坏。

线程的同步是保证多线程安全访问资源的一种手段。

示例

例如: 这里模仿有6个人同时对同一个银行账户进行存款操作

代码:

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
/**
*
* 银行账户类
*
*/
public class BankCount {
//账户名称
private String username;
//账户余额
private int countcash;
public BankCount(String username, int countcash) {
super();
this.username = username;
this.countcash = countcash;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getCountcash() {
return countcash;
}
public void setCountcash(int countcash) {
this.countcash = countcash;
}
/**
* 存钱方法
* @param num 存款金额
*/
public void save(int num){
try {
//模拟操作的时间
Thread.sleep(1000);
//改变账户余额
this.countcash+=num;
System.out.println(Thread.currentThread().getName()+"===>>存入"
+num+",当前余额为"+this.countcash);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
*
* 存款的线程
*
*/
public class SaveThread implements Runnable {
//银行账户
private BankCount bankCount;
//存款金额
private int num;
public SaveThread(BankCount bankCount,int num) {
this.bankCount=bankCount;
this.num=num;
}
@Override
public void run() {
//执行存款操作
bankCount.save(this.num);
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Test {
public static void main(String[] args) {
BankCount bankCount = new BankCount("张三",1000);
Thread t1 = new Thread(new SaveThread(bankCount, 100),"线程1");
Thread t2 = new Thread(new SaveThread(bankCount, 600),"线程2");
Thread t3 = new Thread(new SaveThread(bankCount, 500),"线程3");
Thread t4 = new Thread(new SaveThread(bankCount, 100),"线程4");
Thread t5 = new Thread(new SaveThread(bankCount, 1000),"线程5");
Thread t6 = new Thread(new SaveThread(bankCount, 600),"线程6");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
}

运行结果

很显然,上面的结果是错误的,导致错误的原因就是 多个线程 并发 访问了同一个竞争资源而引起的。

如何解决并发问题?

同步和锁

Java为此也提供了2种锁机制,synchronized 和 Lock.

关键字: synchronized

Java中每一个对象都有一个内置锁。

当程序运行到synchronized同步代码块时,自动获得锁定对象的锁。

一个对象只有一个锁。

所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放锁。
这也意味着任何其他线程都不能进入synchronized方法或代码块,直到该锁被释放。

释放锁是指持锁线程退出了synchronized同步方法或代码块。

Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。

Synchronized的作用主要有三个:

(1)确保线程互斥的访问同步代码
(2)保证共享变量的修改能够及时可见
(3)有效解决重排序问题。

从语法上讲,Synchronized总共有三种用法:

(1)修饰普通方法

(2)修饰静态方法

(3)修饰代码块

Java编程语言对 synchronized 提供了两种基本的同步用法: 同步方法 和 同步语句。

—— synchronized 同步方法

Synchronized 修饰一个方法很简单,就是在方法的前面加synchronized 关键字即可

synchronized关键字 放在 修饰符(如:public 等)之后,返回类型声明(如:void等)之前.

使用synchronized的方法(同步方法),即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候, 当前线程(就是在synchronized方法内部的线程)执行完该方法后,别的线程才能进入.

(一) 修饰普通方法

—示例代码(例如:修改之前账户类的存款方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 存钱方法 (同步方法)
* @param num 存款金额
*/
public synchronized void save(int num){
try {
//Thread.sleep(1000);
//改变账户余额
this.countcash+=num;
System.out.println(Thread.currentThread().getName()+"===>>存入"+num+",当前余额为"+this.countcash);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

——- 测试结果 ————————

使用线程同步以后,操作数据就正确了

( 二 ) 修饰静态方法

示例代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class StaticSynchronizedMethod {
public synchronized static void testMethod(String threadName){
System.out.println(threadName+"---调用testMethod方法开始执行.......");
try {
Thread.sleep(2000);
System.out.println(threadName+"正在执行。。。。");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(threadName+"---testMethod方法完毕。。。。。。");
}
}
测试代码
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
public class TestStaticSynchronized {
public static void main(String[] args) {
final StaticSynchronizedMethod oneObj = new StaticSynchronizedMethod();
final StaticSynchronizedMethod twoObj = new StaticSynchronizedMethod();
//开启第一个线程
new Thread(new Runnable() {
public void run() {
//调用 testMethod 方法
oneObj.testMethod(Thread.currentThread().getName());
}
},"线程1").start();
//开启第二个线程
new Thread(new Runnable() {
public void run() {
//调用 testMethod 方法
twoObj.testMethod(Thread.currentThread().getName());
}
},"线程2").start();
}
}

——- 测试结果 ————————

注意

对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象上的方法),所以即使 oneObj 和 twoObj 属于不同的对象,但是它们都属于StaticSynchronizedMethod 类的实例,所以也只能顺序的执行testMethod方法,不能并发执行。

Synchronized 关键字 修饰普通方法 与 修饰静态方法 的区别
  1. synchronized 修饰静态方法是同步某个类的范围

如 : synchronized static void testMethod()方法是防止多个线程中多个实例同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。

  1. synchronized 修饰普通方法是同步某个实例的范围

如:public synchronized void save()方法( –存钱的方法–),防止多个线程中 对 同一个实例 同时访问这个对象的 synchronized 方法。

—— synchronized 同步块

通过 synchronized 关键字来声明 synchronized 块。语法如下

1
2
3
4
5
//syncObject: 同步对象( 可以是类的实例 或 类 )
synchronized (syncObject) {
//允许被同步的代码
}
示例代码
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
public class SynchronizedBlock{
public void method(){
//同步执行代码块
// SynchronizedBlock.class 获得SynchronizedBlock的类的锁
// 类
/* synchronized (SynchronizedBlock.class) {
System.out.println(Thread.currentThread().getName()+"---开始调用---");
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"--正在执行");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---调用完毕---");
}*/
// 类的实例
synchronized (this) {
System.out.println(Thread.currentThread().getName()+"---开始调用---");
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"--正在执行");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---调用完毕---");
}
}
}
测试代码
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
public class TestSynchronizedBlock {
public static void main(String[] args) {
//创建一个对象实例
final SynchronizedBlock obj = new SynchronizedBlock();
//启动线程一
new Thread(new Runnable() {
@Override
public void run() {
obj.method();
}
},"线程1").start();
//启动线程二
new Thread(new Runnable() {
@Override
public void run() {
obj.method();
}
},"线程2").start();
//启动线程三
new Thread(new Runnable() {
@Override
public void run() {
obj.method();
}
},"线程3").start();
//启动线程四
new Thread(new Runnable() {
@Override
public void run() {
obj.method();
}
},"线程4").start();
}
}

——- 测试结果 ————————

synchronized 方法 与 synchronized 代码块的区别

synchronized 方法 与synchronized 代码块 之间 没有什么区别,只是 synchronized 方法便于阅读理解,而synchronized 代码块 可以更精确的控制冲突限制访问区域,有时候表现更高效率。

关于锁:

Lock接口,比Synchronized更强大的锁