JAVA多线程

最近在做代码优化时学习和研究了下JAVA多线程的使用,看了菜鸟们的见解后做了下总结。
1.JAVA多线程实现方式
JAVA多线程实现方式主要有三种:继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。

2.继承Thread类实现多线程
继承Thread类的方法尽管被我列为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:
public class MyThread extends Thread {
  public void run() {
   System.out.println("MyThread.run()");
  }
}
在合适的地方启动线程如下:
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();

3.实现Runnable接口方式实现多线程
如果自己的类已经extends另一个类,就无法直接extends Thread,此时,必须实现一个Runnable接口,如下:
public class MyThread extends OtherClass implements Runnable {
  public void run() {
   System.out.println("MyThread.run()");
  }
}
为了启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例:
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码:
public void run() {
  if (target != null) {
   target.run();
  }
}

4.使用ExecutorService、Callable、Future实现有返回结果的多线程
ExecutorService、Callable、Future这个对象实际上都是属于Executor框架中的功能类。想要详细了解Executor框架的可以访问http://www.javaeye.com/topic/366591 ,这里面对该框架做了很详细的解释。返回结果的线程是在JDK1.5中引入的新特征,确实很实用,有了这种特征我就不需要再为了得到返回值而大费周折了,而且即便实现了也可能漏洞百出。
可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5下验证过没问题可以直接使用。代码如下:

 

import java.util.concurrent.*;

import java.util.Date;import java.util.List;import java.util.ArrayList;/*** Java线程:有返回值的线程
* 
* @author wb_qiuquan.ying*/@SuppressWarnings("unchecked")public class Test {public static void main(String[] args) throws ExecutionException,
    InterruptedException {
   System.out.println("----程序开始运行----");
   Date date1 = new Date();

   int taskSize = 5;
   // 创建一个线程池
   ExecutorService pool = Executors.newFixedThreadPool(taskSize);
   // 创建多个有返回值的任务
   List<Future> list = new ArrayList<Future>();
   for (int i = 0; i < taskSize; i++) {
    Callable c = new MyCallable(i + " ");
    // 执行任务并获取Future对象
    Future f = pool.submit(c);
    // System.out.println(">>>" + f.get().toString());    list.add(f);
   }
   // 关闭线程池   pool.shutdown();

   // 获取所有并发任务的运行结果
   for (Future f : list) {
    // 从Future对象上获取任务的返回值,并输出到控制台
    System.out.println(">>>" + f.get().toString());
   }

   Date date2 = new Date();
   System.out.println("----程序结束运行----,程序运行时间【" + (date2.getTime() - date1.getTime()) + "毫秒】");
}

}

class MyCallable implements Callable<Object> 

{

private String taskNum;MyCallable(String taskNum) {

   this.taskNum = taskNum;

}

public Object call() throws Exception {   System.out.println(">>>" + taskNum + "任务启动");

   Date dateTmp1 = new Date();
   Thread.sleep(1000);
   Date dateTmp2 = new Date();
   long time = dateTmp2.getTime() - dateTmp1.getTime();
   System.out.println(">>>" + taskNum + "任务终止");
   return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
}
}
   

代码说明:
上述代码中Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads)   //创建固定数目线程的线程池。
public static ExecutorService newCachedThreadPool() 
创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
public static ExecutorService newSingleThreadExecutor() 
创建一个单线程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成

 

Java线程中run和start方法的区别

Thread类中run()和start()方法的区别如下:
run()方法:在本线程内调用该Runnable对象的run()方法,可以重复多次调用;
start()方法:启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程;          

package com.ljq.test;

public class ThreadTest {
      
   public static void main(String[] args){
       Thread thread=new ThreadDemo();
       //第一种
       //表明: run()和其他方法的调用没任何不同,main方法按顺序执行了它,并打印出最后一句
       //thread.run();
       
       //第二种
       //表明: start()方法重新创建了一个线程,在main方法执行结束后,由于start()方法创建的线程没有运行结束,
       //因此主线程未能退出,直到线程thread也执行完毕.这里要注意,默认创建的线程是用户线程(非守护线程)
       //thread.start();
       
       //第三种
       //1、为什么没有打印出100句呢?因为我们将thread线程设置为了daemon(守护)线程,程序中只有守护线程存在的时候,是可以退出的,所以只打印了七句便退出了
       //2、当java虚拟机中有守护线程在运行的时候,java虚拟机会关闭。当所有常规线程运行完毕以后,
       //守护线程不管运行到哪里,虚拟机都会退出运行。所以你的守护线程最好不要写一些会影响程序的业务逻辑。否则无法预料程序到底会出现什么问题
       //thread.setDaemon(true);
       //thread.start();
       
       //第四种
       //用户线程可以被System.exit(0)强制kill掉,所以也只打印出七句
       thread.start();
       System.out.println("main thread is over");
       System.exit(1);
   }
   
   public static class ThreadDemo extends Thread{
       @Override
       public void run() {
           for (int i = 0; i < 100; i++) {
               System.out.println("This is a Thread test"+i);
           }
       }
   }

}

 

 

 1: 守护线程 : 

守护线程(DaemonThread)是某些提供通用服务的在后台运行的程序,是优先级最低的线程。当所有的非守护线程执行结束后,程序会结束所有的守护线程而终止运行。如果当前还有非守护线程的线程在运行,则程序不会终止,而是等待其执行完成。

例子:

 import java.util.concurrent.TimeUnit;

public class SimpleDaemons implements Runnable {

  @Override

  public void run() {

    try {

      System.out.println("Start daemons");

      TimeUnit.SECONDS.sleep(1);

    } catch (Exception e) {

      System.out.println("sleep() interrupted");

    } finally {

      System.out.println("Finally is running");

    }

  }

}

 

public static void main(String[] args) {

    try {

      Thread thread = new Thread(new SimpleDaemons());

      System.out.println(thread.isDaemon());

//      thread.setDaemon(true);  //设置为守护线程

      thread.start();

    } catch (Exception e) {

    }

    System.out.println("main end!");

  }

输出结果: 

Start daemons 

 

Finally没有执行,如果注释掉daemon.setDaemon(true)设置守护进程这一句代码。 

 

输出结果: 

Start daemons 

Finally is running 

 

结果原因是,main()是这个程序中唯一的非守护线程,当没有非守护线程在运行时,JVM强制推出终止守护线程的运行。 

通过Thread对象的setDaemon方法可以设置线程是否为守护线程,通过isDaemon方法可以判断线程对象是否为守护线程。 

由守护线程创建的线程对象不论有没有通过setDaemon方法显式设置,都是守护线程。 

 

2: synchronized线程同步

(1).对象方法同步: 每个对象有一个线程同步锁与之关联,同一个对象的不同线程在同一时刻只能有一个线程调用methodA方法。

public synchronized void methodA() {

    System.out.println("object method synchronized!");

  }

 

(2).类所有对象方法同步(标识为static的方法):静态方法的线程同步锁对类的所有对象都起作用,即所有对象的线程在同一时刻只能有一个类的一个线程调用该方法。

 

public synchronized static void methodB() {

    System.out.println("synchronized static");

  }


(3).对象同步代码块:使用当前对象作为线程同步锁,同一个对象的不同线程在同一时刻只能有一个线程调用methodC方法中的代码块。

 

public void methodC() {

    synchronized (this) {

      System.out.println("object synchronized block");

    }

  }

  

(4).类同步代码块: 使用类字节码对象作为线程同步锁,类所有对象的所有线程在同一时刻只能有一个类的一个线程调用methodD的同步代码块。

 public void methodC() {

    synchronized (NullTest.class) {

      System.out.println("class synchronized block!");

    }

  }

 

注意:线程的同步是针对对象的,不论是同步方法还是同步代码块,都锁定的是对象,而非方法或代码块本身。每个对象只能有一个线程同步锁与之关联。 
如果一个对象有多个线程同步方法,只要一个线程访问了其中的一个线程同步方法,其它线程就不能同时访问这个对象中任何一个线程同步方法。

   

synchronized的实现方式

synchronized的类型可以分为两种:

  1. synchronized method

  2. synchronized block

两者的实现方式是不一样的,jvm规范中写道,编译后的synchronized method 会有一个ACC_SYNCHRONIZED 的flag,也就是说当jvm的方法调用指令(the method invocation instruction)从the run-time contant pool 中查找到这个method的时候,已经知道它是一个synchronized method,所以锁操作是由方法调用以及返回指令来控制的。
而 synchronized block 的锁是由 monitorenter 和 monitorexit 这两个指令来控制。

 

可以通过 javap 命令来“反汇编”一下class文件。

java代码:

 public class StaticMethodTest {  

  public static synchronized void staticMethod() { }

  public static void staticMethod1() {

    synchronized (StaticMethodTest.class) {
    // ...
    }
  }

  public void memberMethod() { }

  }

 

}

编译成class文件后执行

  1. javap -verbose StaticMethodTest  

两个静态方法的输出结果分别为

  1. public static synchronized void staticMethod();  

  2.   descriptor: ()V  

  3.   flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED  

  4.   Code:  

  5.     stack=0, locals=0, args_size=0  

  6.        0: return  

  7.     LineNumberTable:  

  8.       line 2: 0  

 

 

  1. public static void staticMethod1();  

  2.   descriptor: ()V  

  3.   flags: ACC_PUBLIC, ACC_STATIC  

  4.   Code:  

  5.     stack=2, locals=2, args_size=0  

  6.        0: ldc           #2                  // class StaticMethodTest  

  7.        2: dup  

  8.        3: astore_0  

  9.        4: monitorenter  

  10.        5: aload_0  

  11.        6: monitorexit  

  12.        7: goto          15  

  13.       10: astore_1  

  14.       11: aload_0  

  15.       12: monitorexit  

  16.       13: aload_1  

  17.       14: athrow  

  18.       15: return  

  19.     Exception table:  

  20.        from    to  target type  

  21.            5     7    10   any  

  22.           10    13    10   any  

 

其实不管静态方法还是成员方法,synchronized的实现方式都是一样的 

 

 

类和对象

 

首先要了解的就是类究竟是怎么来的。 

JVM拿到编译器编译好的class文件后,首先会把文件载入到内存中,class文件当然会有自己的格式,所以需要由ClassLoader来解析文件的内容,这个解析出来的内容会用一个Class类的实例 - Class object 来表示,这个object可以通过Java的  ClassName.class 来获取。

也就是说,Class object 是一个Class类型的实例(instance),而对象是一个ClassName的instance。Class 和 ClassName都是类型,ClassName是由class关键字定义的,而Class是内置类型。

因此成员方法的synchronized method 就等价于 synchronized (this) block,即下面两种方式是等价的。

 

  1. public synchronized void fun1() {  

  2.     // do something here  

  3. }  

  4.  

  5. public synchronized void fun2() {  

  6.     synchronized (this) {  

  7.     // do something here  

  8.     }  

  9. }  

成员方法是属于this,而静态方法是属于Class Object,那么静态方法的 synchronized method也就等价于下面这种形式的 synchronized block 了。

 

  1. public static synchronized void fun2() {  

  2.     synchronized (ClassName.class) {  

  3.     // do something here  

  4.     }  

  5. }  

 

验证代码

 定义了三个静态方法,分别采用不同的锁机制,并且每个方法都是一个死循环。然后再定义三个线程分别执行调用三个方法。

 

  1. import java.util.concurrent.*;  

  2.  

  3. public class SynchronizedTest {  

  4.  

  5.     private static Object lock = new Object();  

  6.  

  7.     public synchronized static void fun() {  

  8.         while (true) {  

  9.             System.out.println("in fun");  

  10.         try {  

  11.             TimeUnit.MILLISECONDS.sleep(500);  

  12.         } catch (InterruptedException e) {  

  13.             e.printStackTrace();  

  14.         }  

  15.     }  

  16.     }  

  17.   

  18.   

  19.     public static void fun1() {  

  20.         synchronized (SynchronizedTest.class) {  

  21.             while (true) {  

  22.                 System.out.println("in fun1");  

  23.                 try {  

  24.                     TimeUnit.MILLISECONDS.sleep(500);  

  25.             } catch (InterruptedException e) {  

  26.                     e.printStackTrace();  

  27.             }  

  28.             }  

  29.         }  

  30.     }  

  31.   

  32.   

  33.     public static void fun2() {  

  34.         synchronized (lock) {  

  35.             while (true) {  

  36.                 System.out.println("in fun2");  

  37.                 try {  

  38.                     TimeUnit.MILLISECONDS.sleep(500);  

  39.             } catch (InterruptedException e) {  

  40.                     e.printStackTrace();  

  41.             }  

  42.             }  

  43.         }  

  44.     }  

  45.   

  46.   

  47.     public static void main(String[] args) {  

  48.         new Thread() {  

  49.         @Override  

  50.         public void run() {  

  51.             fun();  

  52.         }  

  53.     }.start();  

  54.   

  55.   

  56.         new Thread() {  

  57.         @Override  

  58.         public void run() {  

  59.             fun1();  

  60.         }  

  61.     }.start();  

  62.   

  63.   

  64.         new Thread() {  

  65.         @Override  

  66.         public void run() {  

  67.             fun2();  

  68.         }  

  69.     }.start();  

  70.     }  

  71. }  

运行结果就是 in fun 和 in fun2 交替出现,就是没有 in fun1。 
只要运行fun的线程不交出锁,fun1就无法被方法,因为他们都是共享了 Class object 的锁。

 

1. 类锁:在代码中的方法上加了static和synchronized的锁,或者synchronized(xxx.class)的代码段,如下文中的increament();

2.对象锁:在代码中的方法上加了synchronized的锁,或者synchronized(this)的代码段,只在当前类中有效。

3.私有锁:在类内部声明一个私有属性如private Object lock,在需要加锁的代码段synchronized(lock),如下文中的synMethodWithObj()。

2. synchronized methods(){} 与synchronized(this){}之间没有什么区别,只是 synchronized methods(){} 便于阅读理解,
而synchronized(this){}可以更精确的控制冲突限制访问区域,有时候表现更高效率。

3.synchronized关键字是不能继承的 也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。
继承类需要你显式的指定它的某个方法为synchronized方法;

 

posted @ 2015-09-28 08:48  南极山  阅读(171)  评论(0)    收藏  举报