线程安全/线程锁
线程安全问题出现在并发的环境下,
多个线程访问同一个资源,可能会出现线程安全问题

1.什么是线程安全:
在多线程访问和使用共享资源的情况下,线程在运行的时候可能被打断,变为阻塞状态,失去执行权。
例如i ++,i++不是原子操作 ! 其执行要分为3步: 1、读内存到寄存器; 2、在寄存器中自增; 3、写回内存。
因为不是原子操作,所以i可以被打断。

ps“补充:原子性、可见性、指令重排——线程安全问题围绕这三点。


如何解决:加同步锁
2.如何使用同步锁(互斥锁)
1》同步代码块
格式:synchronized(监视器/锁){需要同步的内容}
监视器:JAVA中任何一个对象都是一个监视器,也就是锁、
要保证互斥性,所有的线程必须使用同一个监视器。(即线程对自己去加锁没意义)
因此,在以继承父类作为方法时,使用object 和this均无法锁定
代码实例:售票系统
首先需要一个ticket类
//售票系统
public class Ticket {
int tick=100;//总得票数
}
然后是User类
public class User extends Thread{
Ticket tk;
Object lock =new Object();
public User(Ticket tk) {
this.tk = tk;
}
@Override
public void run() {
//,每个人买50张票
for (int i = 0; i <50 ; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// ticket();
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + ":线程买了" + tk.tick + "号票");
tk.tick--;
// }
}
}
// public synchronized void ticket(){
// System.out.println(Thread.currentThread().getName() + ":线程买了" + tk.tick + "号票");
// tk.tick--;
}
}
Test类
public class Test {
public static void main(String[] args) {
Ticket tk =new Ticket();
User user =new User(tk);
User user2 =new User(tk);
user.setName("用户A");
user2.setName("用户B");
// new Thread(user,"用户A").start();
// new Thread(user,"用户B").start();
user.start();
user2.start();
}
}
2》同步方法:对象锁(this)
将关键字直接放在方法上
public synchronized void ticket(){}
锁的东西就是this
代码示例 银行取钱1.0
//银行账户类
public class Account {
private int balance;
public Account(int balance) {
this.balance = balance;
}
public int getBalance() {
return balance;
}
public synchronized void add(int num) {
balance = balance + num;
}
public synchronized void withdraw(int num) {
balance = balance - num;
}
}
//存钱线程
public class AddThread implements Runnable{
Account account;
int amount;
public AddThread(Account account, int amount) {
this.account = account;
this.amount = amount;
}
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
account.add(amount);
System.out.println(Thread.currentThread().getName()+"本次存"+amount+"元后余额为"+account.getBalance());
}
}
}
//取钱线程
public class WithdrawThread implements Runnable {
Account account;
int amount;
public WithdrawThread(Account account, int amount) {
this.account = account;
this.amount = amount;
}
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 200; i++) {
if (account.getBalance()>0) {
account.withdraw(amount);
System.out.println(Thread.currentThread().getName() + "本次取" + amount + "元后余额为" + account.getBalance());
}else {
System.out.println("余额不足");
break;}
}
}
}
//主函数类
public class Test {
public static void main(String[] args){
Account account = new Account(1000);
Thread a = new Thread(new AddThread(account, 20), "add");
Thread b = new Thread(new WithdrawThread(account, 20), "withdraw");
a.start();
b.start();
// //join?
// a.join();
// b.join();
System.out.println("账户余额为"+account.getBalance());
}
}
3.Lock(接口)
作用与synchronized一样
结合实现类使用 ReentrantLock
加锁和释放锁都是自己控制的
代码实例 银行取钱2.0
import java.util.concurrent.locks.ReentrantLock;
public class AddThread implements Runnable{
Account account;
int amount;
ReentrantLock lock = new ReentrantLock();
public AddThread(Account account, int amount) {
this.account = account;
this.amount = amount;
}
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
lock.lock();
System.out.println("存钱进程锁");
for (int i = 0; i < 100; i++) {
account.add(amount);
System.out.println(Thread.currentThread().getName() + "本次存" + amount + "元后余额为" + account.getBalance());
}
} //catch(InterruptedException e){e.printStackTrace();}
finally {
lock.unlock();
System.out.println("存钱进程解锁");
}
}
}
import java.util.concurrent.locks.ReentrantLock;
public class WithdrawThread implements Runnable {
Account account;
int amount;
ReentrantLock lock= new ReentrantLock();;
public WithdrawThread(Account account, int amount) {
this.account = account;
this.amount = amount;
}
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
lock.lock();
System.out.println("取钱进程上锁");
for (int i = 0; i < 200; i++) {
if (account.getBalance() > 0) {
account.withdraw(amount);
System.out.println(Thread.currentThread().getName() + "本次取" + amount + "元后余额为" + account.getBalance());
} else {
System.out.println("余额不足");
break;
}
}
}
finally {
lock.unlock();
System.out.println("取钱进程解锁");
}
}
}
这里放存钱和取钱线程的代码更改。Test和Account类不变。
补充一些问题
1.同步方法和同步代码块的区别是什么?
同步方法默认用this或者当前类class对象作为锁;
ps:当前都会有一个不需要申明的默认class对象,一半用于作为同步方法的锁。
同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法;
同步方法使用关键字
synchronized修饰方法,而同步代码块主要是修饰需要进行同步的代码,用 synchronized(object){代码内容}进行修饰;
2.什么是死锁(deadlock)?
所谓死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。死锁产生的4个必要条件:
互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某
资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能
由获得该资源的进程自己来释放(只能是主动释放)。
请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源
已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被
链中下一个进程所请求。我自己的理解是类似于需要多到工序做饭的过程。首先资源(食材)一次智能被一个锅煮,这个锅煮了,下一个锅就得等着。在请求别的锅里的食材联动的时候,别的锅没好,这边也只能等着,同时占用锅和菜。
循环链则是接近于流水线。
3.如何确保N个线程可以访问N个资源的同时又不导致死锁?
指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。
本文介绍了Java中的线程安全问题,包括线程安全的定义、如何使用同步锁(同步代码块和同步方法)以及Lock接口的ReentrantLock实现。文章还探讨了死锁的概念及其四个必要条件,并提出了防止死锁的一种策略——指定获取锁的顺序。
447

被折叠的 条评论
为什么被折叠?



