多线程安全与锁

本文详细介绍了Java中的线程安全问题,通过一个卖票任务展示了线程不安全的现象。接着,讨论了解决线程不安全的四种方法:同步代码块、同步方法、显式锁(ReentrantLock)以及公平锁和非公平锁的概念。通过实例代码展示了如何使用这些机制确保多线程环境下的数据一致性。最后,提到了死锁问题并给出了一个死锁的示例。
摘要由CSDN通过智能技术生成

1.线程安全问题

​ 在大多数的实际应用中,两个及以上的线程执行同一任务(共享同一数据的存取)。如果多个线程存取同一个对象,并且每个线程都改变了该对象的状态,这时可能会发生线程相互覆盖,导致数据错乱。

//一个卖票的任务
public class Task implements Runnable {
    int count = 5; //总共五张票
    @Override
    public void run() {
        while(count > 0){
            System.out.println("出票完成!");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count--;
            System.out.println("剩余票数:" + count);
        }
    }
}
public class DemoMultiThread {
    public static void main(String[] args) {
        Task task = new Task();
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
    }
}

运行结果如下:

可以看到,卖出的票数远不止五张有些票卖出了多次,甚至票数为负的情况还在卖票。
在这里插入图片描述
解决线程不安全的方法:

  • 同步代码块
  • 同步方法
  • 显式锁

2.同步代码块

synchronized 关键字,当线程进入如下的代码块时,它会获得obj的锁,此时其他线程不能进入该代码块

synchronized(obj){	//Java任何对象都可作为锁对象传入到obj//code
}
public class DemoMultiThread {
    public static void main(String[] args) {
        Task task = new Task();
        //三个线程同时卖票
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
    }
}

class Task implements Runnable {
    int count = 10; //总票数为10
    Object object = new Object();	//新建一个锁对象
    @Override
    public void run() {
        while(count > 0){
            synchronized (object) {
                if (count > 0) {
                    System.out.println(Thread.currentThread().getName() + "出票完成!");
//                    try {
//                        Thread.sleep(1000);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
                    count--;
                    System.out.println("剩余票数:" + count);
                }
            }
        }
    }
}

从运行结果可知,多线程同时卖票,且票数数据没有发生错乱
在这里插入图片描述

3.同步方法

//同步方法格式
public synchronized void methodName(){
 	// method body   
}

//等价于
public void methodName(){
	this.intrinsicLock.lock();
    try{
        //method body;
    } finally {
        this.intrinsicLock.unlock();
    }
}
  • 从Java1.0开始,每个对象都有一个内部锁,同步方法的锁对象是this (当类有一个静态同步方法时,锁对象为className.class)
public class DemoMultiThread {
    public static void main(String[] args) {
        Task task = new Task();
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
    }
}

class Ticket implements Runnable {
    private int count = 10;
    @Override
    public void run() {
        while(count > 0){
            sellTicket();
        }
    }

    public synchronized void sellTicket(){
        if (count > 0) {
            System.out.println(Thread.currentThread().getName() + "出票完成!");
//                    try {
//                        Thread.sleep(1000);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
            count--;
            System.out.println("剩余票数:" + count);
        }
    }
}

在这里插入图片描述

4.显示锁ReentrantLock

  • 同步代码块和同步方法都属于隐式锁,

  • 显示锁:Lock 子类 ReentrantLock

用ReentrantLock保护代码块的基本结构如下:

myLock.lock();	// a ReentrantLock object
try{
    // critical section
} finally {
    myLock.unlock(); //make sure the lock is unlocked even if an exception is thrown
}

一旦一个线程锁定了锁对象,其它任何线程都无法通过lock语句

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DemoMultiThread {
    public static void main(String[] args) {
        Task task = new Task();
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
    }
}

class Ticket implements Runnable {
    private int count = 10;
    private Lock l = new ReentrantLock(); //显示锁 l
    @Override
    public void run() {
        while (count > 0) {
            l.lock();
            if (count > 0) {
                System.out.println(Thread.currentThread().getName() + "出票完成!");
//                        try {
//                            Thread.sleep(1000);
//                        } catch (InterruptedException e) {
//                            e.printStackTrace();
//                        }
                count--;
                System.out.println("剩余票数:" + count);
            }
            l.unlock();
        }
    }
}

在这里插入图片描述

5.公平锁和非公平锁

  • 公平锁:排队,先来先得
  • 非公平锁:锁一旦解开,所有线程一起抢

当使用显示锁时,

Lock l = new ReentrantLock(true) //传入参数true, l即为公平锁

6.死锁

public class DemoDeadLock {
    public static void main(String[] args) {
        Interviewer p1 = new Interviewer();
        Interviewee p2 = new Interviewee();
        new InterviewThread(p1, p2).start();	// p1.say(p2)
        p2.say(p1);
    }
}

class InterviewThread extends Thread {
    private Interviewer p1;
    private Interviewee p2;

    InterviewThread() {};
    public InterviewThread(Interviewer p1, Interviewee p2) {
        this.p1 = p1;
        this.p2 = p2;
    }

    @Override
    public void run() {
        p1.say(p2);
    }
}

// 面试官
class Interviewer {
    public synchronized void say(Interviewee p2) {
        System.out.println("答出来什么是死锁,就给你发offer");
        p2.reply();
    }
    public synchronized void reply() {
        System.out.println("先回答问题");
    }
}
// 求职者
class Interviewee {
    public synchronized void say(Interviewer p1) {
        System.out.println("给我发offer,就回答什么是死锁");
        p1.reply();
    }
    public synchronized void reply() {
        System.out.println("先发offer");
    }
}

这种情况就有几率出现死锁,运行结果如下:

  • p2.say(p1); 执行完打印输出后,会调用p1的reply方法, (say方法未执行完毕,p2对象(this)此时是锁住的)

  • 而此时new InterviewThread(p1, p2).start(); // p1.say(p2) 线程也正在运行,打印完后等待p2.reply,(say方法未执行完毕,p1对象(this)此时是锁住的)

  • p1、p2都在等待双方的回应,但二者此时都是锁着的,所以产生了死锁
    在这里插入图片描述

也有一定几率是正常的:
当p2.say(p1)执行完毕,而new InterviewThread(p1, p2).start()还未执行时便可以跳出死锁
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值