java的线程安全

76 阅读2分钟

1.说到线程安全,我们可以举例子,假设一个餐厅里面有30份饭,那么可以写一个类如下:

public class Restaurant {
    private int food = 30;
    //这里加上了同步锁,为的就是同一时刻只能让一个线程执行方法.
    public synchronized void sell(){
        if(food > 0){
        food--;
        }
        //注意这里的Thread.currentThread().getName()指的就是当前执行方法的线程。
        System.out.println(Thread.currentThread().getName() + "购买了一份食物,还剩:" + getFood());
    }
    public int getFood() {
        return food;
    }
}

2.既然有了一个餐厅,那么肯定有很多个学生来餐厅买饭,代码:

public class Student implements Runnable{
    //之所以要创建一个restaurant变量是为了调用sell()方法。
    private Restaurant restaurant;
    public Student(Restaurant restaurant) {
        this.restaurant = restaurant;
    }
    @Override
    public void run() {
        while (restaurant.getFood() > 0) {
            try {
                Thread.sleep(200);
                restaurant.sell();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

3.接下来就得运行线程类了,直接创建主方法:

public class Main extends Thread {
    public static void main(String[] args) {
        //实例化一个restaurant变量是为了在student的实例化对象里面放入共同的变量,即有一个共同的food变量来并发。
        Restaurant restaurant = new Restaurant();
        for (int i = 0; i < 4; i++) {
            Student student = new Student(restaurant);
            Thread thread = new Thread(student);
            thread.setName("学生" + i);
            thread.start();
        }
    }
}

加入了synchronized锁则就意味着满足了两个性质,为:

可见性:当多个线程访问同一个变量时,一个线程改变了这个变量,那么其他的线程都能立刻看得见。, 原子性:方法执行过程中不会被其他的任何因素打断

但是上了锁之后的效率明显就降低了,因为其他很多线程都是在等待中的,未响应的。所以上锁主要适用于写操作的场景,例如用户修改个人信息,下单,点赞等等;而且由于消耗效率,所以尽量锁住最小的代码块,不建议给大段的方法上锁。

4.上面的可以看出来加了锁,这是悲观的,所以我们也可以不上锁,不上锁的思想则就是乐观的。所以我们可以直接修改Restaurant类就可以得到代码如下:

public class Restaurant {
    private AtomicInteger food = new AtomicInteger(30);

    public void sell(){
        int newFood = 0;
        if (food.get() > 0){
             newFood = food.getAndDecrement();
        }
        System.out.println(Thread.currentThread().getName() + "购买了一份食物,还剩:" + newFood);
    }

    public int getFood() {
        return food.get();
    }
}

上面的我们的代码运行后会发现顺序比较乱,但是不会出现食物重复,而且查看源码也会发现getAndDecrement()方法并没有加锁,故这种不上锁的思想是乐观的。