一、进程是什么?线程是什么?二者有什么区别和联系?

  • 进程是操作系统中资源分配的基本单位;

  • 线程是CPU独立运行和独立调度的基本单位;


区别归纳:

  • 地址空间和其它资源:
    进程间相互独立,具有独立的空间地址,同一进程的各线程间共享。
    某进程内的线程在其它进程不可见。

  • 通信:
    进程间通信IPC(如信号、管道),
    线程间可以直接读写进程数据段(如全局变量)来进行通信。
    (需要进程同步和互斥手段的辅助,以保证数据的一致性)

  • 调度和切换:
    线程上下文切换比进程上下文切换要快得多。

  • 在多线程OS中,进程不是一个可执行的实体。


联系:

线程是一个进程的不同执行路径,一个线程只能属于一个进程,而一个进程有1~n个线程。


深入理解参考:

http://www.cnblogs.com/tiankong101/p/4229584.html


二、什么是多线程

多线程:

是指从软件或者硬件上实现多个线程的并发技术。

多线程是指在同一程序中有多个顺序流在执行。


多线程的好处:

  • 使用多线程可以把程序中占据时间长的任务放到后台去处理,
    如图片、视频的下载

  • 发挥多核处理器的优势,并发执行让系统运行的更快、更流畅,用户体验更好

多线程的缺点:

  • 大量的线程降低代码的可读性

  • 更多的线程需要更多的内存空间

  • 当多个线程对同一个资源出现争夺时候要注意线程安全的问题


三、线程同步

  • 线程同步就是线程排队。同步就是排队。
    线程同步的目的就是避免线程“同步”执行。

  • “共享”这两个字。只有共享资源的读写访问才需要同步。
    如果不是共享资源,那么就根本没有同步的必要。

  • 只有“变量”才需要同步访问。如果共享的资源是固定不变的,那么就相当于“常量”,线程同时读取常量也不需要同步。至少一个线程修改共享资源,这样的情况下,线程之间就需要同步。

  • 多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码;无论是否执行同一份代码,只要这些线程的代码访问同一份可变的共享资源,这些线程之间就需要同步。


深入理解参考:

http://blog.csdn.net/ysughw/article/details/9980791


四、java实现多线程

1. 继承Thread类

class MyThread extends Thread{
private String name;
    public MyThread(String name) {
       this.name=name;
    }
public void run() {
        //运行代码
}
}
public class Main {

public static void main(String[] args) {
MyThread mTh=new MyThread("ziry");
mTh.start();//注意不是run()!!
}

}


2. 实现Runable接口

class MyThread implements Runnable{
private String name;

public MyThread(String name) {
this.name=name;
}

@Override
public void run() {
  //执行代码
}

}
public class Main {

public static void main(String[] args) {
new Thread(new MyThread("ziry")).start();
}

}


注意:start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。


实现Runnable接口比继承Thread类所具有的优势:

  • 适合多个相同的程序代码的线程去处理同一个资源

  • 可以避免java中的单继承的限制

  • 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立



五、线程状态转换


  1. 新建状态(New):新创建了一个线程对象。

  2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

  3. 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
    (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
    (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

  5. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。


六、线程调度

调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。

Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:

  • static int MAX_PRIORITY

              线程可以具有的最高优先级,取值为10。

  • static int MIN_PRIORITY

              线程可以具有的最低优先级,取值为1。

  • static int NORM_PRIORITY

              分配给线程的默认优先级,取值为5。

 

Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。

 

每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。

线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。

JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。

 

  • 线程睡眠:
    Thread.sleep(long millis)方法,使线程转到阻塞状态。
    millis参数设定睡眠的时间,以毫秒为单位。
    当睡眠结束后,就转为就绪(Runnable)状态。
    sleep()平台移植性好。

  • 线程等待:
    Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。

  • 线程让步:
    Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。

  • 线程加入
    join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。

  • 线程唤醒:
    Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。


 注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。


详细参考:

http://www.mamicode.com/info-detail-517008.html

http://www.cnblogs.com/wxd0108/p/5479442.html


七、线程类的一些常用方法:

  • sleep():
        强迫一个线程睡眠N毫秒。 

  • isAlive():
        判断一个线程是否存活。 

  • join():
        等待线程终止。 

  • activeCount():
        程序中活跃的线程数。 

  • enumerate():
        枚举程序中的线程。 

  • currentThread():
        得到当前线程。 

  • isDaemon():
        一个线程是否为守护线程。 

  • setDaemon():
        设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束) 

  • etName():
        为线程设置一个名称。 

  • wait():
        强迫一个线程等待。 

  • notify():
        通知一个线程继续运行。 

  • setPriority():
        设置一个线程的优先级。


八、多线程相关类

1. Callable 接口

Callable 和 Runnable 的使用方法大同小异, 区别在于: 

  1. Callable 使用 call() 方法, Runnable 使用 run() 方法 

  2. call() 可以返回值, 而 run()方法不能返回。 

  3. call() 可以抛出受检查的异常,比如ClassNotFoundException, 而run()不能抛出受检查的异常。 


Callable示例如下: 

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableImpl implements Callable<String> {   
    private String acceptStr;
     
    public CallableImpl(String acceptStr) {        
        this.acceptStr = acceptStr;
    }    

    @Override    
    public String call() throws Exception {        
        // 任务阻塞 1 秒
        Thread.sleep(1000);       
        return this.acceptStr + " append some chars and return it!";
    }    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<String> callable = new CallableImpl("my callable test!");
        FutureTask<String> task = new FutureTask<>(callable);
        // 创建线程        
        long beginTime = System.currentTimeMillis();       
        new Thread(task).start();
        // 调用get()阻塞主线程,反之,线程不会阻塞         
        String result = task.get();        
        long endTime = System.currentTimeMillis();
        System.out.println("hello : " + result);
        System.out.println("cast : " + (endTime - beginTime) / 1000 + " second!");
    }
}


2.ThreadLocal类

用处:保存线程的独立变量。对一个线程类(继承自Thread)

当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session信息。


实现:每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。功能还是一个map。)以本身为key,以目标为value。

主要方法是get()和set(T a),set之后在map里维护一个threadLocal -> a,get时将a返回。ThreadLocal是一个特殊的容器。


ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:

  • void set(Object value)
        设置当前线程的线程局部变量的值。

  • public Object get()
        该方法返回当前线程所对应的线程局部变量。

  • public void remove()
        将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

  • protected Object initialValue()
        返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。


3.原子类(AtomicInteger、AtomicBoolean……

如果使用atomic wrapper class如atomicInteger,或者使用自己保证原子的操作,则等同于synchronized


AtomicReference

对于AtomicReference 来讲,也许对象会出现,属性丢失的情况,即oldObject == current,但是oldObject.getPropertyA != current.getPropertyA。

这时候,AtomicStampedReference就派上用场了。这也是一个很常用的思路,即加上版本号


import java.util.concurrent.atomic.AtomicInteger;  
import sun.misc.Unsafe;  
  
public class TestAtomic {  
  
    /** 
     * @param java中的原子操作类AtomicInteger 
     * @author yangcq 
     *  
     * 关于AtomicInteger的说明(来自官方文档注解) 
     * /** 
     * An {@code int} value that may be updated atomically.  See the 
     * {@link java.util.concurrent.atomic} package specification for 
     * description of the properties of atomic variables. An 
     * {@code AtomicInteger} is used in applications such as atomically 
     * incremented counters, and cannot be used as a replacement for an 
     * {@link java.lang.Integer}. However, this class does extend 
     * {@code Number} to allow uniform access by tools and utilities that 
     * deal with numerically-based classes. 
     * 
     * @since 1.5 
     * @author Doug Lea 
     */  
    public static void main(String[] args) {  
        // 初始值为1  
        AtomicInteger atomicInteger = new AtomicInteger(1);  
        System.out.println("--初始值atomicInteger = " + atomicInteger);  
          
        // 以原子方式将当前值加1,注意这里返回的是自增前的值   
        System.out.println("atomicInteger.getAndIncrement() = " + atomicInteger.getAndIncrement());  
        System.out.println("--自增后的 atomicInteger = " + atomicInteger);  
          
        // 以原子方式将当前值减1,注意这里返回的是自减前的值   
        System.out.println("atomicInteger.getAndIncrement() = " + atomicInteger.decrementAndGet());  
        System.out.println("--自减后的 atomicInteger = " + atomicInteger);  
          
        // 以原子方式将当前值与括号中的值相加,并返回结果  
        System.out.println("atomicInteger.getAndIncrement() = " + atomicInteger.addAndGet(10));  
        System.out.println("--自减后的 atomicInteger = " + atomicInteger);  
          
        // 如果输入的值等于预期的值,则以原子方式将该值设置成括号中的值  
        System.out.println("atomicInteger.getAndIncrement() = " + atomicInteger.compareAndSet(1, 2));  
        System.out.println("--自减后的 atomicInteger = " + atomicInteger);  
        System.out.println("atomicInteger.getAndIncrement() = " + atomicInteger.compareAndSet(11, 9999));  
        System.out.println("--自减后的 atomicInteger = " + atomicInteger);  
          
        /** 
         * 一,AtomicInteger 是如何实现原子操作的呢? 
         *  
         * 我们先来看一下getAndIncrement的源代码: 
         *    public final int getAndIncrement() { 
         *        for (;;) { 
         *            int current = get();  // 取得AtomicInteger里存储的数值 
         *            int next = current + 1;  // 加1 
         *            if (compareAndSet(current, next))   // 调用compareAndSet执行原子更新操作 
         *                return current; 
         *        } 
         *    } 
         *  
         * 这段代码写的很巧妙: 
         * 1,compareAndSet方法首先判断当前值是否等于current; 
         * 2,如果当前值 = current ,说明AtomicInteger的值没有被其他线程修改; 
         * 3,如果当前值 != current,说明AtomicInteger的值被其他线程修改了,这时会再次进入循环重新比较; 
         *     
         * 注意这里的compareAndSet方法,源代码如下: 
         * public final boolean compareAndSet(int expect, int update) { 
         *     return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
         * } 
         *  
         * 调用Unsafe来实现 
         * private static final Unsafe unsafe = Unsafe.getUnsafe(); 
         *  
         * 二,java提供的原子操作可以原子更新的基本类型有以下三个: 
         *  
         * 1,AtomicBoolean 
         * 2,AtomicInteger 
         * 3,AtomicLong 
         *  
         * 三,java提供的原子操作,还可以原子更新以下类型的值: 
         *  
         * 1,原子更新数组,Atomic包提供了以下几个类:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray 
         * 2,原子更新引用类型,也就是更新实体类的值,比如AtomicReference<User> 
         * AtomicReference:原子更新引用类型的值 
         * AtomicReferenceFieldUpdater:原子更新引用类型里的字段 
         * AtomicMarkableReference:原子更新带有标记位的引用类型 
         * 3,原子更新字段值 
         * AtomicIntegerFieldUpdater:原子更新整形的字段的更新器 
         * AtomicLongFieldUpdater:原子更新长整形的字段的更新器 
         * AtomicStampedReference:原子更新带有版本号的引用类型的更新器 
         *  
         *  
         */  
    }  
  
}



4. Lock类

lock: 在java.util.concurrent包内。共有三个实现:

ReentrantLock
ReentrantReadWriteLock.ReadLock
ReentrantReadWriteLock.WriteLock

主要目的是和synchronized一样, 两者都是为了解决同步问题,处理资源争端而产生的技术。功能类似但有一些区别。


首先要说明的就是Lock,通过查看Lock的源码可知,Lock是一个接口:

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

Lock接口中每个方法的使用,

lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来获取锁的。

unLock()方法是用来释放锁的。


区别如下:

Lock是Java 5以后引入的新的API,和关键字synchronized相比主要相同点:

  • Lock 能完成synchronized所实现的所有功能;

主要不同点:

  • Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

  • synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

  • Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

  • 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

  • Lock可以提高多个线程进行读操作的效率。

示例:

Lock lock = ...;
lock.lock();
try{
    //处理任务
}catch(Exception ex){
 
}finally{
    lock.unlock();   //释放锁
}
Lock lock = ...;
if(lock.tryLock()) {
     try{
         //处理任务
     }catch(Exception ex){
 
     }finally{
         lock.unlock();   //释放锁
     } 
}else {
    //如果不能获取锁,则直接做其他事情
}

详细参考:http://www.importnew.com/18320.html


5. Condition 

Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set (wait-set)。在Condition中,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll(),传统线程的通信方式,Condition都可以实现,这里注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。

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;//如果写索引写到队列的最后一个位置了,那么置为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;//如果读索引读到队列的最后一个位置了,那么置为0  
       --count;//个数--  
       notFull.signal();//唤醒写线程  
       return x;  
     } finally {  
       lock.unlock();  
     }  
   }   
 }


详细参考:http://blog.csdn.net/ghsau/article/details/7481142



6. 线程池

多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。 

   

假设一个服务器完成一项任务所需时间为:
T1 创建线程时间,
T2 在线程中执行任务的时间,
T3 销毁线程时间。


如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。


一个线程池包括以下四个基本组成部分:

  1. 线程池管理器(ThreadPool):
        用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;

  2. 工作线程(PoolWorker):
        线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;

  3. 任务接口(Task):
        每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;

  4. 任务队列(taskQueue):
        用于存放没有处理的任务。提供一种缓冲机制。

                

线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。

线程池不仅调整T1,T3产生的时间段,而且它还显著减少了创建线程的数目,



Java通过Executors提供四种线程池,分别为:

  • newCachedThreadPool
        创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

  • newFixedThreadPool
        创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

  • newScheduledThreadPool
        创建一个定长线程池,支持定时及周期性任务执行。

  • newSingleThreadExecutor
        创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。


示例:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
for (int i = 0; i < 10; i++) {
	final int index = i;
	fixedThreadPool.execute(new Runnable() {
		public void run() {
			try {
				System.out.println(Thread.currentThread()+"="+ index);
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	});
}

详细参考:http://blog.csdn.net/hsuxu/article/details/8985931



注意:本文归作者所有,未经作者允许,不得转载