码迷,mamicode.com
首页 > 编程语言 > 详细

java之多线程

时间:2016-04-26 21:41:22      阅读:193      评论:0      收藏:0      [点我收藏+]

标签:

当一个程序进入内存运行时,即变成一个进程。进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。

进程的三个特性:独立性,动态性,并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。

并发性是指同一时刻只有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。并行在同一时刻,多条指令在多个处理器上同时执行。

多线程扩展了多进程,使得同一个进程可以同时并发处理多个任务。线程是进程的执行单元。
进程可以拥有多个线程,一个线程必须有一个父线程。

线程拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不拥有系统资源,它与父进程的其他线程共享所拥有的全部资源。

线程是独占式的,当前运行的线程在任何时候都可能被挂起,以便另外一个线程可以运行。

通过继承Thread类来创建并启动多线程步骤:
1、定义Thread的子类,重写run()方法(线程执行体)。
2、创建Thread子类的实例,创建线程对象。
3、调用线程对象的start()方法。
使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。

实现Runnable接口创建并启动多线程的步骤:
1、定义Runnable接口的实现类,并重写该接口的run()方法(线程执行体)。
2、创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
程序创建的Runnable对象只是线程的target,而多个线程可以共享一个target,所以多个线程可以共享同一个线程类的实例变量。

使用Callable和Future创建并启动有返回值的线程的步骤:
1、创建Callable接口的实现类并实现call()方法(线程执行体),且该call()方法有返回值,再创建Callable的实例。(可以直接用Lambda表达式创建Callable对象)
2、使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值
3、调用FutureTask对象作为Thread对象的target创建并启动新线程。
4、调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。the get methods will block if the computation has not yet completed.

Callable是Runnable的增强版,是个函数式接口,call()方法提供了返回值而且可以抛出异常。

Interface Future< V> - The result type returned by this Future’s get method
A Future represents the result of an asynchronous computation. Methods are provided to check if the computation is complete, to wait for its completion, and to retrieve the result of the computation.

public interface RunnableFuture< V> extends Runnable, Future< V>

FutureTask< v>extends Object implements RunnableFuture< V>
A cancellable asynchronous computation.
因为实现了Runnable接口,所以可以作为Thread的target。

new Thread(task, “有返回值的线程1”).start();
new Thread(task2, “有返回值的线程2”).start();
这个和Runnable还是区别很大的。

The Executors class contains utility methods to convert from other common forms to Callable classes.

使用Runnable、Callable接口的方式创建多线程的好处:
1、线程类只是实现了Runnable、Callable接口,还可以继承其他类。
2、多个线程可以共享一个target对象,所以非常适合多个相同线程来处理同一份资源的情况。

线程的生命周期有五个:新建、就绪、运行、阻塞、死亡。
他们之间的相互关系如图所示:
技术分享

如果希望调用子线程的start()方法后子线程立即开始执行,程序可以使用Thread.sleep(1)让当前现场休眠即可。因为这1毫秒CPU不会空闲,去执行另一个处于就绪状态的线程,这样就可以让子线程立即开始执行。

public final boolean isAlive()
新建、死亡状态返回false;就绪、运行、阻塞状态返回true。
public final void join() throws InterruptedException
被join线程必须在该线程执行完之后才能执行。
public final void setDaemon(boolean on)
将线程设置为后台线程。当只有后台线程运行时,JVM就会退出。
public static void sleep(long millis) throws InterruptedException
当前正在执行的线程暂停一段时间,并进入阻塞状态。The thread does not lose ownership of any monitors.
public static void yield()
让当前线程暂停一下,让线程调度器重新调度一次,完全有可能再次调用这个线程执行一遍。
public final void setPriority(int newPriority)
改变线程优先级

线程开始执行同步代码块之前,必须先获得对同步监视器的锁定,任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,线程会释放对监视器的锁定。
监视器的目的就是阻止两个线程对同一个共享资源进行并发访问,因此通常推荐使用可能被并发访问的共享资源充当同步监视器。

对于synchronized修饰的实例方法,同步方法的监视器就是this,也就是调用该方法的对象。

只对会改变共享资源的方法进行同步。
如果可变类有两种运行环境:单线程环境和多线程环境,应该为可变提供两种版本,线程安全版和线程不安全版。如单线程环境下用StringBuilder,多线程环境下用StringBuffer.

线程释放同步监视器:
1、线程的同步方法、同步代码块执行结束。
2、线程的同步方法、同步代码块遇到break、return终止了方法、代码块的继续进行。
3、线程在执行同步方法、同步代码块时出现了未处理的Error或Exception。
4、线程在执行同步方法、同步代码块时,程序执行了同步监视器对象的wait()方法。
不会释放同步监视器:
Thread.sleep()、Thread.yield()、Thread.suspend()

同步锁对象Lock比同步方法、同步代码块操作更加灵活,有很多不同的属性,支持相关的Condition对象。
一般情况下,锁提供了对共享资源的独此访问,每次只有一个线程对Lock加锁,线程开始访问共享资源之前应该先获得Lock对象。但是有的锁提供了对共享资源的并发访问。如ReadWriteLock。

当获取多个锁时,必须以相反的顺序释放,且必须在与所有锁被获取时相同的范围内释放所有锁。
ReetrantLock具有可重入性,一个线程可以对已被加锁的ReetrantLock再次加锁,ReetrantLock对象会维持一个计数器来追踪lock()方法的嵌套调用,线程在每次调用lock()后,必须显示调用unlock()释放锁,所以一段被锁保护的代码可以调用另一个被相同所保护的方法。

ReetrantLock用法:

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

当两个线程相互等待对方释放同步监视器时就会发生死锁。

wait()、notify()、notifyAll()是Object类的三个方法

synchronized修饰的同步方法,this就是监视器,所以直接用这三个方法。
synchronized修饰的同步代码块,监视器是括号里的对象,必须用这个对象调用这三个方法。

wait():当前线程等待,直到notify()或者notifyAll()来唤醒该线程。调用wait()方法会释放监视器。
notify():唤醒在此同步监视器上等待的单个线程。选择是任意性的。
notifyAll():唤醒在此同步监视器上等待的所有线程。

如果不使用synchronized关键字,直接使用Lock对象保证同步,系统中就没有隐士的同步监视器,也就不能使用以上三个方法了。在这种情况下,Lock代替了同步方法和同步代码块,Condition代替了同步监视器。
Condition的三个方法分别是:wait()、notify()、notifyAll()。

 class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length)
         notFull.await();
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0)
         notEmpty.await();
       Object x = items[takeptr];
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   }
 }

BlockingQueue也是用来作为线程同步的工具的。
技术分享

 class Producer implements Runnable {
   private final BlockingQueue queue;
   Producer(BlockingQueue q) { queue = q; }
   public void run() {
     try {
       while (true) { queue.put(produce()); }
     } catch (InterruptedException ex) { ... handle ...}
   }
   Object produce() { ... }
 }

 class Consumer implements Runnable {
   private final BlockingQueue queue;
   Consumer(BlockingQueue q) { queue = q; }
   public void run() {
     try {
       while (true) { consume(queue.take()); }
     } catch (InterruptedException ex) { ... handle ...}
   }
   void consume(Object x) { ... }
 }

 class Setup {
   void main() {
     BlockingQueue q = new SomeQueueImplementation();
     Producer p = new Producer(q);
     Consumer c1 = new Consumer(q);
     Consumer c2 = new Consumer(q);
     new Thread(p).start();
     new Thread(c1).start();
     new Thread(c2).start();
   }
 }

线程池在系统启动时创建大量空闲的线程,程序将一个Runnable或Callable对象传给线程池,线程池就会启动一个线程来执行它们的run()或call()方法。结束后线程不会死亡,再次返回线程池成为空闲状态,等待下一个run()或call()方法。
Executor是接口,Executors是类,用来创建ExecutorService。
使用线程池来执行线程任务的步骤如下:
1、调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池。
2、创建Runnable实现类或Callable实现类,作为线程执行任务。
3、调用ExecutorService对象的submit()方法来提交Runnable实例或Callable实例。
4、当不想提交任务时调用ExecutorService对象的shutdown()方法来关闭线程池。

Here is a sketch of a network service in which threads in a thread pool service 
incoming requests. It uses the preconfigured Executors.newFixedThreadPool(int) 
factory method: 

 class NetworkService implements Runnable {
   private final ServerSocket serverSocket;
   private final ExecutorService pool;

   public NetworkService(int port, int poolSize)
       throws IOException {
     serverSocket = new ServerSocket(port);
     pool = Executors.newFixedThreadPool(poolSize);
   }

   public void run() { // run the service
     try {
       for (;;) {
         pool.execute(new Handler(serverSocket.accept()));
       }
     } catch (IOException ex) {
       pool.shutdown();
     }
   }
 }

 class Handler implements Runnable {
   private final Socket socket;
   Handler(Socket socket) { this.socket = socket; }
   public void run() {
     // read and service request on socket
   }
 }

终止线程池:

The following method shuts down an ExecutorService in two phases, first by 
calling shutdown to reject incoming tasks, and then calling shutdownNow, if 
necessary, to cancel any lingering tasks: 

 void shutdownAndAwaitTermination(ExecutorService pool) {
   pool.shutdown(); // Disable new tasks from being submitted
   try {
     // Wait a while for existing tasks to terminate
     if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
       pool.shutdownNow(); // Cancel currently executing tasks
       // Wait a while for tasks to respond to being cancelled
       if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
     }
   } catch (InterruptedException ie) {
     // (Re-)Cancel if current thread also interrupted
     pool.shutdownNow();
     // Preserve interrupt status
     Thread.currentThread().interrupt();
   }
 }

Java提供了一种特殊的线程池ForkJoinPool。它支持将一个任务拆分成多个“小任务”放在多个处理器上并行计算,再把多个“小任务”合并成总的计算成果。
技术分享
RecursiveAction和RecursiveTask是两个抽象类,区别是RecursiveAction执行没有返回值的任务,RecursiveTask执行有返回值的任务。
它们都有一个抽象方法 protected abstract V compute()

Sample Usages. Here is a simple but complete ForkJoin sort that sorts a given 
long[] array: 


 static class SortTask extends RecursiveAction {
   final long[] array; final int lo, hi;
   SortTask(long[] array, int lo, int hi) {
     this.array = array; this.lo = lo; this.hi = hi;
   }
   SortTask(long[] array) { this(array, 0, array.length); }
   protected void compute() {
     if (hi - lo < THRESHOLD)
       sortSequentially(lo, hi);
     else {
       int mid = (lo + hi) >>> 1;
       invokeAll(new SortTask(array, lo, mid),
                 new SortTask(array, mid, hi));
       merge(lo, mid, hi);
     }
   }
   // implementation details follow:
   static final int THRESHOLD = 1000;
   void sortSequentially(int lo, int hi) {
     Arrays.sort(array, lo, hi);
   }
   void merge(int lo, int mid, int hi) {
     long[] buf = Arrays.copyOfRange(array, lo, mid);
     for (int i = 0, j = lo, k = mid; i < buf.length; j++)
       array[j] = (k == hi || buf[i] < array[k]) ?
         buf[i++] : array[k++];
   }
 }

ThreadLocal
线程局部变量为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程可以独立的改变自己的副本而不会与其他线程冲突。
他有三个常用方法:get()、set(T value)、remove();
同步机制是为了同步多个线程对相同资源的并发访问,是多个线程之间进行通信的有效方式;ThreadLocal是为了隔离多个线程的数据共享,从根本上避免多个线程之间共享资源的竞争。
一般多个线程之间如果需要共享资源,以达到线程之间的通信功能,就使用同步机制;如果仅仅需要隔离多个线程之间的共享冲突,就可以使用ThreadLocal。

java之多线程

标签:

原文地址:http://blog.csdn.net/yeshiwu/article/details/51233571

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!