你的位置:首页 > Java教程

[Java教程]java多线程系列3


如果一个资源被多个线程同时访问,可能会遭到破坏,这篇文章介绍java线程同步来解决这类问题

引入问题

某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。

方法一:继承Thread类

public class SellTicket extends Thread {   // 定义100张票  // private int tickets = 100;  // 为了让多个线程对象共享这100张票,我们其实应该用静态修饰  private static int tickets = 100;   @Override  public void run() {    // 定义100张票    // 是为了模拟一直有票    while (true) {      if (tickets > 0) {        System.out.println(getName() + "正在出售第" + (tickets--) + "张票");      }    }  }}/* * 继承Thread类来实现。 */public class SellTicketDemo {  public static void main(String[] args) {    // 创建三个线程对象    SellTicket st1 = new SellTicket();    SellTicket st2 = new SellTicket();    SellTicket st3 = new SellTicket();     // 给线程对象起名字    st1.setName("窗口1");    st2.setName("窗口2");    st3.setName("窗口3");     // 启动线程    st1.start();    st2.start();    st3.start();  }}

方法二:实现Runnable接口

public class SellTicket implements Runnable {  // 定义100张票  private int tickets = 100;   @Override  public void run() {    while (true) {      if (tickets > 0) {        System.out.println(Thread.currentThread().getName() + "正在出售第"            + (tickets--) + "张票");      }    }  }}/* * 实现Runnable接口的方式实现 */public class SellTicketDemo {  public static void main(String[] args) {    // 创建资源对象    SellTicket st = new SellTicket();     // 创建三个线程对象    Thread t1 = new Thread(st, "窗口1");    Thread t2 = new Thread(st, "窗口2");    Thread t3 = new Thread(st, "窗口3");     // 启动线程    t1.start();    t2.start();    t3.start();  }}

电影院售票程序,从表面上看不出什么问题,在真实生活中,售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟

改实现接口方式的卖票程序,每次卖票延迟100毫秒,代码如下:

public class SellTicket implements Runnable {  // 定义100张票  private int tickets = 100;  @Override  public void run() {    while (true) {      // t1,t2,t3三个线程      // 这一次的tickets = 1;      if (tickets > 0) {        // 为了模拟更真实的场景,我们稍作休息        try {          Thread.sleep(100); //t1进来了并休息,t2进来了并休息,t3进来了并休息,        } catch (InterruptedException e) {          e.printStackTrace();        }         System.out.println(Thread.currentThread().getName() + "正在出售第"            + (tickets--) + "张票");        //窗口1正在出售第1张票,tickets=0        //窗口2正在出售第0张票,tickets=-1        //窗口3正在出售第-1张票,tickets=-2      }    }  }}/* * 实现Runnable接口的方式实现 */public class SellTicketDemo {  public static void main(String[] args) {    // 创建资源对象    SellTicket st = new SellTicket();     // 创建三个线程对象    Thread t1 = new Thread(st, "窗口1");    Thread t2 = new Thread(st, "窗口2");    Thread t3 = new Thread(st, "窗口3");     // 启动线程    t1.start();    t2.start();    t3.start();  }}

出现问题:

相同的票出现多次

CPU的一次操作必须是原子性的

还出现了负数的票

随机性和延迟导致的

解决线程安全问题的基本思想与方法

首先想为什么出现问题?(也是我们判断是否有问题的标准)

  • 是否是多线程环境
  • 是否有共享数据
  • 是否有多条语句操作共享数据

如何解决多线程安全问题呢?

基本思想:让程序没有安全问题的环境。

把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。

解决线程安全问题实现1--同步代码块

格式:synchronized(对象){需要同步的代码;}

同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。

修改上面的代码如下:

public class SellTicket implements Runnable {  // 定义100张票  private int tickets = 100;  //创建锁对象  private Object obj = new Object();    @Override  public void run() {    while (true) {      synchronized (obj) {        if (tickets > 0) {          try {            Thread.sleep(100);          } catch (InterruptedException e) {            e.printStackTrace();          }          System.out.println(Thread.currentThread().getName()              + "正在出售第" + (tickets--) + "张票");        }      }    }  }}/* * 同步代码块: *     synchronized(对象){ *       需要同步的代码; *     } * *     A:对象是什么呢? *       我们可以随便创建一个对象试试。 *     B:需要同步的代码是哪些呢? *       把多条语句操作共享数据的代码的部分给包起来 * *     注意: *       同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。 *       多个线程必须是同一把锁。 */public class SellTicketDemo {  public static void main(String[] args) {    // 创建资源对象    SellTicket st = new SellTicket();     // 创建三个线程对象    Thread t1 = new Thread(st, "窗口1");    Thread t2 = new Thread(st, "窗口2");    Thread t3 = new Thread(st, "窗口3");     // 启动线程    t1.start();    t2.start();    t3.start();  }}

注意:同步代码块可以用任意对象做锁

解决线程安全问题实现2--同步方法

就是把同步关键字加到方法上

1、同步方法的锁对象:this

public class SellTicket implements Runnable {  private static int tickets = 100;  private Object obj = new Object();  @Override  public void run() {    while (true) {      synchronized (this) {        if (tickets > 0) {          try {            Thread.sleep(100);          } catch (InterruptedException e) {            e.printStackTrace();          }          System.out.println(Thread.currentThread().getName()              + "正在出售第" + (tickets--) + "张票 ");        }      }    }  }  private synchronized void sellTicket() {    if(tickets > 0) {      try {        Thread.sleep(100);      } catch (InterruptedException e) {        // TODO Auto-generated catch block        e.printStackTrace();      }      System.out.println(Thread.currentThread().getName()          + "正在出售第" + (tickets--) + "张票 ");    }  }}

2、静态方法的锁对象:类的字节码文件对象。

public class SellTicket implements Runnable {  private static int tickets = 100;  private Object obj = new Object();  @Override  public void run() {    while (true) {      synchronized (SellTicket.class) {        if (tickets > 0) {          try {            Thread.sleep(100);          } catch (InterruptedException e) {            e.printStackTrace();          }          System.out.println(Thread.currentThread().getName()              + "正在出售第" + (tickets--) + "张票 ");        }      }    }  }  private static synchronized void sellTicket() {    if(tickets > 0) {      try {        Thread.sleep(100);      } catch (InterruptedException e) {        // TODO Auto-generated catch block        e.printStackTrace();      }      System.out.println(Thread.currentThread().getName()          + "正在出售第" + (tickets--) + "张票 ");    }  }}

同步的前提:

  • 多个线程
  • 多个线程使用的是同一个锁对象

同步的好处:同步的出现解决了多线程的安全问题。

同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

解决线程安全问题实现3--Lock锁的使用

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

ReentrantLock (Java Platform SE 6)

一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock; public class SellTicket implements Runnable {   // 定义票  private int tickets = 100;   // 定义锁对象  private Lock lock = new ReentrantLock();   @Override  public void run() {    while (true) {      try {        // 加锁        lock.lock();        if (tickets > 0) {          try {            Thread.sleep(100);          } catch (InterruptedException e) {            e.printStackTrace();          }          System.out.println(Thread.currentThread().getName()              + "正在出售第" + (tickets--) + "张票");        }      } finally {        // 释放锁        lock.unlock();      }    }  } }