【多线程 - 08、线程同步1 synchronized】

一、什么情况下会产生线程安全问题

同时满足以下两个条件时:

  • 多个线程在操作共享的数据
  • 操作共享数据的线程代码有多条

即:当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生

例子:四个线程卖 100 张票(线程不安全):

public class ThreadTest {
    public static void main(String[] args) {
        synchronizeThread st = new synchronizeThread();
        new Thread(st,"1").start();
        new Thread(st,"2").start();
        new Thread(st,"3").start();
        new Thread(st,"4").start();
    }
}
class synchronizeThread implements Runnable {
    private int ticketNumber = 100;
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (ticketNumber > 0) {
                System.out.println("线程【" + Thread.currentThread().getName() + "】卖出了一张票,现在剩余了【" + ticketNumber + "】张票");
                ticketNumber--;
            } else {
                break;
            }
        }
    }
}

运行结果:发现会有多个线程卖同一张票的情况发生,这就是线程安全问题

解决这样的问题就是线程同步的方式来实现

二、线程同步

同步就是协同步调,按预定的先后次序进行运行。这里的同步不要理解成同时进行,应是指协同、协助、互相配合。线程同步是指多线程通过特定的设置来控制线程之间的执行顺序(即所谓的同步)也可以说是在线程之间通过同步建立起执行顺序的关系,如果没有同步,那线程之间是各自运行各自的

三、synchronized 关键字

1、特点

  1. 可以修饰类、方法、代码块
  2. 可以保证变量的可见性和原子性
  3. 会造成线程的阻塞
  4. 隐式的加锁
  5. 发生异常时会自动释放占有的锁,因此不会出现死锁
  6. 可重入锁、不可中断锁、非公平锁
  7. 锁的是对象,锁信息保存在对象头中
  8. 是 JVM 层次通过监视器实现的

2、同步方法

即用 synchronized 关键字修饰方法

由于 Java 的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态

编程:

public synchronized void save(){}

3、同步代码块

即用 synchronized 关键字修饰语句块

被该关键字修饰的语句块会自动被加上内置锁,被保护的语句代码所在的线程要执行,需要获得内置锁,否则就处于阻塞状态

编程:

synchronized(object){ 
}

括号里的这个对象可以是任意对象,这个对象一般称为同步锁

4、同步方法和同步块的选择

同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用 synchronized 代码块同步关键代码即可

5、例子:四个线程卖 100 张票(线程安全)

package cn.hyl.test1;

public class ThreadTest {
    public static void main(String[] args) {
        synchronizeThread st = new synchronizeThread();
        new Thread(st,"1").start();
        new Thread(st,"2").start();
        new Thread(st,"3").start();
        new Thread(st,"4").start();
    }
}
class synchronizeThread implements Runnable {
    private Integer ticketNumber = 100;
    private Object object = new Object();
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
           synchronized (ticketNumber){
               if (ticketNumber > 0) {
                   System.out.println("线程【" + Thread.currentThread().getName() + "】卖出了一张票,现在剩余了【" + ticketNumber + "】张票");
                   ticketNumber--;
               } else {
                   break;
               }
           }
        }
    }
}

6、锁对象

  • 对于普通同步方法,锁是当前实例对象
  • 对于静态同步方法,锁是当前类的Class对象
  • 对于同步方法块,锁是 Synchonized 括号里配置的对象

7、原理

底层实现:
synchronized 是由一对 monitorenter/monitorexit 指令实现的,monitor 对象是同步的基本实现单元。在 Java 6 之前,monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作,性能也很低。但在 Java 6 的时候,Java 虚拟机 对此进行了改进,提供了三种不同的 monitor 实现,也就是常说的三种不同的锁:偏向锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能

锁升级的原理:
在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

玄天灵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值