多线程的学习-线程的状态以及创建方法

先看两张线程状态的图



线程总共有以下几个状态

1.新建状态(NEW) 新创建一个线程的对象   如:Thread t = new MyThread();

2.就绪状态(Runnable):线程对象创建后,其他线程让那个调用了该对象的start()方法(t.start();)。这个状态的线程位于“可运行的线程池”中,变成了可运行的状态,获取到cpu的使用权即可运行。简而言之,就绪状态的线程做好了一切运行的准备,只等cpu调度执行了。但是实际上还没开始运行。

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

4.阻塞状态(Blocked)阻塞状态是线程因为某种原因放弃了CPU的使用权,暂时停止运行,进入到阻塞状态。直到线程进入就绪状态(Runnable),才有机会被cpu调度重新回到运行状态,这里说的是有机会,而不是立马就会进入运行状态。

阻塞产生的原因分为三种:

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

(2)同步阻塞:运行的线程在获取对象的同步锁的时候,如果该同步锁被别的线程占用了,那么JVM会将该线程放入“锁池”

中。

(3)其他阻塞通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

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


线程实现的方式

1.继承Thread类

2.实现Runnable接口

不论如何实现,当new了这个对象之后,线程就进入了初始状态;

调用对象的start()方法后,线程进入就绪状态;

就绪状态下的线程对象被操作系统选中之后(获得CPU使用权),该线程进入运行状态(Running);

进入运行状态下的线程:

   (1)run()方法或者main()方法结束后,线程进入终止状态;

   (2)线程调用了自身的说了sleep()方法或者其他线程的join()方法的时候,当前的线程会交出CPU的使用权,进入阻塞状态。sleep()结束或者join()结束后,该线程进入可运行状态,会继续等待操作系统分配cpu时间片(cpu使用权)。

   (3)线程调用yield()方法,即放弃当前的cpu使用权,回到就绪状态,此时当前线程与其他线程都处于竞争状态,等待进入运行状态;

     (4)当线程进入可运行状态,发现将要调用的资源被synchroniza(同步),获取不到锁标记,将会立即进入“锁池”状态,等待获取锁标记。当线程获取到锁标记之后,转入就绪状态,等待被cpu调用。

      (5)suspend()和resume()方法:suspend()方法使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume()被调用,才能使得线程重新进入可执行状态。典型地,suspend()和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume()使其恢复。 

(6)wait()和notify()方法 当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占用的所有资源,与阻塞状不同),进入这个状态之后,必须依靠其他线程调用notify()或者notifuAll()方法才能被唤醒(由于notify()只是唤醒一个线程,但我们由不能确定具体唤醒的是哪一个线程,也许我们需要唤醒的线程不能够被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁池,等待获取锁标记。 

wait()方法使得线程进入阻塞状态,有两种形式

    一直允许指定以毫秒为单位的时间作为参数,另一种没有参数。前者当对应的notif()被调用或者超出指定时间时线程重新进入可执行状态(就绪状态),没有指定时间的则必须对应的notify()被调用。调用wait()之后,线程会释放它占有的“锁标志”,使得线程所在对象中的其他synchronize数据可以被别的线程使用。wait()和notif()因为会对对象的“锁标志”进行操作,所以它们必须在synchronize函数或synchronizedblok中进行调用。


JAVA多线程的创建以及启动

     Java中线程的创建有三种形式:

1.继承Thread类,重写该类的run()方法。

/**
 * @author: sunzhitao
 * @date: 2018/7/14 15:48
 * @description:
 */
public class MyThread extends Thread {
    private int i;

    @Override
    public void run() {
        for (int j = 0; j < 100; j++) {
            System.out.println(Thread.currentThread().getName()+" "+j);

        }
    }
}

使用:

public class ThreadTest {

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"" + i);
            if(i==30){
                //创建一个新的线程,线程处于新建状态
                MyThread myThread1 = new MyThread();
                //创建一个新的线程,线程处于新建状态
                MyThread myThread2 = new MyThread();
                //调用start()方法,线程进入就绪状态
                myThread1.start();
                myThread2.start();
            }

        }
    }
}

继承Thread类,通过重写run()方法定义了一个新的线程类MyThread,run()方法的方法代表了线程需要完成的任务,称为线程执行体。当创建此线程类的对象的时候一个新的线程得以创建,并且进入线程的新建状态。通过调用线程的start()方法,使线程进入就绪状态。

2.实现Runnable接口,重写接口的run()方法,ru()方法同样是线程的执行体,创建Runnable实现类的实例,并且该实例作为Thread类的Target参数来创建Thread对象,此时的Thread对象才是真正的线程对象。举例如下:

/**
 * @author: sunzhitao
 * @date: 2018/7/14 16:00
 * @description:
 */
public class MyRunnable implements Runnable {
    private int i = 0;
    @Override
    public void run() {

        for (int j = 0; j < 100; j++) {
            System.out.println(Thread.currentThread().getName()+" " + j);
        }

    }
}

使用:

public class RunnableThread {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==30){
                //创建Runnable实现类对象
                MyRunnable myRunnable = new MyRunnable();
                //将myRunnable作为Thread Target 创建新的线程
                Thread thread1 = new Thread(myRunnable);
                Thread thread2 = new Thread(myRunnable);
                //调用start()方法使得线程进入就绪状态
                thread1.start();
                thread2.start();
            }

        }
    }
}

3.使用Callable和Future接口创建线程。具体过程

      (1)创建Callable接口的实现类,并且实现call()方法

      (2)使用FutureTask类来包装Callable实现类的对象

      (3)FutureTask对象作为Thread对象的target来创建线程

举例如下:

自定义Callable:

public class MyCallable implements Callable<Integer>{
    private int i = 0;

    /**
     *  call方法具有返回值
     * @return
     * @throws Exception
     */
    @Override
    public Integer call() throws Exception {
        int sum =0;
        for (int j = 0; j < 100; j++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
            sum+=j;
        }
        return sum;
    }
}

使用:

/**
 * @author: sunzhitao
 * @date: 2018/7/14 17:51
 * @description: 使用Callable和Future接口创建线程的方法练习
 */
public class CallableAndFutureThread {
    public static void main(String[] args) {

        //创建MyCallable对象
        Callable<Integer> myCallable = new MyCallable();
        //使用FutureTask来包装Callable对象
        FutureTask<Integer> ft = new FutureTask<>(myCallable);

        for (int i = 0; i < 100; i++) {

            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==30){
                //futureTask对象作为Thread对象的target创建新的线程
                Thread thread = new Thread(ft);
                //线程进入就绪状态
                thread.start();
            }
        }
        System.out.println("======主线程for循环执行完毕======");
        try {
            Integer sum = ft.get();
            System.out.println("sum = " + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }


    }
}

可以看出,在实现Callable接口的过程中,不再是run()方法,而是call()方法,call()方法作为程序执行体,并且还有返回值。创建新的线程的时候,通过FutureTask来包装MyCallable对象,同时作为Thread对象的Targe。事实上,FutureTask类同时实现了Runnable和Future接口,所以有了Runnable和Future的双重特性。可以作为Thread对象的Target,Future特性则使得其可以取得线程中的call()方法的返回值。

程序执行过程中,sum=4950 永远是最后输出的,原因是通过ft.get()方法获取子线程的call()方法的返回值的时候,子线程方法没有执行完的情况下,ft.get()方法会一直阻塞,直到call()方法执行完毕才能取到值。

多线程的就绪、运行、死亡状态转换

就绪状态----->运行状态 :当此线程得到cpu使用权

运行状态----->就绪状态 :当此线程主动调用yield()方法或者在运行的过程中失去CPU使用权。

运行状态----->死亡状态 :当此线程执行体执行完毕或者发生了异常。

注意:当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。

猜你喜欢

转载自blog.csdn.net/sunzhitao1990/article/details/81042125