同步问题引出
问题概况:售票系统中只能售出5张票,有三个线程在刷票,每个线程刷票的次数是100次:
class MyThread implements Runnable {
private int ticket = 5;
public void run() {
for(int i = 0; i < 100; i++) {
if(ticket > 0) {
try {
Thread.sleep(300);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("卖票: ticket = " + ticket--);
}
}
}
}
public class SyncDemo {
public static void main(String[] args) {
MyThread mt = new MyThread();
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
Thread t3 = new Thread(mt);
t1.start();
t2.start();
t3.start();
}
}
程序运行的结果:
卖票: ticket = 4
卖票: ticket = 5
卖票: ticket = 4
卖票: ticket = 3
卖票: ticket = 2
卖票: ticket = 1
卖票: ticket = 0
卖票: ticket = -1
同步问题分析
总结操作代码中对票数的操作如下:
- 判断票数是否大于0,大于0则表示还有票可以卖;
- 如果票大于0,则将票卖出。
但是,步骤一跟步骤二之间假如了延迟操作,那么一个线程就有可能还没有对票数进行减操作之前,其他县城就已经将票数减少,就会出现负数的情况。前面已经提及过通过Runnable接口实现程序的多线程,可以在线程间共享类属资源,则意味着类中的属性将被多个线程共享,这样就会造成一个问题,如果多个线程要操作同一资源就有可能出现资源同步的问题。
所谓同步,就是指多个操作之间在同一个时间段内只能有一个线程进行,其他线程要等待次线程完成之后才可以继续执行。
JAVA中实现同步的方法有两种:
- 同步代码块
synchronized(同步对象) {
需要同步的代码;
}
- 同步方法
synchronized 方法返回值 方法名称(参数列表){
}
同步问题解决
使用同步代码块解决同步问题
class MyThread implements Runnable {
private int ticket = 5;
public void run() {
for(int i = 0; i < 100; i++) {
synchronized (this) { // 同步代码块
if(ticket > 0) {
try {
Thread.sleep(300);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("卖票: ticket = " + ticket--);
}
}
}
}
}
使用同步方法解决同步问题
class MyThread implements Runnable {
private int ticket = 5;
public void run() {
for(int i = 0; i < 100; i++) {
this.sale();
}
}
public synchronized void sale() {
if(ticket > 0) {
try {
Thread.sleep(300);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("卖票: ticket = " + ticket--);
}
}
}
死锁问题
同步可以保证资源共享操作的正确性,但是过多的同步有可能是导致死锁的,造成程序的停滞。关于死锁的概念,可以想象一下如下情景:
- 张三想要李四的画,李四想要张三的书;
- 张三 --> 李四 : 你给我画,我给你书;
- 李四 --> 张三 : 你给我书,我给你画;
- 张三跟李四互不想让,就造成了两人一直干等下去。
代码的实现:
class Zhangsan {
public void say() {
System.out.println("张三 --> 李四 : 你给我画,我给你书.");
}
public void get() {
System.out.println("张三:得到了画!");
}
}
class Lisi {
public void say() {
System.out.println("李四 --> 张三 : 你给我书,我给你画。");
}
public void get() {
System.out.println("李四:得到了书!");
}
}
public class ThreadDeadLock implements Runnable {
private static Zhangsan zs = new Zhangsan(); //同步对象
private static Lisi ls = new Lisi(); //同步对象
private boolean flag = false;
public void run() {
if(flag) { // thA 执行的代码
synchronized (zs) { //获得"张三"同步锁
zs.say();
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (ls) { // 需获得"李四"才能往下执行,此时"李四"被thB获得,而thB释放"李四" zs.get(); // 条件是获得"张三",因为此时"张三"为thA所有,造成了两线程的死锁
}
}
} else {
synchronized (ls) { //thB 执行的代码,获得"李四"同步锁
ls.say();
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (zs) {
ls.get();
}
}
}
}
public static void main(String[] args) {
ThreadDeadLock t1 = new ThreadDeadLock();
ThreadDeadLock t2 = new ThreadDeadLock();
t1.flag = true;
t2.flag = false;
Thread thA = new Thread(t1, "thA");
Thread thB = new Thread(t2, "thB");
System.out.println(thA.getName() + " flag is " + t1.flag);
System.out.println(thB.getName() + " flag is " + t2.flag);
thA.start();
thB.start();
}
}
运行结果
thA flag is true
thB flag is false
张三 --> 李四 : 你给我画,我给你书。
李四 --> 张三 : 你给我书,我给你画。
思考:
如何修改以上程序来解决死锁问题?
总结
通过Runnable()接口来实现多线程程序,由于存在线程间资源间的共享问题,同步是用来解决不同线程对共享资源操作的完整性,但是过多的同步就有可能产生死锁,应该要尽力避免程序死锁的发生。
BR~
Jianwei Wang