首页 > 代码库 > Java笔记六.线程同步、线程死锁

Java笔记六.线程同步、线程死锁

线程同步、线程死锁
    在上一篇文章中,有一个模拟售卖火车票系统,在卖车票的程序代码中,极有可能碰到一种意外,就是同一张票号被打印两次多次,也可能出现打印出0甚至负数的票号。具体表现为:假设tickets的值为1的时候,线程1刚执行完if(tickets>0)这行代码,正准备执行下面的代码,就在这时,操作系统将CPU切换到了线程2上执行,此时tickets的值仍为1,线程2执行完上面两行代码,tickets的值变为0后,CPU又切回到了线程1上执行,线程1不会再执行if(tickets>0)这行代码,因为先前已经比较过了,并且比较的结果为真,线程1继续执行后面的代码,最终导致tickets的值为0,而这个结果是我们不允许的。
一、线程同步
1.线程同步
    为了解决上述多线程操作同一资源出现不同步的问题,我们可以这样做,即当一个线程运行到if(tickets>0)后,CPU不去执行其他线程中的、可能影响当前线程中的下一句代码的执行结果的代码块,必须等到下一句执行完后才能去执行其他线程中的有关代码块。这段代码就好比一座独木桥,任意时刻,只能有一个人在桥上行走,程序中不能有多个线程同时在这两句代码之间执行,这就是线程同步。
2.synchronized语句
格式:synchronized(object){代码段}    //pbject可以是任意的一个对象
     synchronized语句内的代码段,就形成了同步代码块。也就是说,在同一时刻只能有一个线程可以进入同步代码块内运行,只有当该线程离开同步代码块后,其他线程才能进入同步代码块内运行。object为任意类型的对象,该对象都有一个标志位,该标志位具有0、1两种状态,其开始状态为1,当执行synchronized(object)语句后,object对象的标志位变为0状态,知道执行完整个synchronized语句中的代码块后又回到了1状态。一个线程执行到synchronized(object)语句处时,先检查object对象的标志位(即同步对象的锁标旗),如果为0状态,表明已经有另外的线程的执行状态正在有关的同步代码块中,这个线程将暂时阻塞,让出CPU资源,知道另外的线程执行完有关的同步代码块,将object对象的标志位恢复到1状态这个阻塞就被取消。
    一个用于synchronized语句中的对象称为一个监视器,当一个线程获得了synchronized(object)语句中的代码块的执行权,即意味着它锁定了监视器,在一段时间内,只能有一个线程可以锁定监视器。当同步块代码执行完毕或者遇到break语句或抛出异常时,线程也会释放该锁旗标。
synchronized语句同步代码实现:
public class DemoThread {
  public static void main(String[] args)
  {
   ThreadTest runnable = new ThreadTest();	 //声明一个Runnable子类对象
    new Thread(runnable).start();	 //创建四个线程,它们使用的是同一资源
    new Thread(runnable).start();
   new Thread(runnable).start();
   new Thread(runnable).start();
  }
}
//实现一个Runnable子类
public class ThreadTest implements Runnable {
 int tickets=100;
 String str = new String();    //定义一个锁旗标
 public void run()
 {
   while(true)
   {
    synchronized(str)
    {
     if(tickets>0)
     {
      try
      {
       Thread.sleep(1000);
      }
      catch(Exception e)
      {
       e.printStackTrace();
      }
      System.out.println(Thread.currentThread().getName()+" is saling tickets"+tickets--);
     }
    }
   }
 }
}
分析如果我们将 synchronized同步对象的锁标旗在run()方法中定义时,多线程不能实现同步。那是因为当一个线程启动后将会调用run方法,对每一次调用,程序都产生一个不同的str局部对象,这四个线程使用的同步监视器完全是四个不同的对象,所以彼此之间不能同步。
3.不同代码块相互同步
    上面我们提到的同步代码块,是指不仅同一个代码块在多个线程间实现同步。当然,若干个不同的代码块也可以实现相互之间的同步,只要各synchronized(object)语句中的object完全是同一个对象就可以了。
4.同步函数
    除了可以对代码块进行同步外,也可以对函数实现同步,即只要在需要同步的函数定义前加上synchronized关键字即可。
(1)DemoThread.java
public class DemoThread {
  public static void main(String[] args)
  {
   ThreadTest runnable = new ThreadTest();	 //声明一个Runnable子类对象
    new Thread(runnable).start();	 //创建四个线程,它们使用的是同一资源
    new Thread(runnable).start();
   new Thread(runnable).start();
   new Thread(runnable).start();
  }
}
(2)ThreadTest.java
//实现一个Runnable子类
public class ThreadTest implements Runnable
{
 int tickets=100;
 public void run()
 {
   while(true)
   {
    sale();
   }
 }
 public synchronized void  sale()
 {
   if(tickets>0)
   {
    try
    {
     Thread.sleep(1000);
    }
    catch(Exception e)
    {
     e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+" is saling tickets"+tickets--);
   }
 }
}
技术分享技术分享

分析1在同一类中,使用synchronized关键字定义的若干方法,可以在多个线程之间同步,当有一个线程进入了synchronized修饰的方法(获得监视器),其他线程就不能进入同一个对象的所有使用了synchronized修饰的方法,直到第一个线程执行完它所进入的synchronized修饰的方法未知(离开监视器)。
分析2我们通过观察程序结果发现,程序的运行速度要比原来没有使用同步处理时慢,那是因为系统要不停的对同步监视器进行,需要更多时间的开销。所以说同步是以牺牲程序的性能为代价的,如果我们能够确定程序没有安全性问题,就没必要使用同步控制。
分析3在实现代码块与函数之间的同步时,由于同步函数使用的监视器是this对象(类中的非静态方法始终都能访问到的一个对象就是这个对象本身即this),所以同步代码块中应使用this对象来作为同步监视器。

二、线程死锁
    死锁是一种少见的、而且难以调试的错误,在两个线程对两个同步对象具有循环依赖时具就会出现死锁。例如一个线程进入对象X的监视器,而另一个对象进入了对象Y的监视器,这时进入X对象监视器的线程如果还试图进入Y对象的监视器就会被阻隔,接着进入Y对象监视器的线程如果试图进入X对象的监视器也会被阻隔,这样两个线程都处于挂起状态。
(1)A.java
public class A {
 synchronized void foo(B b)
 {
  String name = Thread.currentThread().getName();	//获取当前线程的名称
  System.out.println(name+"enter A.foo");
  try
  {
   Thread.sleep(100);
  }
  catch(Exception e)
  {
   e.printStackTrace();
  }
  System.out.println(name+" is trying to call b.last");
  b.last();
 }
 synchronized  void last()
 {
  System.out.println("inside A.last");
 }
}

(2)B.java
public class B {
 synchronized void bar(A a)
 {
  String name = Thread.currentThread().getName();	//获取当前线程的名称
  System.out.println(name+"enter B.bar");
  try
  {
   Thread.sleep(100);
  }
  catch(Exception e)
  {
   e.printStackTrace();
  }
  System.out.println(name+" is trying to call b.last");
  a.last();
 }
 synchronized  void last()
 {
  System.out.println("inside B.last");
 }
}
(3)BlockRunnable.java
public class BlockRunnable implements Runnable
{
 A a=new A();	 //创建一个A类对象
 B b=new B();	 //创建一个B类对象
 BlockRunnable()  //构造方法
 {
  Thread.currentThread().setName("MainThread");	 //设置主线程名字
  new Thread(this).start();	 //创建并启动一个子线程
  a.foo(b);	 //主线程中调用a类的foo方法,foo方法试图调用B类的bar方法
  System.out.println("back in the Main thread");
 }
 public void run() {
  Thread.currentThread().setName("ChildThread");
  b.bar(a);
  System.out.println("back in the ChildThread");
 }
 //主
 public static void main(String[] args)
 {
  new BlockRunnable();
 }
}

技术分享

Java笔记六.线程同步、线程死锁