JAVA之线程学习

一、进程线程

1、进程

进程是一个具有独立功能的应用程序,能够申请和分配系统资源。程序本身是没有生命的,只有处理器赋予其执行权力时,程序才能够成为一个活动的实体,我们称之为进程。 正在运行的程序,是系统进行资源分配和调用的独立单位。 每个进程都有着它自己的内存空间和系统资源。

多进程的作用不是提高执行速度,而是提高CPU使用率


2、线程

线程是进程的单个顺序控制流,是一条执行路径。一个进程中有一条执行路径,是单进程,而一个进程中有多条执行路径,是多进程。多个进程相互独立,多个线程共享进程资源。不同进程中的线程相互不可见。线程依赖于进程,每个线程都有进程带来的各自独立的资源。

多线程的作用是提高抢占资源的几率


并行与并发的区别

并行是逻辑上同时发生的,指在某个时间段内同时运行多个程序

并发是物理上同时发生的,指在某个时间点同时运行多个程序


二、线程的五种状态

线程和进程一样分为五个阶段:新建、就绪、运行、阻塞、死亡



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

2、就绪状态(Runnable):
线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。

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

4、阻塞状态(Blocked):
阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

    阻塞的情况分三种:

(1)、等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。 进入这 个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,



(2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。


(3)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状        态超时、join ()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。


5、死亡状态(Dead):

线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

三、Thread类,Runable接口

这里继承Thread类的方法是比较常用的一种,如果说你只是想起一条线程。没有什么其它特殊的要求,那么可以使用Thread,下面来看一个简单的实例

[java] view plain copy
  1. package com.multithread.learning;  

  2. class Thread1 extends Thread{  
  3.     private String name;  
  4.     public Thread1(String name) {  
  5.        this.name=name;  
  6.     }  
  7.     public void run() {  
  8.         for (int i = 0; i < 5; i++) {  
  9.             System.out.println(name + "运行  :  " + i);  
  10.             try {  
  11.                 sleep((int) Math.random() * 10);  
  12.             } catch (InterruptedException e) {  
  13.                 e.printStackTrace();  
  14.             }  
  15.         }  
  16.          
  17.     }  
  18. }  
  19. public class Main {  
  20.   
  21.     public static void main(String[] args) {  
  22.         Thread1 mTh1=new Thread1("A");  
  23.         Thread1 mTh2=new Thread1("B");  
  24.         mTh1.start();  
  25.         mTh2.start();  
  26.   
  27.     }  
  28.   
  29. }  
输出结果:

A运行  :  0,B运行  :  0,A运行  :  1,A运行  :  2,A运行  :  3,A运行  :  4,B运行  :  1,B运行  :  2,B运行  :  3,B运行  :  4

说明:
程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用MitiSay的两个对象的start方法,另外两个线程也启动了,这样,整个应用就在多线程下运行。


注意:start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。
从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。
Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。


采用Runnable也是非常常见的一种,我们只需要重写run方法即可。下面也来看个实例。

[java] view plain copy
  1.   
  2. package com.multithread.runnable;  
  3. class Thread2 implements Runnable{  
  4.     private String name;  
  5.   
  6.     public Thread2(String name) {  
  7.         this.name=name;  
  8.     }  
  9.   
  10.     @Override  
  11.     public void run() {  
  12.           for (int i = 0; i < 5; i++) {  
  13.                 System.out.println(name + "运行  :  " + i);  
  14.                 try {  
  15.                     Thread.sleep((int) Math.random() * 10);  
  16.                 } catch (InterruptedException e) {  
  17.                     e.printStackTrace();  
  18.                 }  
  19.             }  
  20.           
  21.     }  
  22.       
  23. }  
  24. public class Main {  
  25.   
  26.     public static void main(String[] args) {  
  27.         new Thread(new Thread2("A")).start();  
  28.         new Thread(new Thread2("B")).start();  
  29.     }  
  30.   
  31. }  
输出结果:

A运行  :  0,B运行  :  0,B运行  :  1,A运行  :  1,B运行  :  2,A运行  :  2,B运行  :  3,A运行  :  3,B运行  :  4,A运行  :  4

说明:
Thread2类通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。
在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。

三、Thread和Runnable的区别

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

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

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

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

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


四、线程调度

假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?

线程有两种调度模型:
    分时调度模型 : 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片

    抢占式调度模型 :  优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。 

Java使用的是抢占式调度模型。

注意:

    线程默认优先级是:5,   优先级范围是1~10。

    线程优先级高仅仅表示线程获取的CPU时间片的几率高,但是要在次数比较多或多次运行的时候才能看到比较好的效果

Thread类有以下三个静态常量

  1. static int MAX_PRIORITY  
  2.           线程可以具有的最高优先级,取值为10。  
  3. static int MIN_PRIORITY  
  4.           线程可以具有的最低优先级,取值为1。  
  5. static int NORM_PRIORITY  
  6.           分配给线程的默认优先级,取值为5。

public final int getPriority()    返回线程对象的优先级

public final void setPriority(int newPriority)     更改线程的优先级


public static void sleep(long millis)  线程休眠

public final void join()  线程加入

public static void yield() 线程礼让(暂停当前正在执行的线程对象,执行其他线程,让对个线程的执行更和谐,不能保证一人一次)

public final void setDaemon(boolean on) 后台线程 (将该线程标记为守护线程或用户线程,当正在运行的线程都是守护线程是java虚拟机退出) 该方法必须在启动线程前调用

public final void stop() 线程停止

public void interrupt() 中断线程


线程的状态转换图



五、线程安全问题

首先想为什么出现问题?(也是我们判断是否有问题的标准)
   1、是否是多线程环境
   2、是否有共享数据
   3、是否有多条语句操作共享数据

如何解决多线程安全问题呢

    把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。

1、同步代码块
            格式:
synchronized(对象){需要同步的代码;}

            同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。

2、同步方法

            在方法上加上 synchronized 

3、锁机制

        为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

        ReentrantLock()

        void lock() 加锁 (对象调用)

        void unlock()解锁

六、同步与异步

同步

   线程同步是多个线程同时访问同一资源,等待资源访问结束,浪费时间,效率低    

异步

   线程异步:访问资源时在空闲等待时同时访问其他资源,实现多线程机制


同步的前提

   多个线程使用的是同一个锁对象

同步的好处

   同步的出现解决了多线程的安全问题。

同步的弊端

   当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。效率低,如果出现了同步嵌套,就容易产生死锁问题

        死锁是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象


异步处理就是,你现在问我问题,我可以不回答你,等我用时间了再处理你这个问题.同步不就反之了,同步信息被立即处理

最后,有兴趣研究多线程的推荐直接看java的源码,你将会得到很大的提升!

阅读更多

没有更多推荐了,返回首页