alex_lo

导航

JAVA 基础之 多线程

关键:多线程同步,有两种角度。

1.从JVM的角度来看,instance  = new instance () 不是一个原子操作,在jvm被分成三步,分配内存,设置intance引用指向实例,初始化,JVM乱序执行。需要考虑线陈并发处理问题。可以通过volatile控制语句的原子性。

 

2.从多条语句之间的角度来看: if(instance =null) {instance  = new instance ()} 这样两句话,如何保证同步。使用sychornized 关键字。通过锁,保证同步代码块 。

 

1. 主线程

Static Thread currentThread();  //currentThread() 是Thread类的共有静态方法

Thread t = Thread.currentThread();  //获得主线程

Static void Sleep();  //Thread 类的 静态方法,是当前线程睡眠 , 单位毫秒,可修改单位

 

2. 线程的实现 :

  1) Runnable 接口

  2)扩展Thread类

class MyThread extends Thread{
  public int x = 0;

  public void run(){
    System.out.println(++x);
  }
}

class R implements Runnable{
  private int x = 0;
  public void run(){
    System.out.println(++x);
  }
}

public class Test {
  public static void main(String[] args) throws Exception{
    
    for(int i=0;i<10;i++){
      Thread t = new MyThread();
      t.start();
    }
    Thread.sleep(10000);//让上面的线程运行完成
    R r = new R();
    for(int i=0;i<10;i++){
      Thread t = new Thread(r);
      t.start();
    }
  }
}

 

总结:Thread和Runnable是实现java多线程的2种方式,runable是接口,thread是类,建议使用runable实现java多线程,不管如何,最终都需要通过thread.start()来使线程处于可运行状态。

 

认识Thread的start和run

1) start:

用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的 start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到spu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。

2) run:

run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。

 

sleep & yield 
  sleep()方法让线程睡眠一段时间,时间过后,线程才会去争取cpu时间片。 
  Java1.5提供了一个新的TimeUnit枚举,让sleep的可读性更好。 
  yield()方法让线程主动让出cpu,让cpu重新调度以决定哪个线程可以使用cpu。

 

 

3. 线程同步: 

http://www.cnblogs.com/yyyyy5101/archive/2011/07/20/2112157.html

  1) 同步方法 : synchronize关键字修饰 需要保证同步的方法(临界区)

  2) 同步语句 :  synchronized块

  在synchronized关键字后面,要传一个对象参数,任何线程要进入临界区时必须先要获得该对象的锁,退出临界区时要释放该对象的锁,这样别的线程才有机会进入临界区。 
  临界区和synchronized方法,其原理都是一样的,都是通过在对象上加锁来实现的,只不过临界区来得更加灵活,因为它不光可以对this对象加锁,也可以对任何别的对象加锁。

 

  在使用Java多线程中会需要使用Synchronized块同步方法。我们不仅可以通过synchronized块来同步一个对象变量。也可以使用synchronized块来同步类中的静态方法和非静态方法。

AD: 2013大数据全球技术峰会低价抢票中

 

synchronized关键字有两种用法。第一种就是在《使用Synchronized关键字同步类方法》一文中所介绍的直接用在方法的定义中。另外一种就是synchronized块。我们不仅可以通过synchronized块来同步一个对象变量。也可以使用synchronized块来同步类中的静态方法和非静态方法。

synchronized块的语法如下:

  1. public void method()  
  2. {  
  3.     … …  
  4.     synchronized(表达式)  
  5.     {  
  6.         … …  
  7.     }  

一、非静态类方法的同步   

从《使用Synchronized关键字同步类方法》一文中我们知道使用synchronized关键字来定义方法就会锁定类中所有使用synchronzied关键字定义的静态方法或非静态方法,但这并不好理解。而如果使用synchronized块来达到同样的效果,就不难理解为什么会产生这种效果了。如果想使用synchronized块来锁定类中所有的同步非静态方法,需要使用this做为synchronized块的参数传入synchronized块国,代码如下:

通过synchronized块同步非静态方法

  1. public class SyncBlock  
  2.  {  
  3.       public void method1()  
  4.       {  
  5.           synchronized(this)  // 相当于对method1方法使用synchronized关键字  
  6.           {  
  7.               … …  
  8.           }  
  9.       }  
  10.       public void method2()  
  11.       {  
  12.           synchronized(this)  // 相当于对method2方法使用synchronized关键字  
  13.           {  
  14.               … …  
  15.           }  
  16.       }  
  17.       public synchronized void method3()    
  18.       {  
  19.           … …  
  20.       }  
  21.   } 

在上面的代码中的method1和method2方法中使用了synchronized块。而第017行的method3方法仍然使用synchronized关键字来定义方法。在使用同一个SyncBlock类实例时,这三个方法只要有一个正在执行,其他两个方法就会因未获得同步锁而被阻塞。在使用synchronized块时要想达到和synchronized关键字同样的效果,必须将所有的代码都写在synchronized块中,否则,将无法使当前方法中的所有代码和其他的方法同步。

除了使用this做为synchronized块的参数外,还可以使用SyncBlock.this作为synchronized块的参数来达到同样的效果。

在内类(InnerClass)的方法中使用synchronized块来时,this只表示内类,和外类(OuterClass)没有关系。但内类的非静态方法可以和外类的非静态方法同步。如在内类InnerClass中加一个method4方法,并使method4方法和SyncBlock的三个方法同步,代码如下:

使内类的非静态方法和外类的非静态方法同步

  1. public class SyncBlock  
  2. {  
  3.     … …  
  4.     class InnerClass  
  5.     {  
  6.         public void method4()  
  7.         {  
  8.             synchronized(SyncBlock.this)  
  9.             {  
  10.                 … …   
  11.             }  
  12.         }  
  13.     }  
  14.     … …  

在上面SyncBlock类的新版本中,InnerClass类的method4方法和SyncBlock类的其他三个方法同步,因此,method1、method2、method3和method4四个方法在同一时间只能有一个方法执行。

Synchronized块不管是正常执行完,还是因为程序出错而异常退出synchronized块,当前的synchronized块所持有的同步锁都会自动释放。因此,在使用synchronized块时不必担心同步锁的释放问题。

二、静态类方法的同步

由于在调用静态方法时,对象实例不一定被创建。因此,就不能使用this来同步静态方法,而必须使用Class对象来同步静态方法。代码如下:

通过synchronized块同步静态方法

  1. public class StaticSyncBlock  
  2.   {  
  3.       public static void method1()  
  4.       {  
  5.           synchronized(StaticSyncBlock.class)    
  6.           {  
  7.               … …  
  8.           }  
  9.       }  
  10.       public static synchronized void method2()    
  11.       {  
  12.           … …  
  13.       }  
  14.   } 

在同步静态方法时可以使用类的静态字段class来得到Class对象。在上例中method1和method2方法同时只能有一个方法执行。除了使用class字段得到Class对象外,还可以使用实例的getClass方法来得到Class对象。上例中的代码可以修改如下:

使用getClass方法得到Class对象(每一个加载过的类在JVM中都有Class对象,包括基本数据类型和void关键字)

  1. public class StaticSyncBlock  
  2. {  
  3.     public static StaticSyncBlock instance;   
  4.     public StaticSyncBlock()  
  5.     {  
  6.         instance = this;  
  7.     }  
  8.     public static void method1()  
  9.     {  
  10.        synchronized(instance.getClass())  
  11.        {  
  12.               
  13.        }  
  14.     }  
  15.        
  16. }  

在上面代码中通过一个public的静态instance得到一个StaticSyncBlock类的实例,并通过这个实例的getClass方法得到了Class对象(一个类的所有实例通过getClass方法得到的都是同一个Class对象,因此,调用任何一个实例的getClass方法都可以)。我们还可以通过Class对象使不同类的静态方法同步,如Test类的静态方法method和StaticSyncBlock类的两个静态方法同步,代码如下:

Test类的method方法和StaticSyncBlock类的method1、method2方法同步

  1. public class Test  
  2. {  
  3.     public static void method()  
  4.     {  
  5.         synchronized(StaticSyncBlock.class)  
  6.         {  
  7.                
  8.         }  
  9.     }  

注意:在使用synchronized块同步类方法时,非静态方法可以使用this来同步,而静态方法必须使用Class对象来同步。它们互不影响。当然,也可以在非静态方法中使用Class对象来同步静态方法。但在静态方法中不能使用this来同步非静态方法。这一点在使用synchronized块同步类方法时应注意。


 

对象锁

当使用同步块时,如果方法下的同步块都同步到一个对象上的锁,则所有的任务(线程)只能互斥的进入这些同步块。
Resource1.java演示了三个线程(包括main线程)试图进入某个类的三个不同的方法的同步块中,虽然这些同步块处在不同的方法中,但由于是同步到同一个对象(当前对象 synchronized (this)),所以对它们的方法依然是互斥的。

 

package com.zj.lock;
import java.util.concurrent.TimeUnit;
 
public class Resource1 {
    public void f() {
       // other operations should not be locked...
       System.out.println(Thread.currentThread().getName()
              + ":not synchronized in f()");
       synchronized (this) {
           for (int i = 0; i < 5; i++) {
              System.out.println(Thread.currentThread().getName()
                     + ":synchronized in f()");
              try {
                  TimeUnit.SECONDS.sleep(3);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
           }
       }
    }
 
    public void g() {
       // other operations should not be locked...
       System.out.println(Thread.currentThread().getName()
              + ":not synchronized in g()");
       synchronized (this) {
           for (int i = 0; i < 5; i++) {
              System.out.println(Thread.currentThread().getName()
                     + ":synchronized in g()");
              try {
                  TimeUnit.SECONDS.sleep(3);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
           }
       }
    }
 
    public void h() {
       // other operations should not be locked...
       System.out.println(Thread.currentThread().getName()
              + ":not synchronized in h()");
       synchronized (this) {
           for (int i = 0; i < 5; i++) {
              System.out.println(Thread.currentThread().getName()
                     + ":synchronized in h()");
              try {
                  TimeUnit.SECONDS.sleep(3);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
           }
       }
    }
 
    public static void main(String[] args) {
       final Resource1 rs = new Resource1();
 
       new Thread() {
           public void run() {
              rs.f();
           }
       }.start();
 
       new Thread() {
           public void run() {
              rs.g();
           }
       }.start();
 
       rs.h();
    }
}//匿名内部类

 匿名类是不能有名称的类,所以没办法引用它们。必须在创建时,作为new语句的一部分来声明它们。这就要采用另一种形式的new语句,如下所示: new <类或接口> <类的主体> 这种形式的new语句声明一个新的匿名类,它对一个给定的类进行扩展,或者实现一个给定的接口。它还创建那个类的一个新实例,并把它作为语句的结果而返回。要扩展的类和要实现的接口是new语句的操作数,后跟匿名类的主体。如果匿名类对另一个类进行扩展,它的主体可以访问类的成员、覆盖它的方法等等,这和其他任何标准的类都是一样的。如果匿名类实现了一个接口,它的主体必须实现接口的方法。

 

附带问题:为什么局部内部类(即:定义在方法中的内部,包括匿名内部类),使用的参数必须为final?

答 :局部变量的生命周期与局部内部类的对象的生命周期的不一致性!

假设:
1. 我们在方法内定义了一个 匿名的 Thread 子类,他使用了方法的局部参数,然后我让这个线程运行去,因为是不同的线程,那么当我这个方法的启动线程 的语句执行过了,而且我修改了这个参数或局部变量,那么那个线程启动执行的时候是不是会出现莫名其妙的问题:运行时刻能访问到的变量太难以捉摸了,我是该 复制一份过去给新线程运行时使用还是到时候再来取呢(再来取时已经物是人非了)?

2. 设方法f被调用,从而在它的调用栈中生成了变量i,此时产生了一个局部内部类对象inner_object,它访问了该局部变量i .当方法f()运行结束后,局部变量i就已死亡了,不存在了.但:局部内部类对象inner_object还可能   一直存在(只能没有人再引用该对象时,它才会死亡),它不会随着方法f()运行结束死亡.这时:出现了一个"荒唐"结果:局部内部类对象 inner_object要访问一个已不存在的局部变量i!
Java 为了消除这个编程中可能出现的歧义,使用方法内的内部类时如果访问了方法的参数或局部变量,那么它应该是 final 的。 

final 原理,当变量是final时,通过将final局部变量"复制"一份

另详解: http://feiyeguohai.iteye.com/blog/1500108

 

 

如果满足下面的一些条件,使用匿名内部类是比较合适的:

?只用到类的一个实例。 ?类在定义后马上用到。

?类非常小(SUN推荐是在4行代码以下)

?给类命名并不会导致你的代码更容易被理解。

在使用匿名内部类时,要记住以下几个原则:

?匿名内部类不能有构造方法。

?匿名内部类不能定义任何静态成员、方法和类。

?匿名内部类不能是public,protected,private,static。

?只能创建匿名内部类的一个实例。

?一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。

?因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。

 


 

4.线程间通信 :

 

  notify() wait() notifyAll();

 

Java中如何实现线程同步 
  在Java中,线程的同步涉及到synchronized和三个方法wait()、notify()和notifyAll()。 

  synchronized在上篇中已经讲过了,这里就不再重复了。 

  wait()方法与Thread类的sleep()和yield()方法类似,都是让当线程睡眠,或者说是暂停执行;与之不同的是wait()方法会释放掉当前对象的锁,也因为此wait()方法必须在synchronized块里才能被调用。 

  notify()和notifyAll()方法用于唤醒之前调用wait()方法睡眠的线程,与wait()方法一样,notify()和notifyAll()也必须在synchronized块里才能被调用。 

  注意wait()、notify()和notifyAll()三个方法都是Object类的,而不是Thread类的。因为这三个方法都涉及的锁的操作,而锁的操作适用于所有的对象。

posted on 2013-03-14 15:59  alex_lo  阅读(659)  评论(0编辑  收藏  举报