一、同步
当多个线程访问同一资源的时候一定需要考虑到同步问题,那么下面首先通过一个简单的卖票程序,来观察不同步的情况。
范例:观察问题
class MyThread implements Runnable { private int ticket = 8; // 一共8张票 @Override public void run() { // 线程的主方法 for (int x = 0; x < 20; x++) { if (this.ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ",ticket = " + this.ticket--); } } } } public class TestDemo { public static void main(String[] args) { MyThread mt = new MyThread(); new Thread(mt, "票贩子A").start(); new Thread(mt, "票贩子B").start(); new Thread(mt, "票贩子C").start(); new Thread(mt, "票贩子D").start(); new Thread(mt, "票贩子E").start(); new Thread(mt, "票贩子F").start(); } }
那么在这个时候就发生了不同步的情况,而发生的原因也很简单。对于整个卖票过程实际上是两步完成的:
- 第一步:使用if语句判断是否有票;
- 第二步:进行票数的修改。
但是此时有可能会出现这样一种情况:如果说现在只有最后一张票了,可以满足if条件判断,所以线程可以通过if语句拦截,可是在修改票之前出现了一个延迟操作,那么有可能在这个延迟的过程之中,又出现了其它线程进入到方法之中,由于此时没有修改票数,那么这个线程也可以满足if条件判断,后面的线程依次类推,所以当休眠时间一过,进行票数修改的时候,都将在已有的票数上修改,自然就有可能出现负数。
那么既然已经清楚了问题的产生原因,下面就必须进行问题的解决,问题的解决关键是需要一把锁。如果要想上好责把“锁”,则可以采用两种方式完成:同步代码块、同步方法。
范例:使用同步代码块
同步代码块是使用synchronized关键字定义的代码块,但是在进行同步的时候一定要设置好一个同步对象,所以这个同步对象一般使用当前对象this表示。
class MyThread implements Runnable { private int ticket = 8; // 一共8张票 @Override public void run() { // 线程的主方法 for (int x = 0; x < 20; x++) { synchronized (this) { if (this.ticket > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ",ticket = " + this.ticket--); } } } } } public class TestDemo { public static void main(String[] args) { MyThread mt = new MyThread(); new Thread(mt, "票贩子A").start(); new Thread(mt, "票贩子B").start(); new Thread(mt, "票贩子C").start(); new Thread(mt, "票贩子D").start(); new Thread(mt, "票贩子E").start(); new Thread(mt, "票贩子F").start(); } }
此时的程序执行速度变得明显缓慢了,因为需要一个个排队进行,就好比你们上网和去银行取钱。一定是上网快。虽然性能变慢了,但是数据的安全性提高了,也就是说异步处理(不加synchronized)和同步处理(使用synchronized)的区别也在于此。
范例:同步方法实现
如果在一个方法的声明上使用了synchronized关键字,则表示此方法是一个同步方法。
class MyThread implements Runnable { private int ticket = 8; // 一共8张票 @Override public void run() { // 线程的主方法 for (int x = 0; x < 20; x++) { this.sale(); } } public synchronized void sale() { if (this.ticket > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ",ticket = " + this.ticket--); } } } public class TestDemo { public static void main(String[] args) { MyThread mt = new MyThread(); new Thread(mt, "票贩子A").start(); new Thread(mt, "票贩子B").start(); new Thread(mt, "票贩子C").start(); new Thread(mt, "票贩子D").start(); new Thread(mt, "票贩子E").start(); new Thread(mt, "票贩子F").start(); } }
同步的核心意义:一个线程要等待另一个线程执行完毕。
二、死锁
死锁实际上是在项目运行过程之中产生的一种问题,所以来讲下面的代码只是为大家演示死锁出现的状况,但是代码本身没有任何研究的意义。
所谓的死锁就是指的互相等待的情况,下面编写代码观察。
class JinBo {
public synchronized void tell(XiePengXiang xpx) {
System.out.println("小金子说:给我5000W,放了你儿子,见钱放人!");
xpx.get();
}
public synchronized void get() {
System.out.println("小金子得到钱,放了小刁。");
}
}
class XiePengXiang {
public synchronized void tell(JinBo j) {
System.out.println("小谢子说:先放了我儿子,给你5000W,见人给钱!");
j.get();
}
public synchronized void get() {
System.out.println("小谢子亏了钱,救了儿子。");
}
}
public class TestDemo implements Runnable {
private JinBo jin = new JinBo();
private XiePengXiang xpx = new XiePengXiang();
public static void main(String[] args) {
new TestDemo();
}
public TestDemo() {
new Thread(this).start();
jin.tell(xpx);
}
@Override
public void run() {
xpx.tell(jin);
}
}
结论:多个线程访问同一资源的时候一定要考虑到线程的同步,但是同步会影响程序的性能,同时会提升数据的安全性。过多的同步(synchronized)会有可能出现死锁。