多线程的安全性问题
多线程的安全性问题其实就是数据不一致的问题。类似取钱过程。银行卡中有100块,A正在用卡取出50块(取钱过程比较慢,还没有取完,账户上的余额显示还没有修改)。此时B也正好在用存折想取出100块。A,B如果都成功了,银行不就亏本了?这种数据不一致的问题银行不允许出现!
怎么解决呢?
可以规定在取钱过程中只允许一个人操作(加锁)。但是会存在问题:如果这个人待了1h,其他人都不能操作,效率低。
(见上机练习)无锁机制–比较交换 CAS(Compare and Swap)。里面设置了三个值,VEN。
V表示准备要被更新的变量 实际值
E表示我们提供的 期望的值 期望值
N表示新值 ,准备更新V的值 改变值每次当E == V时,才能发生改变操作,将N写入V中。
如果 E != V,我们要准备一个新的E去跟V比较,成功后才能更新V。
使用同步解决多线程的安全问题
同步的前提:
- 必须是两个及以上的线程使用同一资源
- 必须保证同步中只能有一个线程在运行
卖票案例中,无论是,都存在数据不一致的问题。解决方法都是线程同步(加锁)。
例子在GitHub-Day07-thread的ticket包中。多线程——线程与进程,线程的实现方式文中也有相关描述。
实现方式
同步代码块
Synchronized(共享资源,共享对象,需要是object的子类) {具体执行的代码块}
()中的obj称为同步监视器1
2
3
4
5
6
7
8
9
10
11
12
13
14public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this) { // this 表示当前对象
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
}
}
}
}同步方法
将核心代码逻辑定义成一个方法,使用synchronized关键字修饰
不需要自己指定锁对象(同步监视器),因为同步方法的监视器是this,也就是该对象本身1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20private int ticket = 5; // 一共5张票
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
/** this的意思是sale()已经属于TicketRunnable3这个对象了,而不归于整个类了
* this代表的就是synchronized的锁的对象
*/
this.sale();
}
}
public synchronized void sale() {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票");
}
}
死锁
- 同步可以保证资源共享操作的正确性,但过多同步可能产生死锁
- 死锁一般情况下表示互相等待