多线程读取一个静态资源是不安全的
模拟一个卖票程序,有100张票,3个窗口在卖。
卖票程序:
public class ticketSell implements Runnable {
static int num = 100;
@Override
public void run() {
while (num > 0) {
System.out.println(Thread.currentThread().getName() + ": 还剩" + num-- + "张票");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
主函数:
public class test {
public static void main(String[] args) {
ticketSell ts=new ticketSell();
Thread ck1 = new Thread(ts, "窗口1");
Thread ck2 = new Thread(ts, "窗口2");
Thread ck3 = new Thread(ts, "窗口3");
ck1.start();
ck2.start();
ck3.start();
}
}
结果为:
窗口3: 还剩98张票
窗口2: 还剩99张票
窗口1: 还剩100张票
窗口3: 还剩97张票
窗口2: 还剩97张票
窗口1: 还剩97张票
窗口3: 还剩96张票
窗口2: 还剩95张票
窗口1: 还剩94张票
窗口1: 还剩93张票
窗口2: 还剩93张票
窗口3: 还剩93张票
窗口2: 还剩92张票
窗口1: 还剩92张票
窗口3: 还剩92张票
窗口2: 还剩91张票
窗口3: 还剩91张票
窗口1: 还剩91张票
窗口2: 还剩90张票
窗口1: 还剩90张票
窗口3: 还剩90张票
窗口1: 还剩89张票
窗口2: 还剩89张票
窗口3: 还剩89张票
窗口1: 还剩88张票
窗口3: 还剩88张票
窗口2: 还剩88张票
窗口2: 还剩87张票
窗口3: 还剩87张票
窗口1: 还剩87张票
窗口1: 还剩86张票
窗口2: 还剩86张票
窗口3: 还剩85张票
窗口3: 还剩84张票
窗口1: 还剩84张票
窗口2: 还剩83张票
窗口2: 还剩82张票
窗口3: 还剩82张票
窗口1: 还剩82张票
窗口2: 还剩81张票
窗口3: 还剩80张票
窗口1: 还剩81张票
窗口2: 还剩79张票
窗口1: 还剩78张票
窗口3: 还剩78张票
窗口3: 还剩77张票
窗口2: 还剩77张票
窗口1: 还剩77张票
窗口1: 还剩76张票
窗口3: 还剩76张票
窗口2: 还剩75张票
窗口2: 还剩73张票
窗口3: 还剩74张票
···
输出结果总票数变大,静态变量读取错误,3个线程的执行循序出现错乱,这是由于cpu给线程分配时间片时,类实例完成了票数修改但是还未完成语句输出,线程就被阻塞了。
此时我们应该引入同步的概念,同步是多线程在资源共享的程序中,单一线程对在代码未执行完之前对代码有锁的操作。
*同步 synchronized *
synchronized关键字可以用于锁定代码块,表面在本块代码执行时,当前线程有唯一操控权。
用法为
卖票代码变为:
synchronized (某对象) {代码块}
这里需要声明任意对象,将对象或者this传入synchronized作为参数,每次执行时,synchronized将对象作为锁标记。其他线程使用时会查询锁标记。
public class ticketSell implements Runnable {
static int num = 100;
Object obj = new Object();
@Override
public void run() {
synchronized (obj) {
while (num > 0) {
System.out.println(Thread.currentThread().getName() + ": 还剩" + num-- + "张票");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
结果就不会有错:
窗口1: 还剩100张票
窗口1: 还剩99张票
窗口1: 还剩98张票
窗口1: 还剩97张票
窗口1: 还剩96张票
窗口1: 还剩95张票
窗口1: 还剩94张票
窗口1: 还剩93张票
窗口1: 还剩92张票
窗口1: 还剩91张票
窗口1: 还剩90张票
窗口1: 还剩89张票
窗口1: 还剩88张票
窗口1: 还剩87张票
窗口1: 还剩86张票
窗口1: 还剩85张票
···
解释一下这里为什么全是窗口一在执行,这是因为窗口一抢到了资源并加锁,执行过程中别的线程是无法操作的。其他窗口唯一可以抢到操作权的机会是在窗口一执行结束,如果此时线程一的时间片分配正好结束,那其他线程是可以插进来的。不过这种概率微乎其微。
改进:
只做锁住该锁的代码:
@Override
public void run() {
while (num > 0) {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + ": 还剩" + num-- + "张票");
}
}
}
这样就不会出现只被一个窗口锁定的情况:
结论,使用锁时,应在满足要求的情况下,尽量减小锁定范围。