【Java并发编程 安全】10.线程安全概念和synchronized用法

什么是线程安全?

当多个线程同时对同一资源访问,并且写的时候,可能会受到其它线程干扰,导致数据问题,这种现象被称之为线程安全问题。(多线程读的时候不会产生线程安全问题)

线程不安全例子

/**
* 线程不安全例子
* @author terry
* @date 2018年5月26日
*/
public class ThreadUnsafe {
 
    //导致线程安全
    public static void main(String[] args) {
        SellTicket ticket = new SellTicket();
        for (int i = 0;i<50;i++) {
            BuyTicket buy = new BuyTicket(ticket);
            buy.start();
        }
    }
 
 
}
/**
* 窗口卖票
*/
class SellTicket{
 
    public int count = 100;//
 
    public void doSell(){
        count--;
    }
}
/**
* 黄牛买票
*/
class BuyTicket extends Thread{
 
    SellTicket ticket;
 
    public BuyTicket(SellTicket ticket) {
        this.ticket = ticket;
    }
 
    @Override
    public void run() {
        while(ticket.count > 0){
            System.out.println("黄牛【"+getName()+"】买了一张票,当前票数:"+ticket.count);
            ticket.doSell();
        }
    }
 
}

可以看到下面多处数据问题,概率还是很大的

黄牛【Thread-0】买了一张票,当前票数:100
黄牛【Thread-3】买了一张票,当前票数:100
黄牛【Thread-1】买了一张票,当前票数:100
黄牛【Thread-2】买了一张票,当前票数:100
黄牛【Thread-4】买了一张票,当前票数:98
黄牛【Thread-4】买了一张票,当前票数:96
黄牛【Thread-4】买了一张票,当前票数:95
黄牛【Thread-4】买了一张票,当前票数:94
黄牛【Thread-4】买了一张票,当前票数:93
黄牛【Thread-4】买了一张票,当前票数:92
黄牛【Thread-4】买了一张票,当前票数:91
黄牛【Thread-4】买了一张票,当前票数:90
黄牛【Thread-4】买了一张票,当前票数:89
黄牛【Thread-5】买了一张票,当前票数:89

线程如何同步,保证数据的原子性。
解决方案:

  • synchronized 特点:自动挡

  • lock jdk1.5(并发包中) 特点:手动锁(需要手动开启和关闭)


使用synchronized解决线程安全问题

public class ThreadSyn {
 
    //导致线程安全
    public static void main(String[] args) {
        SellTicket ticket = new SellTicket();
        for (int i = 0;i<5;i++) {
            BuyTicket buy = new BuyTicket(ticket);
            buy.start();
        }
    }
 
}
/**
* 窗口卖票
*/
class SellTicket{
 
    public int count = 100;//
 
}
/**
* 黄牛买票
*/
class BuyTicket extends Thread{
 
    SellTicket ticket;
 
    public BuyTicket(SellTicket ticket) {
        this.ticket = ticket;
    }
 
    @Override
    public void run() {
        while(ticket.count > 0){
            doSell();
        }
    }
 
 
    //写法一:同步代码块
    public void doSell(){
        synchronized (ticket) {//只要有线程持有该把锁,其它线程就不会进来
            if (ticket.count <= 0) {
                return;
            }
            System.out.println("黄牛【"+getName()+"】买了一张票,当前票数:"+ticket.count);
            ticket.count--;
        }
    }
 
    //写法二:同步函数,一般推荐同步代码块,锁住的区域越多,效率越慢(相当于单线程了)
    public synchronized void doSell1(){//当前写法存在线程安全问题,稍后解释
        if (ticket.count <= 0) {
            return;
        }
        System.out.println("黄牛【"+getName()+"】买了一张票,当前票数:"+ticket.count);
        ticket.count--;
    }
}

同步代码块执行结果:

黄牛【Thread-4】买了一张票,当前票数:11
黄牛【Thread-4】买了一张票,当前票数:10
黄牛【Thread-4】买了一张票,当前票数:9
黄牛【Thread-4】买了一张票,当前票数:8
黄牛【Thread-4】买了一张票,当前票数:7
黄牛【Thread-4】买了一张票,当前票数:6
黄牛【Thread-4】买了一张票,当前票数:5
黄牛【Thread-4】买了一张票,当前票数:4
黄牛【Thread-4】买了一张票,当前票数:3
黄牛【Thread-4】买了一张票,当前票数:2
黄牛【Thread-4】买了一张票,当前票数:1

改成调用同步函数:

黄牛【Thread-4】买了一张票,当前票数:3
黄牛【Thread-4】买了一张票,当前票数:2
黄牛【Thread-4】买了一张票,当前票数:1
黄牛【Thread-0】买了一张票,当前票数:21
黄牛【Thread-2】买了一张票,当前票数:22
黄牛【Thread-3】买了一张票,当前票数:7
黄牛【Thread-1】买了一张票,当前票数:13

可以看到出现线程问题了,注意:同步函数的锁对象是this,每次线程进行判断的时候拿的不是同一把锁。
解决方法:

  • 方案一:只创建一个BuyTicket对象,那么this对象就是同一把锁了。
public class ThreadSyn {
 
    //导致线程安全
    public static void main(String[] args) {
        SellTicket ticket = new SellTicket();
        BuyTicket buy = new BuyTicket(ticket);
        for (int i = 0;i<5;i++) {
            Thread thread = new Thread(buy);
            thread.start();
        }
    }
 
 
}
  • 方案二:使用静态同步函数,那么锁对象就是class文件了
public class ThreadSyn {
 
    //导致线程安全
    public static void main(String[] args) {
        SellTicket ticket = new SellTicket();
        for (int i = 0;i<5;i++) {
            BuyTicket buy = new BuyTicket(ticket);
            buy.start();
        }
    }
 
 
}
/**
* 窗口卖票
*/
class SellTicket{
 
    public int count = 100;//
 
}
/**
* 黄牛买票
*/
class BuyTicket extends Thread{
 
    static SellTicket ticket;
 
    public BuyTicket(SellTicket ticket) {
        this.ticket = ticket;
    }
 
    @Override
    public void run() {
        while(ticket.count > 0){
            doSell1();
        }
    }
 
 
    public void doSell(){
        synchronized (ticket) {//只要有线程持有该把锁,其它线程就不会进来
            if (ticket.count <= 0) {
                return;
            }
            System.out.println("黄牛【"+getName()+"】买了一张票,当前票数:"+ticket.count);
            ticket.count--;
        }
    }
    public synchronized static void doSell1(){
        if (ticket.count <= 0) {
            return;
        }
        System.out.println("黄牛【"+Thread.currentThread().getName()+"】买了一张票,当前票数:"+ticket.count);
        ticket.count--;
    }
 
}

注意细节:

  1. 有两条线程或以上的线程,可能会产生线程安全的地方。
  2. 线程必须持有同一把锁(比如:class,局部变量,或者线程拿到的一样值。。),才能保证同步块一条线程执行
  3. 锁对象:可以为任意对象(必须保证唯一)
  4. 线程调用sleep方法,是不会释放锁对象的

原理:

  1. 当一个线程拿到锁后,其它线程就要等待该线程执行完毕后再执行。
  2. 锁释放,当代码执行完毕或者程序抛出异常,都会释放锁。

**缺点:**加锁效率会变慢

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

terrybg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值