首页 > 代码库 > java多线程

java多线程

1      什么是进程,线程

进程:

进程是程序执行的一个实例。它是一个动态的概念。比如说,10个用户同时执行IE,那么就有10个独立的进程(尽管他们共享同一个可执行代码)。

进程的特点:

每一个进程都有自己的独立的一块内存空间(独立的堆和栈,不共享堆栈)、一组资源系统。进程由操作系统调度, 进程间的切换会有较大的开销。

守护进程:

在系统的引导的时候会开启很多服务,这些服务就叫做守护进程,随系统关闭时终止。

线程:

线程是程序中一个单一的顺序控制流程。

线程的优点:

同类的多个线程共享一块内存空间和一组系统资源,线程本身的数据通常只有CPU的寄存器数据,以及一个供程序执行时的堆栈。线程在切换时负荷小,因此,线程也被称为轻负荷进程。一个进程中可以包含多个线程。

  

线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。

多进程是指操作系统能同时运行多个任务(程序)。

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

并发执行指在同一时间内执行多个程序。但是从严格上讲,也不是绝对的同一时刻执行多个程序,只不过CPU在执行时通过时间片等调度算法不同进程高速切换。

CLR:公共语言运行库(Common Language Runtime)和Java虚拟机一样也是一个运行时环境

程序:

程序是一个存储在计算机系统的硬盘等存储空间中的静态文件。

 

PS:进程是一个动态的概念,是程序的一个运行实例,

程序是静态文件

 

Java中的进程通过ProcessBuilder.start()创建一个process.本次不过过多介绍。

 

2      Java多线程

2.1  线程实现

继承Thread

定义一个线程类,它继承类Thread并复写run()方法。Thread类中的相关函数可以启动线程、终止线程、线程挂起等。由于Java只支持单一继承,这种方法定义的类不能再继承其它的类了。

实现Runnable

新建一个类,实现Runable接口,重写 run()方法。这种方法定义的类仍然可以继承其它的类。

实现Callable

Java SE5中引入Callable,可以从方法call()中返回函数执行的值,并且必须使用ExecutorService.submit()方法调用它。submit()方法会产生Future对象,Future拿到了程序执行的返回值。

推荐使用Runable(Callable)的方式实现多线程(其实 Thread 只是实现 Runnable 接口)。

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

1): 适合多个相同的程序代码的线程去处理同一个资源(比较容易实现资源共享)

2):可以避免java中的单继承的限制

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

PS: 在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。

2.2  线程状态

线程的状态有创建、就绪、运行、阻塞、终止

         1.创建状态(NewBorn)

         Thread thread = new Thread();来创建线程对象

                 仅仅是一个空的线程对象,系统不为它分配资源。

2.就绪状态()

调用start方法将线程的状态转换为就绪状态。线程已经得到除CPU时间之外的其它系统资源,只等JVM的线程调度器按照线程的优先级进行调度,从而使该线程拥有能够获得CPU时间片的机会。

         3.运行状态(Running)

         当线程获得CPU时间片后,它才进入运行状态,真正开始执行run()方法。

4.阻塞状态(Blocked)

        所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。

1>线程通过调用sleep方法进入睡眠状态;
       2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
       3>线程试图得到一个锁,而该锁正被其他线程持有;
       4>线程在等待某个触发条件;      

  ......               

        5.消亡状态(Dead)

         一般两种方法:

        1).线程执行完毕,自然销毁

        2). 一个未捕获的异常终止了run方法而使线程猝死。或者调用thread.stop()或thread.interrupt()等方法中断线程;

 技术分享

 

  

2.3  线程调度

线程调度是JVM根据线程的优先级在运行的多个线程进行系统级的协调的行为。避免多个线程争用有限资源而导致应用系统死机或者崩溃。

线程优先级:

Java线程用整数表示,取值范围是1~10,数字越大表明线程的级别越高,在创建线程对象之后可以调用线程对象的setPriority/getPriority方法设置/获取该线程的运行优先级。

Thread类有以下三个静态常量

static int MAX_PRIORITY

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

static int MIN_PRIORITY

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

static int NORM_PRIORITY

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

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

2.4  线程同步

1.同步方法 

    即用synchronized关键字修饰的方法。 

    由于java的每个对象都有一个内置锁,当用此关键字修饰方法前,需要获得内置锁,否则就处于阻塞状态。

    代码如: 

    public synchronized void save(){}

   注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。如果一个对象中多个方法使用synchronized。则必须等一个方法执行完后释放锁,下一个synchronized才能获得锁执行。

2.同步代码块 

    即有synchronized关键字修饰的语句块。 

    被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

    代码如: 

    synchronized(object){ 

    }

    注:同步是一种高开销的操作,因此应该尽量减少同步的内容。 

    通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

3.使用局部变量实现线程同步 

    如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 副本之间相互独立,这样每一个线程都可以通过set,get方法随意修改自己的变量副本,而不会对其他线程产生影响。

 注:    在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。 

    ReentrantLock可以通过lock(),unlock()实现同步,但会大幅度降低程序运行效率,不推荐使用 

2.5  线程数据传递

1、 通过构造方法传递数据 

适用于简单参数传递。

2、 通过变量和方法传递数据 

创建public的方法或变量,通过set方法来设置变量(传递复杂数据,集合、类等建议使用): 

3、通过回调函数传递数据 (线程运行的过程中动态地获取数据)

 技术分享

 

2.6   线程间通信

同步

这里讲的同步是指多个线程通过synchronized关键字这种方式来实现线程间的通信。

参考示例:

 

public class MyObject {
   synchronized public void methodA() {
      //do something....
  
}
   synchronized public void methodB() {
      //do some other thing
  
}
}

public class ThreadA extends Thread {
   private MyObject object;
   //省略构造方法
  
@Override
   public void run() {
      super.run();
      object.methodA();
   }
}

public class ThreadB extends Thread {
   private MyObject object;
   //省略构造方法
  
@Override
   public void run() {
      super.run();
      object.methodB();
   }
}

public class Run {
   public static void main(String[] args) {
      MyObject object = new MyObject();
      //线程A与线程B 持有的是同一个对象:object
     
ThreadA a = new ThreadA(object);
      ThreadB b = new ThreadB(object);
      a.start();
      b.start();
   }
}

while轮询的方式

假如有两个线程,ThreadA不断地改变条件,线程ThreadB不停地通过while语句检测这个条件是否成立 ,从而实现了线程间的通信。但是这种方式会浪费CPU资源。JVM调度器将CPU交给线程B执行时,它没做啥“有用”的工作,只是在不断地测试 某个条件是否成立。

wait/notify机制

public class ThreadA extends Thread {
   private Object lock;
   public ThreadA(Object lock) {
      this.lock = lock;
   }
   @Override
   public void run() {
      synchronized (lock) {
         if (!condition) {
            lock.wait();
         }
      }
   }
}

public class ThreadB extends Thread {
   private Object lock;
   public ThreadB(Object lock) {
      this.lock = lock;
   }
   @Override
   public void run() {
      synchronized (lock) {
         if (condition) {
            lock.notify();
         }
      }
   }
}

线程A条件不满足时,调用wait() 放弃CPU,并进入阻塞状态。当线程B条件满足时调用 notify()通知 线程A,并让它进入可运行状态。

2.7   多线程的安全问题

出现安全问题的原因:多个线程执行共享数据的代码块时,其中的一个线程还没有执行完代码块,另一个线程就开始执行代码块,这会造成共享数据的错误。从而出现安全问题。

多线程安全问题的解决:同步。因为同步可以保证多线程代码只能被持有锁的线程运行,其它线程不能运行。任意时刻,一个锁只能被一个线程拥有。(持有锁的线程会在执行完多线程代码时释放锁,这样锁就能被其它线程拥有。)

 

2.8   线程并发

Java并发

Java1.5后增加了Executors框架用来处理线程并发:

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

 

Spring并发

ThreadPoolTaskExecutor(继承Executor)

两种初始化方式:

通过new创建一个对象,通过对象的set方法设置线程数量等,然后初始化。

通过配置文件配置相应的属性,定义变量进行注入。

<bean id="threadPoolTaskExecutor"  

    class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> 

    <property name="corePoolSize" value="http://www.mamicode.com/10" />  

    <property name="maxPoolSize" value="http://www.mamicode.com/15" />  

    <property name="queueCapacity" value="http://www.mamicode.com/1000" />  

</bean> 

 

利用线程池启动线程

 技术分享

 

 

使用线程池的好处

1.减少在创建和销毁线程上所花的时间以及系统资源的开销 
    2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。

 

Spring和java并发对比:

 

3      附录

Runable资源共享例子

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

 技术分享

 

输出:

 技术分享

 

从上面可以看出,不同的线程之间count是不同的,这对于卖票系统来说就会有很大的问题,当然,这里可以用同步来作。这里我们用Runnable来做下看看

技术分享

 


输出:

 技术分享

 

这里要注意每个线程都是用同一个实例化对象,如果不是同一个,效果就和上面的一样了!

 

java多线程