Java高并发程序设计(二)-多线程基础

一,什么是线程

要解释线程,就必须明白什么是进程。

    进程是指运行中的应用程序,每个进程都有自己独立的地址空间(内存空间),比如用户点击桌面的IE浏览器,就启动了一个进程,操作系统就会为该进程分配独立的地址空间。当用户再次点击左面的IE浏览器,又启动了一个进程,操作系统将为新的进程分配新的独立的地址空间。目前操作系统都支持多进程。

要点:用户每启动一个进程,操作系统就会为该进程分配一个独立的内存空间。

 

线程(线程是进程内的执行单元):

    是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。线程有就绪、阻塞和运行三种基本状态。

命令:

列出进程:jps

列出线程:jstack pid
二,线程的生命周期
Java中的线程的生命周期大体可分为5种状态。
1. 新建(NEW):新创建了一个线程对象。
2. 可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
3. 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
4. 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
5. 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生

线程生命周期状态图

初始状态:
  1. 实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态
可运行状态:
  1. 可运行状态只是说你资格运行,调度程序没有挑选到你,你就永远是可运行状态。
  2. 调用线程的start()方法,此线程进入可运行状态。
  3. 当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入可运行状态。
  4. 当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入可运行状态。
  5. 锁池里的线程拿到对象锁后,进入可运行状态。
运行状态:
  1. 线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
死亡状态:
  1. 当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。
  2. 在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
阻塞状态:
  1. 当前线程T调用Thread.sleep()方法,当前线程进入阻塞状态。
  2. 运行在当前线程里的其它线程t2调用join()方法,当前线程进入阻塞状态。
  3. 等待用户输入的时候,当前线程进入阻塞状态。
等待队列(本是Object里的方法,但影响了线程)):
  1. 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内。
  2. 与等待队列相关的步骤和图
  • 线程1获取对象A的锁,正在使用对象A。
  • 线程1调用对象A的wait()方法。
  • 线程1释放对象A的锁,并马上进入等待队列。
  • 锁池里面的对象争抢对象A的锁。
  • 线程5获得对象A的锁,进入synchronized块,使用对象A。
  • 线程5调用对象A的notifyAll()方法,唤醒所有线程,所有线程进入锁池。||||| 线程5调用对象A的notify()方法,唤醒一个线程,不知道会唤醒谁,被唤醒的那个线程进入锁池。
  • notifyAll()方法所在synchronized结束,线程5释放对象A的锁。
  • 锁池里面的线程争抢对象锁,但线程1什么时候能抢到就不知道了。||||| 原本锁池+第6步被唤醒的线程一起争抢对象锁。
锁池状态:
  1. 当前线程想调用对象A的同步方法时,发现对象A的锁被别的线程占有,此时当前线程进入锁池状态。简言之,锁池里面放的都是想争夺对象锁的线程。
  2. 当一个线程1被另外一个线程2唤醒时,1线程进入锁池状态,去争夺对象锁。
  3. 锁池是在同步的环境下才有的概念,一个对象对应一个锁池。
三,线程的基本操作
(1)创建线程
主要有两种方式:

1)继承Thread类创建线程

2)实现Runnable接口创建线程

通过继承Thread类来创建并启动多线程的一般步骤如下

1】d定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。

2】创建Thread子类的实例,也就是创建了线程对象

3】启动线程,即调用线程的start()方法

代码实例

public class MyThread extends Thread{//继承Thread类

  public void run(){

  //重写run方法

  }

}

public class Main {

  public static void main(String[] args){

    new MyThread().start();//创建并启动线程

  }

}

通过实现Runnable接口创建并启动线程一般步骤如下:

1】定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体

2】创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象

3】第三部依然是通过调用线程对象的start()方法来启动线程

代码实例:

public class MyThread2 implements Runnable {//实现Runnable接口

  public void run(){

  //重写run方法

  }

}

public class Main {

  public static void main(String[] args){

    //创建并启动线程

    MyThread2 myThread=new MyThread2();

    Thread thread=new Thread(myThread);

    thread().start();

    //或者    new Thread(new MyThread2()).start();

  }

}


(2)终止线程

Thread.stop()  不推荐使用,它会立即释放所有monitor(对象或锁),会导致多线程的数据的不一致性,例如写进程突然终止,读进程可能会读到刚才还没有写完的“残破”的数据。

(3)中断线程

public void Thread.interrupt() // 中断线程

public boolean Thread.isInterrupted() // 判断是否被中断

public static boolean Thread.interrupted() // 判断是否被中断,并清除当前中断状态

直接打断,并不能够把该线程中断,因为一直在循环中:

public void run(){

    while(true){

         Thread.yield();    

    }

}

t1.interrupt();


优雅的方法,如果发现有人“打招呼”,就会跳出循环:

public void run(){

    while(true){

        if(Thread.currentThread().isInterrputed()){

             System.out.println("interrupted");

             break;

        }

       Thread.yield();

 }}

设置睡眠,如果抛出异常后会清除中断标记位 ,需要设置中断状态

public static native void sleep(long millis) throws InterruptedException

public void run(){

while(true){

if(Thread.currentThread().isInterrupted()){

System.out.println("Interruted!");

break;

}

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

System.out.println("Interruted When Sleep");

//设置中断状态,抛出异常后会清除中断标记位

Thread.currentThread().interrupt();

}

Thread.yield();

}

}


(4)挂起(suspend)和继续执行(resume)线程

– suspend()不会释放锁

– 如果加锁发生在resume()之前 ,则死锁发生


(5)等待线程结束(join)和谦让(yeild) 
public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedExceptio
yeild:释放当前占用的CPU,并再来竞争(给你机会跟我抢)              
                         
四,守护线程
在后台默默地完成一些系统性的服务,比如垃圾回收线程、 JIT线程就可以理解为守护线程
当一个Java应用内,只有守护线程时,Java虚拟机就会自然退出
Thread t=new DaemonT();
t.setDaemon(true);  //设置为守护线程
t.start();

五,线程优先级
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
例子:
Thread high=new HightPriority();
LowPriority low=new LowPriority();
high.setPriority(Thread.MAX_PRIORITY);
low.setPriority(Thread.MIN_PRIORITY);
low.start();
high.start();
高优先级的线程更容易再竞争中获胜

六,基本的线程同步操作
synchronized
– 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
– 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
– 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。

等待和唤醒


猜你喜欢

转载自blog.csdn.net/hhq12/article/details/80851566