线程不安全和线程死锁、同步锁Synchronize
什么叫线程不安全?
当多个线程共享同一个资源时,在不使用同步锁的情况下对共享资源进行读写操作是,导致的程序执行结果异常,产生不正常的结果数据。
举个例子:两个人去抢票,当强到最后一张的时,在两个人眼里都看见有一张票,于是两个同时去抢,结果两个人都抢到了,而票的数量变成的-1。
解决线程不安全的办法?
解决线程不安全的办法就是给线程加锁,使用线程同步机制synchronize。线程同步其实就是个等待机制,多个线程访问同一个资源时,就要让这些线程进入一个等待池形成队列,等待前一个线程使用完毕,下一个线程使用。
排队使用机制。
Synchronize同步锁
形成机制:队列 + 锁
当一个线程获取到对象的排它锁时,其他线程必须等待,等该线程结束使用(打开锁)时才能使用。
使用同步锁容易产生的问题?
- 线程必须要等待上一个线程结束才能使用,会导致程序效率问题。
- 在多线程且有多个资源对象锁的使用情况下,可能会产生一个线程占用了另一个进程的待使用资源,而处于一直等待该线程释放锁的状态,如果该线程一直占用锁,那么就会产生线程死锁问题。
- 如果一个优先级高的线程等待一个优先级底的线程释放锁,会影响性能问题(比如很多个简单线程都在等一个复杂线程处理事务结束,就会影响执行效率)。
Synchronize同步锁:同步方法 和 同步代码块
同步方法 默认锁为this(当前对象),任何对象都能成为锁。
private synchronized void buyTicket() {
System.out.println(Thread.currentThread().getName() + ":我抢到了一个票" + ticketnum--);
// 控制线程结束
if (ticketnum <= 0) {
falg = false;
}
}
同步代码块 :一般将共享资源或者需要被操作的资源对象作为锁。
private void buyTicket() {
synchronized (this){ // Obj 代表任何资源对象
System.out.println(Thread.currentThread().getName() + ":我抢到了一个票" + ticketnum--);
// 控制线程结束
if (ticketnum <= 0) {
falg = false;
}
}
}
PS:线程sleep方法是让线程休息等待,不释放锁。wait是让线程进入等待区,释放锁。
什么是线程死锁?
线程死锁其实在上面已经提到过了。多个线程在共享一些共同资源的情况下,且拥有两个以上锁,而都在等待着对方释放锁,导致的线程处于一直等待状态。
举例:我有笔,但是我需要你的纸才能写字,而你需要我的笔才能写字。但是我们都等着对放给自己,导致的一直等待状态。
实现:
// 笔对象
class Pen{}
// 纸对象
class Paper{}
// 写字
class Write implements Runnable{
static Pen pen = new Pen();
static Paper paper = new Paper();
int flag;
String name;
Write(int flag,String name){
this.flag = flag;
this.name = name;
}
@Override
public void run() {
try {
writeWork();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void writeWork() throws InterruptedException {
// 当flag 0时获取笔
if(flag==0){
synchronized (pen){
System.out.println(name+":拿到了笔");
Thread.sleep(2000);
// 然后去拿纸的锁
synchronized (paper){
System.out.println(name+":拿到了纸");
}
}
}else {
synchronized (paper){
System.out.println(name+":拿到了纸");
Thread.sleep(2000);
// 然后去拿纸的锁
synchronized (pen){
System.out.println(name+":拿到了笔");
}
}
}
}
}
/**
* Created by 一只会飞的猪 on 2021/3/8.
* 实现线程死锁现象,写字 = 笔 + 纸
*/
public class DeadLockThread {
public static void main(String[] args) {
new Thread(new Write(0,"小美")).start();
new Thread(new Write(1,"小丑")).start();
}
}
结果:小美和小丑都拿着自己的锁,等终身。
怎么解决死锁呢?
解决死锁没有什么好的办法,一般都是根据业务需求,准备的对对象进行锁和释放锁处理。比如上例。
我们只要让两个人拿到一个资源之后释放锁,再去取另一个资源就行。
public void writeWork() throws InterruptedException {
// 当flag 0时获取笔
if(flag==0){
synchronized (pen){
System.out.println(name+":拿到了笔");
Thread.sleep(2000);
}
// 然后去拿纸的锁
synchronized (paper){
System.out.println(name+":拿到了纸");
}
}else {
synchronized (paper){
System.out.println(name+":拿到了纸");
Thread.sleep(2000);
}
// 然后去拿纸的锁
synchronized (pen){
System.out.println(name+":拿到了笔");
}
}
}
结果:两个人都能愉快。
PS: Synchronize同步锁(隐式同步),在实行完作用域之后,会自动释放锁。而Lock锁(下片),需要主动释放锁(显示同步)