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();
 }
}

时间: 2024-10-08 08:16:10

Java笔记六.线程同步、线程死锁的相关文章

Java之线程,常用方法,线程同步,死锁

1, 线程的概念 进程与线程 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程.(进程是资源分配的最小单位) 线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小.(线程是cpu调度的最小单位) 切换而不是同步 一个程序中的方法有几条执行路径, 就有几个线程 Java中线程的生命周期 Java线程具有五中基本状态 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t =

java笔记--用ThreadLocal管理线程,Callable<V>接口实现有返回值的线程

用ThreadLocal管理线程,Callable<V>接口实现有返回值的线程 ThreadLocal在我的笔记"关于线程同步"的第5种方式里面有介绍,这里就不多说了. --如果朋友您想转载本文章请注明转载地址"http://www.cnblogs.com/XHJT/p/3899890.html  "谢谢-- Callable<V>接口类似于Runnable,两者都是为了哪些其实例可能被另一个线程执行的类设计的, 但是Runnable不会返回

java笔记--使用事件分配线程更新Swing控件

使用事件分配线程更新Swing控件: Swing并不是线程安全的,如果在多个线程中更新Swing控件,则很可能造成程序崩溃. 为了避免这种问题,可以使用时间分配线程来更新Swing控件. EventQueue 是一个与平台无关的类,它将来自于底层同位体类和受信任的应用程序类的事件列入队列. 它封装了异步事件指派机制,该机制从队列中提取事件,然后通过对EventQueue调用dispatchEvent(AWTEvent) 方法来指派这些事件(事件作为参数被指派).该机制的特殊行为是与实现有关的.为

线程同步&amp;线程池

线程同步&线程池 线程同步 线程不同步会出现的问题: 当多个线程操作同一资源时,会出现重复操作和和操作不存在的资源的问题,为了规避这一问题就需要线程的同步操作来实现资源的共同使用. 线程同步: 当多个线程操作同一资源时,给操作该资源的代码加上一把锁,当有一个线程拿到这把锁后,其他线程都不能操作带锁的资源代码,直至拿到锁的线程释放锁. 线程同步实现的3种方式: 同步代码块 synchornized(obj){ //涉及操作同一资源的代码 } 注: 没有静态的同步代码块 obj: 任意类型的对象,相

线程同步--线程间通信

一.线程同步 线程的同步方法跟其他系统下类似,我们可以用原子操作,可以用 mutex,lock 等. iOS 的原子操作函数是以 OSAtomic 开头的,比如:OSAtomicAdd32, OSAtomicOr32 等等.这些函数可以直接使用,因为它 们是原子操作. iOS 中的 mutex 对应的是 NSLock,它遵循 NSLooking 协议,我们可以使用 lock, tryLock, lockBeforeData:来加锁,用 unLock 来解锁.使用示例: BOOL moreToDo

线程:主线程、子线程 同步线程、异步线程 单线程、多线程 System.Threading与System.Windows.Threading

入门-------------------------------------------------------------------------------- 概述与概念    一个C#程序开始于一个单线程,这个单线程是被CLR和操作系统(也称为“主线程”)自动创建. 创建和开始使用多线程    public Window1()    {        //主线程         //Code……        //使用匿名方法来启动子线程        Thread t = new Th

Java线程同步与死锁、生产者消费者模式以及任务调度等

一.Thread类基本信息方法 package Threadinfo; public class MyThread implements Runnable{ private boolean flag = true; private int num = 0; @Override public void run() { while(flag) { System.out.println(Thread.currentThread().getName()+"-->"+num++); } }

多线程——线程同步,死锁

线程同步: 为什么需要同步 ①   线程同步是为了防止多个线程访问一个数据对象时,对数据造成破坏. ②   线程的同步是保证多线程安全访问竞争资源的一种手段. 同步和锁 ①   Java中每一个对象都有一个内置锁. ②   当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁:当程序运行到synchronized同步代码块时,自动获得锁定对象的锁. ③   获得一个对象的锁也称为获取锁.锁定对象.在对象上锁定或在对象上同步.当程序运

C#:多线程、线程同步与死锁

推荐阅读: C#线程系列讲座(1):BeginInvoke和EndInvoke方法 C#线程系列讲座(2):Thread类的应用 C#线程系列讲座(3):线程池和文件下载服务器 C#线程系列讲座(4):同步与死锁 C#线程系列讲座(5):同步技术之Monitor C#中多线程同步的Monitor理解