五、synchronized与volatile

(一)synchronized

线程开始运行,拥有自己的栈空间,就如同一个脚本一样,按照既定的代码一步一步地执行,直到终止。但是,每个运行中的线程,如果仅仅是孤立地运行,那么没有一点儿价值,或者说价值很少,如果多个线程能够相互配合完成工作,包括数据之间的共享,协同处理事情。这将会带来巨大的价值。

Java支持多个线程同时访问一个对象或者对象的成员变量,关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性,又称为内置锁机制。

1.1 对象锁

对象锁也就是实例锁

  static int count = 0;

public static void main(String[] args) throws InterruptedException {
    Object lock = new Object();
    Thread thread1 = new Thread(() -> {
        synchronized (lock) {
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    });
    Thread thread2 = new Thread(() -> {
        synchronized (lock) {
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    });
    thread1.start();
    thread2.start();
    thread1.join();
    thread2.join();
    System.out.println(count);
}

1.2 类锁

类锁其实是概念上的一种,并不是真实的一种,类锁其实锁的是类对应的class对象,锁和对象锁之间也是互不干扰的。

static int count = 0;

public static void main(String[] args) throws InterruptedException {
    Thread thread1 = new Thread(() -> {
        setCount();
    });
    Thread thread2 = new Thread(() -> {
        setCount();
    });
    thread1.start();
    thread2.start();
    thread1.join();
    thread2.join();
    System.out.println(count);
}
private synchronized static void setCount() {
    for (int i = 0; i < 10000; i++) {
        count++;
    }
}

1.3 锁失效场景

  1. 多个线程,其中一个线程没有对资源加锁。
static int count = 0;

public static void main(String[] args) throws InterruptedException {
  	//thread1不使用锁
    Thread thread1 = new Thread(() -> {
        for (int i = 0; i < 1000; i++) {
            count++;
        }
    });
    //thread2使用类锁
    Thread thread2 = new Thread(() -> {
        setCount();
    });
    thread1.start();
    thread2.start();
    thread1.join();
    thread2.join();
    System.out.println(count);
}
private synchronized static void setCount() {
    for (int i = 0; i < 10000; i++) {
        count++;
    }
}
  1. 多个线程,使用的锁不是同一把锁。
static int count = 0;

public static void main(String[] args) throws InterruptedException {
    Object lock = new Object();
    //thread1使用对象锁
    Thread thread1 = new Thread(() -> {
        synchronized (lock) {
            for (int i = 0; i < 1000; i++) {
                count++;
            }
        }
    });
    //thread2使用类锁
    Thread thread2 = new Thread(() -> {
        setCount();
    });
    thread1.start();
    thread2.start();
    thread1.join();
    thread2.join();
    System.out.println(count);
}
private synchronized static void setCount() {
    for (int i = 0; i < 10000; i++) {
        count++;
    }
}

1.4 线程安全问题

模拟重复出票问题,未添加锁

static int count = 10;
public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
        setCount();
    });
    Thread t2 = new Thread(() -> {
        setCount();
    });
    t1.start();
    t2.start();
}

private static void setCount() {
    while (count > 0 ){
        try {
            Thread.sleep(50); //模拟业务耗时
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() +"线程获取到了第" + count -- + "张票");
    }
}


(二)volatile

使用于一个线程写,多个线程读的场景,它能够保证每次读取都能够是最新的值,但是不能够保证读取到最新的值使用的过程中不被修改。很可能在使用之后,最新值已经变更。原数据变成过期数据,这时候就会出现数据不一致(非同步)的问题。

排他性:

同一个时间节点,只能有唯一的一个线程能够得到锁,执行程序逻辑,其他线程必须等待该线程的锁释放

可见性

同一个时间节点,允许有多个线程操作共享变量。

线程被调度时,变量的的值会被加载到CPU的缓存中,等到执行完毕之后再重新写入到主内存中,再此期间其他线程修改了变量的值这个线程是不知道的。

如果该变量添加了volatile关键字,一个线程修改了变量的值,其他线程在执行的过程中获取该变量的值是最新的。

但是这并不意味着它是线程安全的,因为缺少排他性即便获取到最新的值,其他线程也能对变量进行操作,这就是为什么volatile并不能保证线程安全的原因

例:主线程修改fail的值,从线程去判断,最终 System.out.println("end");不会执行,因为从线程启动时会把fail 生成一个副本,以后执行的时候只读副本;主线程修改了值从线程并不会知道。

public class Main extends Thread{
    volatile boolean fail = false;

    @Override
    public void run() {
        System.out.println("start");
        while (!fail) {

        }
        System.out.println("end");
    }

    public static void main(String[] args) throws InterruptedException {
        Main main = new Main();
        main.start();
        Thread.sleep(2000);
        main.fail = true;
        System.out.println("main thread end");
    }
}

注:如果在while循环中执行了System.out.println该线程会刷新一次副本,导致该线程会读取到最新的值,因为println方法中执行了synchronized代码块,synchronized有可见性功能。

fail 增加volatile关键字后可直接查看效果。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值