首先来看下一个场景,某电影院某个时间4个窗口同时在卖票,本场电影总共票只有100张,卖完为止。看下实际代码。
package cn.com.thread;
public class TestThread {
public static void main(String[] args) {
SellTicketThread t=new SellTicketThread();
new Thread(t,"窗口1").start();
new Thread(t,"窗口2").start();
new Thread(t,"窗口3").start();
new Thread(t,"窗口4").start();
}
}
package cn.com.thread;
public class SellTicketThread extends Thread {
private int ticket = 100;
@Override
public void run() {
while (true) {
if(sellTicket()){
break;
}
}
}
private boolean sellTicket() {
try {
if (ticket <= 0) {
return true;
}
Thread.sleep(30);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName()+":"+(ticket--));
return false;
}
}
从运行结果来看,发现出现了负数。在实际业务中是不可以的。
从而我们得出一个结论:多个线程访问一个对象中的实例变量,可能会出现`非线程安全`。
synchronized方法
如果在方法上加关键字synchronized,那么就不会出现上面的那个问题。
package cn.com.thread;
public class SellTicketThread extends Thread {
private int ticket = 100;
@Override
public void run() {
while (true) {
if(sellTicket()){
break;
}
}
}
private synchronized boolean sellTicket() {
try {
if (ticket <= 0) {
return true;
}
Thread.sleep(30);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName()+":"+(ticket--));
return false;
}
}
看到这里有些人就要问了,既然加了锁,我们的代码就只能被一个线程调用,这样岂不是降低了效率,在同步代码的部分并没有多线程并发的情况出现呀?如果你能想到这一点,就说明你对锁的机制了解的差不多了,的确,情况的确如此,因为我们要尽量的缩小同步锁的范围,有什么原则么?所以有时间同步方法并不适合。
synchronized代码块
假如在卖票之前,我们还要去做相关的事。比如我们在美团买了一张票,可能在电影院的系统中,需要去效验下,是不是你在美团买了票,在决定是否出票。这个过程相当耗时,而每个人都是单独的对象去访问,所以是线程安全,但是我们要是用synchronized方法,是不适合的。
package cn.com.thread;
public class SellTicketThread extends Thread {
private int ticket = 100;
private Object lock=new Object();
@Override
public void run() {
while (true) {
if (sellTicket()) {
break;
}
}
}
private boolean sellTicket() {
System.out.println("效验逻辑,耗时10秒");
try {
synchronized (lock) {
if (ticket <= 0) {
return true;
}
System.out.println(Thread.currentThread().getName() + ":"+ (ticket--));
}
Thread.sleep(30);
} catch (Exception e) {
}
return false;
}
}
从上面的例子我们可以看出,这样也是达到我们要的结果。那么我们不禁要问该如何定义一个锁?
- 所谓加锁,就是为了防止多个线程同时操作一份数据,如果多个线程操作的数据都是各自的,那么就没有加锁的必要
- 共享数据的锁对于访问他们的线程来说必须是同一份,否则锁只能私有的锁,各锁个的,起不到保护共享数据的目的,试想一下将 Object lock 的定义放到 run 方法里面,每次都会实例化一个 lock,每个线程获取的锁都是不一样的,也就没有争抢可言,说的在通俗一点甲楼有一个门上了锁,A 要进门,乙楼有一个门上了锁 B 要进门,A 和 B 抢的不是一个门,因此不存在数据保护或者共享;
- 锁的定义可以是任意的一个对象,该对象可以不参与任何运算,只要保证在访问的多个线程看来他是唯一的即可;