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()方法并没有加锁,故这种不上锁的思想是乐观的。