多线程理解(四) 线程

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

二、线程的状态:

  1. 初始(NEW):新建了一个线程,但是还没有调用start(),这时候就是初始状态;
  2. 运行(RUNNABLE):JAVA线程中将就绪(ready)和运行中(running)两种状态统称为“运行”。线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程处于可运行线程池中,等待被线程调度选中,获取cpu的使用权,此时就是处于就绪状态(ready)。就绪状态的线程在获取cpu时间片后变成运行中(running)状态;
  3. 阻塞(BLOCKED):表示当前线程被锁阻塞了;
  4. 等待(WAITTING):进入该状态的线程需要等待其他线程做出某些特定的动作(通知或者中断);
  5. 超时等待(TIME_WAITTING):该状态不同于WAITTING,它可以在特定时间内自行返回。
  6. 终止(TERMINATED):表示该线程已经执行完毕。

三、线程的创建:

  1. 通过实现Runnable接口的方法,代码如下:
class RunnableDemo implements Runnable {
    private Thread t;
    private String threadName;
    RunnableDemo( String name) {
        threadName = name;
        System.out.println("Creating " +  threadName );
    }
    @Override
    public void run() {
        System.out.println("Running " +  threadName );
    }
    public void start () {
        System.out.println("Starting " +  threadName );
        if (t == null) {
            t = new Thread (this, threadName);
            t.start ();
        }
    }
}
class TestThread {
    public static void main(String args[]) {
        RunnableDemo R1 = new RunnableDemo( "Thread-1");
        R1.start();

        RunnableDemo R2 = new RunnableDemo( "Thread-2");
        R2.start();
    }
}

 

2.通过继承Thread类本身,代码如下:

class RunnableDemo extends Thread {
    private Thread t;
    private String threadName;
    RunnableDemo( String name) {
        threadName = name;
        System.out.println("Creating " +  threadName );
    }
    @Override
    public void run() {
        System.out.println("Running " +  threadName );
    }
    @Override
    public void start () {
        System.out.println("Starting " +  threadName );
        if (t == null) {
            t = new Thread (this, threadName);
            t.start ();
        }
    }
}
class TestThread {
    public static void main(String args[]) {
        RunnableDemo R1 = new RunnableDemo( "Thread-1");
        R1.start();

        RunnableDemo R2 = new RunnableDemo( "Thread-2");
        R2.start();
    }
}

将实现Runnable接口改成继承Thread类就行了。

3.通过 Callable 和 Future 创建线程:

  • 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值;
  • 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值;
  • 使用FutureTask对象作为Thread对象的target创建并启动新线程;
  • 使用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

代码如下:

public class CallableThreadTest implements Callable<Integer> {
    public static void main(String[] args) {
        CallableThreadTest ctt = new CallableThreadTest();
        FutureTask<Integer> ft = new FutureTask<Integer>(ctt);
        for(int i = 0;i < 100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
            if(i==20)
            {
                new Thread(ft,"有返回值的线程").start();
            }
        }
        try
        {
            System.out.println("子线程的返回值:"+ft.get());
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        } catch (ExecutionException e)
        {
            e.printStackTrace();
        }
    }
    @Override
    public Integer call() throws Exception {
        int i = 0;
        for(;i<100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
        return i;
    }
}

创建线程的三种方式的对比:

  1. 采用实现Runnable、Callable接口的方式创建多线程时,线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
  2. 使用继承Thread类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

 

四、线程的相关方法:

  1. Thread类的一些方法(被Thread对象调用的):
    1. public void start();使该线程开始执行;Java虚拟机调用该线程的run方法。
    2. public void run();如果该线程是使用独立的Runnable运行对象构造,则调用该Runnable对象的run方法;否则,该方法不执行任何操作并返回。
    3. public final void join(long millisec) 等待该线程终止的时间最长为millisec毫秒。
    4. public void interrupt() 中断线程。
  2. Thread的静态方法:
    1. public static void yield()  暂停当前正在执行的线程对象,并执行其他线程。
    2. Public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

五、线程的状态图

 

 

  1. 初始状态:实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入初始状态。
  2. 就绪状态:
    1. 就绪状态只是说你有资格运行,调度程序没有挑选到你,你永远是就绪状态。
    2. 调用线程的start()方法,就进入到就绪状态。
    3. 当前线程sleep结束,其他线程join结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也都进入就绪状态。
    4. 当前线程时间片用完,调用当前线程的yeild()方法,当前线程进入就绪状态。
    5. 锁池里的线程拿到对象锁后,就进入就绪状态。
  3. 运行中状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一方式。
  4. 阻塞状态:阻塞状态是线程在进入由synchronized修饰的方法或者块被阻塞的一种状态。
  5. 终止状态:
    1. 当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。
    2. 在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

六、等待队列(本是object里的方法,但却影响了线程)

       等待队列存放着的是待唤醒的线程的引用。

  1. 调用obj的wait(),notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内。
  2. 与等待队列相关的步骤和图

七、同步队列

  1. 当前线程想调用对象A的同步方法时,发现对象A的锁被其他对象占有,此时当前线程进入到同步队列。简言之,同步队列里放的是想争夺对象锁的线程。
  2. 当一个线程1被另外一个线程2唤醒时,1线程进入同步队列,去争夺对象锁。
  3. 同步队列是在同步的环境下才有的概念,一个对象对应一个同步队列。

八、几个方法的比较

  1. Thread.sleep(long millis),一定是当前线程调用此方法,当线程进入TIME_WAITTING状态,但不释放对象锁,mills后线程自动苏醒进入就绪状态。作用:给其他线程执行机会的最佳方式。
  2. Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的cpu时间片,由运行状态变回就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。
  3. t.join/t.join(long millis)当前线程调用其他线程t的join()方法,当前线程进入到TIME_WAITTING状态,当前线程不释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程进入就绪状态。
  4. obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。
  5. obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。

九、线程的几个主要概念

在多线程编程时,你需要了解以下几个概念:

  1. 线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,目前实现线程同步的方法有很多,临界区对象就是其中一种。
  2. 线程间通信:多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。
  3. 线程死锁:这种情况可能发生在当两个线程尝试获取其他资源的锁,而每个线程又陷入无线等待其他资源锁的释放,除非一个用户的进程被终止。 
  4. 线程控制:挂起,停止和恢复

 

十、多线程的使用:

有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。通过对多线程编程的使用,可以编写出非常高效的程序。不过注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。因为上下文切换的开销也很重要,如果你创建太多线程,CPU花费在上下文的切换的时间将多于执行程序的时间!

猜你喜欢

转载自blog.csdn.net/linjiaen20/article/details/81188661