咱们如今来经过Runnable接口实现多线程,产生3个线程对象,模拟卖票的场景!java
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 (InterruptedException e) { e.printStackTrace(); } System.out.println("卖票:ticket = " + ticket--); } } } }; public class SyncDemo01 { 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 = 5 卖票:ticket = 4 卖票:ticket = 5 卖票:ticket = 3 卖票:ticket = 2 卖票:ticket = 2 卖票:ticket = 1 卖票:ticket = 0 卖票:ticket = -1 // 票数居然还能负数?
为何会出现“负数”的状况:在上面的操做中,咱们能够发现,由于加入了“延迟操做”一个线程颇有可能在还没对票数进行减操做以前,其余线程就已经将票数减小了,这样就会出现票数为负的状况。并发
有没有方法解决?确定是有的!想解决这样的问题,就必须使用同步!所谓同步,就是指多个操做在同一个时间段内只能有一个线程进行,其余线程要等待此线程完成以后才能够继续执行。this
解决资源共享的同步操做,有两种方法:同步代码块 和 同步方法 !线程
所谓代码块就是指使用“{}”括起来的一段代码,若是在代码块上加上synchronized关键字,则此代码块就成为同步代码块。code
【同步代码块 - 格式】对象
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 (InterruptedException e) { e.printStackTrace(); } System.out.println("卖票:ticket = " + ticket--); } } } } }; public class SyncDemo01 { 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 = 5 卖票:ticket = 4 卖票:ticket = 3 卖票:ticket = 2 卖票:ticket = 1
除了能够将须要的代码设置成同步代码块外,也可使用synchronized关键字将一个方法声明成同步方法。资源
【同步方法 - 格式】
synchronized 方法返回值 方法名称(参数列表) { }
咱们采用同步方法对代码进行修改:
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 (InterruptedException e) { e.printStackTrace(); } System.out.println("卖票:ticket = " + ticket--); } } }; public class SyncDemo01 { 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 = 5 卖票:ticket = 4 卖票:ticket = 3 卖票:ticket = 2 卖票:ticket = 1
从以上程序的运行结果能够发现,此代码完成了与以前同步代码块一样的功能。
多个线程共享同一资源时须要进行同步,以保证资源操做的完整性。
经过上面的例子,咱们发现,同步仍是颇有好处的,它能够保证资源共享操做的正确性,可是过多的同步也会产生问题,这就是咱们接下来要讨论“死锁”问题!
多线程以及多进程改善了系统资源的利用率并提升了系统 的处理能力。然而,并发执行也带来了新的问题 -- 死锁。
在编写多线程的时候,必需要注意资源的使用问题,若是两个或多个线程分别拥有不一样的资源,而同时又须要对方释放资源才能继续运行时,就会发生死锁。
简单来讲:死锁就是当一个或多个进程都在等待系统资源,而资源自己又被占用时,所产生的一种状态。
多个线程竞争共享资源,因为资源被占用,资源不足或进程推动顺序不当等缘由形成线程处于永久阻塞状态,从而引起死锁。
一、互斥条件:进程对于所分配到的资源具备排它性,即一个资源只能被一个进程占用,直到被该进程释放
二、请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已得到的资源保持不放。
三、不剥夺条件:任何一个资源在没被该进程释放以前,任何其余进程都没法对他剥夺占用
四、循环等待条件:当发生死锁时,所等待的进程一定会造成一个环路(相似于死循环),形成永久阻塞。
如今张三想要李四的画,李四想要张三的书,因而产生了如下对话:
张三对李四说:“把你的画给我,我就给你书”
李四对张三说:“把你的书给我,我就给你画”
此时,张山在等着李四的答复,李四也在等着张三的答复,那么这样下去的结果就是,两我的都在等待,可是都没有结果,这就是“死锁”!
从线程角度来讲,所谓死锁就是指两个线程都在等待彼此先完成,形成了程序的停滞,通常程序的死锁都是在程序运行时出现,好比咱们经过一个代码范例来看看发生死锁的场景。
class Zhangsan { public void say() { System.out.println("Zhangsan say: give me your painting, i will give you my book!"); } public void get() { System.out.println("Zhangsan got Lisi's painting!"); } } class Lisi { public void say() { System.out.println("Lisi say: give me your book, i will give you my painting!"); } public void get() { System.out.println("Lisi got Zhangsan's book!"); } } public class ThreadDeadLock implements Runnable { private static Zhangsan zs = new Zhangsan(); // 实例化static型对象,数据共享 private static Lisi ls = new Lisi(); // 实例化static型对象,数据共享 private boolean flag = false; // 声明标记,用于判断哪一个对象先执行 public void run() { if (flag) { // 判断标志位,flag为true,Zhangsan先执行 synchronized (zs) { // 同步第一个对象 zs.say(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (ls) { // 同步第二个对象 zs.get(); } } } else { // Lisi先执行 synchronized (ls) { // 同步第二个对象 ls.say(); try { Thread.sleep(500); } catch (InterruptedException 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); Thread thB = new Thread(t2); thA.start(); thB.start(); } }
咱们执行下这段代码,结果以下:
Zhangsan say: give me your painting, i will give you my book! Lisi say: give me your book, i will give you my painting!
从程序的运行结果中能够看出,两个线程都在彼此等待着对方的执行完成,这样,程序就没法向下继续执行,从而形成了死锁的现象。
要预防和避免死锁的发生,只需将上面所讲到的4个条件破坏掉其中之一便可。
如上面的代码当中,有四个同步代码块,只须要将其中一个同步代码块去掉,便可解决死锁问题,通常而言破坏“循环等待”这个条件是解决死锁最有效的方法。