Java线程学习(一)

  • Java使用Thread类代表线程,所有的线程对象都必须是Tread类或其子类的实例。每条线程的作用是完成一定的任务,实际上就是执行一段程序流。Java使用run方法来封装这样的一段程序流。
    通过继承Thread类来创建并启动多线程的步骤如下:
  1. 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就是代表了线程需要完成的任务。因此我们经常把run方法称为线程执行体。
  2. 创建Thread子类的实例,即使创建了线程对象
  3. 用线程对象的start方法来启动该线程
public class FirstThread extends Thread{
        private int i;
        public void run(){
            for(;i < 100; i ++){
                //当线程类继承Thread类时,可以直接调用getName()方法来返回当前线程的名字
                //如果想获取当前线程,直接使用this即可
                //Thread对象的getName()返回该线程的名字
                System.out.println(getName() + "" + i);
            }
        }
        public static void main(){
                for(int i = 0; I < 100; i++){
                        System.out.println(Thread.currentThread().getName() + "" + i);
                        if (i == 20){
                                new FirstTread().start();
                                new FirstTread().start();
                        }
                }
        }
}
Java线程学习(一)_第1张图片
Thread.JPG

注意一:Thread-0 和Thread -1两条线程输出的i变量不连续,i变量是FirstThread的实例属性,而不是局部变量,但是因为程序每次创建线程对象时都需要创建一个FirstThread对象,所以不共享该实例属性。使用继承Thread类的方法来创建线程类,多条线程之间无法共享线程类的实例变量。
注意二:启动线程应该使用start方法,而不是run方法。永远不要调用线程对象的run方法。调用start方法来启动线程,系统会把该run方法当成线程执行体来处理。但如果直接调用线程对象的run方法,系统指挥把线程对象当作一个普通的对象,而run方法也只是普通方法,而不是线程执行体。

package com.imooc;

public class MyThread extends Thread{
    private int i;
    public void run(){
        for (; i < 100; i++){
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
    
    public static void main(String[] args){
        for (int i = 0; i < 100; i++){
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i ==20){
                new MyThread().run();
                new MyThread().run();
            }
        }
    }
}

  • 实现Runnable接口创建线程
    创建Runnable对象作为线程对象的target。使用这种方法时,两条线程的i变量是连续的,因为两条线程使用的是同一个target,即同一个Runnable对象。
public class SecondThread implements Runnable {

    private int i;

    @Override
    public void run() {
        // TODO Auto-generated method stub
        for(; i < 100; i ++){
            System.out.println(Thread.currentThread().getName() + " " + i);  //注意三
        }
    }

    public static void main(String[] args){
        for(int i = 0; i < 100; i++){
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 20){
                SecondThread st = new SecondThread();
                new Thread(st, "新线程1").start();
                new Thread(st, "新线程2").start();
            }
        }
    }
}

注意三:
使用Runnable接口时,必须使用Thread.currentThread.getName()方法来查看当前线程的名字

  • 线程的生命周期
  1. 新建:new关键字创建了一个线程后,该线程就处于新建状态。此时和其他Java对象一样,仅仅由虚拟机为其分配了内存,并初始化了成员变量的值。没有表现出任何线程的动态特征。
  2. 就绪:当线程对象调用了start()方法后,该线程就处于就绪状态,Java虚拟机会为其创建方法调用栈,程序计数器,但是此时还没有运行,只是代表着该线程可以运行了。至于何时运行,取决于JVM里线程调度器的调度。
  3. 运行:如果就绪的线程获得了CPU,开始执行run方法的线程执行体,此时线程处于运行状态
  4. 阻塞:
    发生以下情况时,线程进入阻塞:
  • 线程调用sleep方法主动放弃处理器资源
  • 等待资源时
  • 程序调用了线程的suspend方法将该线程挂起(容易导致死锁)
针对以上的情况,发生特定的情况将可以接触上面的阻塞,让该线程重新进入就绪状态:
- 调用的sleep方法经过了指定的时间
- 等待的资源已获得
- 处于挂起状态的线程被调用了resume()回复方法。
  1. 死亡:我们可以通过调用线程对象的isAlive()方法来检测线程是否死亡,当线程处于就绪,运行,阻塞时,该方法返回true;处于新建,死亡时,该方法返回false。注意,不要试图对一个已经死亡的线程调用start()方法,否则会产生IllegalThreadStateException异常。
    线程会以以下三种方式之一结束,结束后就处于死亡状态;
  • run()方法执行完成,线程正常结束
  • 线程抛出一个未捕获的Exception或Error
  • 直接调用该线程的stop()来结束该线程-该方法容易导致死锁
  • 控制线程
  1. join线程:当在某个程序执行流中调用A线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的A线程完成为止。
  2. 后台线程:这种线程在后台运行,他的任务是为其他的线程提供服务。如果所有的前台线程死亡,后台线程会自动死亡。调用Thread对象的setDaemon(true)方法可将指定线程设置成后台线程,但是注意,setDeamon(true)必须在start()方法前调用,否则IllegalThreadStateException异常。
  3. 线程睡眠sleep()方法:通过调用Thread类的静sleep方法,可以让当前正在执行的线程暂定指定的时间,并进入阻塞状态Thread.sleep(1000);,在之后,线程自动进入就绪状态。
  4. 线程让步yield方法:这个方法也是Thread提供的一个静态方法,它将正在执行的线程自动转入就绪状态(但不阻塞)。当某个线程调用了yield方法暂停后,线程调度器完全可能再次将其调用出来执行。但是,当某个线程调用了yield()方法暂停后,只有优先级大于、等于当前线程的就绪状态的线程才会获得执行的机会。
  5. 改变线程的优先级:`thread.setPriority(MAX_PRIORITY)

你可能感兴趣的:(Java线程学习(一))