`
yjz20031
  • 浏览: 879 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

浅谈多线程的使用

阅读更多
1、要讲java多线程,首先要说什么是线程,我们先来说说进程的概念,进程是系统资源分派和调度的基本单位,需要在内存为进程分配空间。像运行exe文件就会在系统生成一个对应的进程。线程存在于进程,是进程中实际执行代码的部分,一个进程可能只有一个主线程也可能有多个线程,线程是cpu分派和调度的基本单位。像运行java程序,生成一个进程,而实际执行main代码的部分就是主线程部分。
2、java中如何使用多线程编程,java为多线程封装了一个类Thread,生成线程有两种方式。
1)继承Thread类
public class MyThread extends Thread{
  public MyThread(String threadName){
    super(threadName);
  }
  public void run(){
    System.out.println(Thread.currentThread()+":run");
  }
  public static void main(String[] args){
    MyThread myThread=new MyThread();
    myThread.start();
  }
}
 

2)实现Runnable接口
public class MyRunnable implements Runnable{
  public void run(){
    System.out.println("I run");
  }
  public static void main(String[] args){
     MyRunnable myRunnable=new MyRunnable();
     Thread thread=new Thread(myRunnable);
     thread.start();
  }   
}

两种实现最后都是调用Thread类或其派生类的start方法开启线程,如果是继承Thread的类,在调用start方法后,在开启的线程中会调用Thread类的run()方法,所以如果继承Thread类的话,只要在派生类中重写run()方法并在方法中加入需要在线程中执行的代码。如果直接调用run()方法的话就是普通的方法调用,并没有生成线程。如果是实现Runnable接口的话,从代码中可以看出实现类的实例会作为参数传给Thread对象,然后调用Thread对象的start方法,所以这种方式下开启线程还是执行的是Thread类的run()方法,然后再run()方法中调用Runnable实现类的run()方法,这里有点像代理模式,Thread类是Runnable实现类的代理。
Runnable的实现类的同一个对象可以传给多个Thread对象。
MyRunnable myRunnable=new MyRunnable();
Thread thread1=new Thread(myRunnable);
Thread thread2=new Thread(myRunnable);
....
thread1.start();
thread2.start();
.....

但是在实际应用中,一个MyRunnable对象一般只是给一个线程使用,所以我们在实际开发中这样的代码更加常见
new Thread(new Runnable{
  public void run(){
    .....
  }}.start();

什么时候用实现Runnable接口,什么时候继承Thread类呢
1.由于java的单继承,如果继承了Thread类,就不能继承其他类,所以需要继承其他类的时候需要实现Runnable接口。
2.一般来说能用继承Thread的地方都能用实现Runnable接口,所以有时为了减少生成的类,比如后面使用实现Runnable的方式,所以个人认为尽量用Runnable,而且如果用线程池,Runnable实现类的对象可以直接被线程池的接口类ExecutorService执行。 
  java1.5引进了java.util.concurrent包,引入了很多用于并发处理的类,这里主要讲讲线程池相关的类和接口。
ExecutorService接口,继承自Executor接口,
这里主要讲我们平时在项目中经常用到的几个方法
void execute(Runnable command);执行Runnable任务
<T> Future<T> submit(Callable<T> task);执行Callable任务,与Runnable任务的最大区别是它有返回值
Future<?> submit(Runnable task);执行Runnable任务,与execute没有多大区别,虽然有返回值,但是返回对象Future中取得的值为null
void shutdown();如果执行了,线程池不再接受新任务,但是会执行完已经提交的任务。
ExecutorService的实现类ThreadPoolExecutor,
它有个构造函数
  public ThreadPoolExecutor(int corePoolSize,//核心线程数
                              int maximumPoolSize,//最大线程数
               long keepAliveTime,//线程空闲时间,即线程空闲多少时间久关闭线程
                              TimeUnit unit,//时间单位
                        BlockingQueue<Runnable> workQueue//任务接收队列

通过传入不同的参数可以得到不同的线程池对象。线程池总的原则是如果当前线程数小于核心线程数,则当有任务到来的时候,无论当前是否有空闲线程,都开启新的线程来执行任务;如果当前线程数大于等于核心线程数,但是任务接受队列没满,则将任务加入接受队列,等待线程执行,如果任务接受队列满了,而且当前线程数小于最大线程数,则开启新的线程来执行任务。如果任务接收队列满了,而且当前线程数等于最大线程数,则执行策略,或重试,或抛弃任务等。
java.util.concurrent包下有个帮助类Executors类,它提供了返回ThreadPoolExecutor对象的方法。
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

从调用的构造函数可以看出,当调用newCachedThreadPool()得到线程池处理对象时,核心线程数是0,最大线程数不限,所以只要有任务到来,而由于核心线程数是0,所以当前线程数必定大于等于核心线程数,所以要将其放入任务接收队列,这里要详细说说SynchronousQueue。
SynchronousQueue其实不算是个队列,因为它的容量是0,为什么说它的容量是0呢,如果有线程往该队列放入任务,如果没有其它线程正从队列中取任务,则放入任务失败。这里取任务操作是个阻塞方法,如果单独有线程要取任务,则该线程阻塞,直到有其它线程往队列放任务,此时放任务的线程放任务成功,取任务的线程解除阻塞,取任务成功。放任务的线程不会阻塞,只是没有线程取任务的话,放任务将失败,即无法将任务放入接收任务队列。
下面看举例代码,加深对该队列的理解
public class MyTest2 {
	class A extends Thread{
		private SynchronousQueue<Integer> q;
		public A(SynchronousQueue<Integer> q){
			this.q=q;
			
		}
		public void run(){
			System.out.println("before offer1");
			System.out.println(q.offer(1));
			System.out.println("after offer1");
		}
	}
	class B extends Thread{
		private SynchronousQueue<Integer> q;
		public B(SynchronousQueue<Integer> q){
			this.q=q;
		}
		public void run(){
			try {
				System.out.println("before take1");
				System.out.println(q.take());
				System.out.println("after take1");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	class C extends Thread{
		private SynchronousQueue<Integer> q;
		public C(SynchronousQueue<Integer> q){
			this.q=q;
		}
		public void run(){
			try {
				System.out.println("before take2");
				System.out.println(q.take());
				System.out.println("after take2");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[]args){
		MyTest2 m=new MyTest2();
		SynchronousQueue<Integer> q=new SynchronousQueue<Integer>();
		A a=m.new A(q);
		B b=m.new B(q);
		C c=m.new C(q);
		b.start();
		c.start();
		a.start();
	}


执行结果
before take1
before take2
before offer1
true //表明往队列放数据成功
after offer1
1
after take2
有b,c两个线程再取数据,只有a线程放数据,所以放数据线程能放数据成功,而只有一个线程能取数据成功解除阻塞,只有after take2,表明c线程解除阻塞,取数据成功,而b线程继续阻塞。
newCachedThreadPool生成的线程池,当有任务到来的时候,如果没有空闲线程,则此时没有线程从接收任务队列取任务,所以往队列放任务失败,根据线程池总的原则,如果放任务失败,而当前线程数又小于最大线程数的时候,则开启新的线程执行任务。如果有空闲线程,则空闲线程试图从接收任务队列中取任务,所以在有任务到来的时候,往接收任务队列放任务将成功,放的任务将被空闲线程取走执行。
   public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }


  这里核心线程数等于最大线程数,也就是传入的线程数,线程池将维护指定数目的线程。这里时间参数为0,表示无乱线程空闲多少时间都不会将其关闭。当有任务到来的时候,如果有空闲线程,则用空闲线程来执行任务,如果线程都忙着,查看当前线程数是否小于核心线程,如果小于则开启新线程,否则则将其加入等待队列,这里的等待队列是LinkedBlockingQueue结构,这是个链表结构的队列,无论加入多少任务,都不会满
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

这个和fixed的线程池差不多,只是核心线程数变成了1,所以加入的任务,根据加入的顺序用单线程来执行。
下面看下用线程池来执行Runnable任务的代码
	MyRunnable myRunnable=new MyRunnable();
	ExecutorService executorService=null;
         executorService=Executors.newCachedThreadPool();
	executorService.execute(myRunnable);
	executorService=Executors.newFixedThreadPool(10);
	executorService.execute(myRunnable);
	executorService=Executors.newSingleThreadExecutor();
	executorService.execute(myRunnable);

线程池提供了比原始多线程使用更多的功能和更方便的使用,而且也更加高效,所以在实际项目中尽量用线程池来开启新线程执行代码。
0
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics