多线程(一)Runnable 和 Thread

一个程序只有一个进程,而一个进程可以包含多个线程,所以进程只能是并发,而线程可以并行。 进程是操作系统中资源分配的基本单位,同一进程的线程间可以共享所属进程的资源,在运行期间,线程才是操作系统的调度和分派的基本单位。同时,操作系统在创建、撤销及切换线程的时候,开销会比进程小。线程在状态转换过程中,可以调用Java API提供的某些方法来改变线程运行的状态。如下图。

下面来介绍一下影响线程运行状态的相关方法。

一、Runnable

public interface Runnable {
    public abstract void run();
}

可以看出Runable只有一个run(),而且还是抽象的,并没有实现,因此Tread类要实现run()。或者自己写的类实现Runable接口,也要实现重写run()。

二、Thread

 1、run()

public class Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }
    
    private char        name[];    //该线程的名字为数组,要想用String,需要转换
    private int         priority;    //设置线程优先级
    private Runnable    target;    //Runable对象
    private ThreadGroup group;    //该线程所属线程组
    private ClassLoader contextClassLoader;    //该线程上下文类加载器
    private boolean     daemon = false;    //该线程是否为守护进程
    private long        tid;    //线程ID
    private volatile int threadStatus = 0;    //线程状态
    private long stackSize;        //线程栈大小,如果创建者没有指定栈大小,就会有JVM自己分配
    private static long threadSeqNumber;    //线程ID自动生成,因为static初始化为1,后面每生成一个Thread,就会++threadSeqNumber,所以会自动生成

    @Override
    public void run() {
        if (target != null) {
            target.run();    //只是调用,并没有让他start
        }
    }
}

Thread实现了Runable接口,但是他的run()其实也是调用Runable的run()方法,所以用户如果继承Thread类,还是要自己重写run()。结果就是无论用户继承Thread类还是实现Runable接口都要重写run(),因为它并不会调用任何东西,需要把开启的方法放进去

对于registerNatives(),是对所有的native方法的注册,因为Thread类有很多native方法。而这些native方法的注册是在Thread对象初始化的时候完成的

2、native方法(5个public是外部可以访问的,其他的都是私有的)

(1)//Returns a reference to the currently executing thread object.
    public static native Thread currentThread();    //获取当前cpu正在运行状态的线程的引用

(2)//A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.
    public static native void yield();    //当前线程愿意退出CPU进入就绪状态

(3)//Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers. The thread does not lose ownership of any monitors.
    public static native void sleep(long millis) throws InterruptedException; //使当前运行线程休眠指定时间进入就绪状态,期间不会丢掉锁

(4)private native void start0();    //使线程开始运行

(5)// Just to set the interrupt flag
    private native void interrupt0();    //仅设置中断状态位,并没有使线程中断

(6)private native boolean isInterrupted(boolean ClearInterrupted);    //查看中断状态位,线程并没有中断,并设置是否清除中断状态位

(7)//Tests if this thread is alive. A thread is alive if it has been started and has not yet died.
    public final native boolean isAlive();    //查看线程被启动,且在就绪状态,阻塞状态,运行状态为true,其他状态(未调用start()和已经死亡)返回false

(8)//Returns true if and only if the current thread holds the  monitor lock on the specified object.
    public static native boolean holdsLock(Object obj);    //查看线程是否拥有某个对象的锁

(9)private native void setPriority0(int newPriority);        //设置线程优先级,但不是优先级越高一定被调度,只是被调度的几率大

(10)下面几个方法他们的调用方法都被标注@Deprecated,现在不使用了
private native void stop0(Object o);
private native void suspend0();
private native void resume0();

(1)Thread.currentThread() 返回的是 一个在处在运行状态(正在用CUP)的实例。 只不过呢, 这个实例确实比较特殊。 这个实例是当前运行状态的Thread 的引用。与this还是有区别的。看下下面的函数isAlive(),然后在看文章1,文章2.,

总结:

        1.无论怎么Thread.currentThread()调用isAlive()一直是true,表明:Thread.currentThread()为当前正在运行中的线程。

        2.而this表示new出来的实例对象(类头class的实例),如果该实例对象不是亲自启动start()方法开启自己使自己处于运行状态(只有这种情况Thread.currentThread()==this),其他的(如:其他线程调用他里面的方法或者将该实例包裹在其他线程里面)虽然运行该类的run()方法,但是它不是处于alive的线程(因为它本身未start,那么他就没有处于就绪状态,运行状态,阻塞状态),它只是被调用了而已。看一下new Thread(Runable thread)源码,发现里面并没有使Runable thread进行start()方法调用,只是Thread类本身有个成员变量Runable parent =你传的Runable thread,虽然new出来了,只能说是新线程,但是未开启(alive),可以看最前面那个大图。

(2)对于yield()理解:虽然当前线程(暂时起个名字叫myThread),myThread进入到就绪状态。这时候cpu会从众多的就绪状态里选择,也就是说,myThread线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了。

(3)对于sleep(),必须给定时间:

            1.它总是暂停当前执行的线程,是之进入到阻塞状态,让出cpu给其他线程,但是他的监控状态依然保持者,当指定的时间到了,又会自动变为就绪状态,并等待CPU进一步执行。所以当前线程的实际休眠时间依赖于线程调度器和操作系统。

            2.实际休眠的线程在唤醒开始执行前依赖于系统定时器和调度器,对于一个平稳的系统来住,线程实际执行的时间接近于指定线程休眠时间,但是对于一个忙碌的系统来说它将稍微超出一些。

            3.当线程休眠时不会丢失已经获得的监控和

            4.任何线程都能中断当前休眠的线程(可以调用interrupt()设置中断flag进行中断),将导致InterruptedException异常抛出。而这个异常英文解释是这样:InterruptedException - if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.说明:抛出这个异常,中断flag表示会清除掉。不懂得看下面中断的内容。

(4)对于start0():该方法会调用该线程的run()方法,下面是C的源码:

     class vmSymbolHandles: AllStatic {
        ...
        template(run_method_name,"run") //LOOK!!! 这里决定了调用的方法名称是 “run”!
        ...
     } 

下面是调用start0()的start()方法,被sychronized修饰,同步方法:

public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);    //添加到线程组
        boolean started = false;    //有助于下面判断是否正确开启线程
        try {
            start0();        //主要看这个
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
  }

在外部调用start()方法时,会调用本地方法start0(),而start0()会调用run()。所以你只需要重写run()方法,调用start()方法就行。

下面两个是中断:

(5)对于interrupt0():可以看到源码备注为只设置中断 flag(这个flag又叫中断状态位),并没有使线程中断,下面是调用它的方法:

    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();    ////该方法确定当前运行的线程是否有权修改该线程,如果当前线程不允许访问该线程则抛出SecurityException异常

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

(6)对于isInterrupted(boolean ClearInterrupted):中断状态位将会根据传入的ClearInterrupted参数值来确定是否重置,有两个

方法会调用它(虽然中断状态位为true,但是线程并没有中断)

public boolean isInterrupted() {
    return isInterrupted(false);
}

isInterrupted()的 false说明,查看中断状态时,不清除中断状态位,表明:仅查看中断状态位,其他原样该是啥还是啥。

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

interrupted()的true说明,查看中断状态时,清除中断状态位,表明:查看完中断状态位还要清除中断状态位。

总结(5)(6)可以看出:只有interrupt()才能设置flag(将中断状态说成flag好理解,而且Java实现也就是flag),isInterrupted()只有查看flag功能,interrupted()不仅有查看flag功能还有清除flag功能,但是不管怎样线程不会中断,isInterrupted()和interrupted()返回为true说明有flag,返回false说明没有flag。

(7)对于isAlive():检查线程是否处于存活状态(启动起来的和未死亡的,包括:就绪状态,运行状态,阻塞状态),请看(1)currentThread()里面的内容进行理解。

(8)对于holdsLock(Object obj):判断线程是否持有一个锁对象。

(9)对于setPriority0(int newPriority):对线程进行优先级的设定,使之在就绪状态,有高的概率被调用到运行状态。下面是调用它的原函数:

public final static int MIN_PRIORITY = 1;

public final static int NORM_PRIORITY = 5;

public final static int MAX_PRIORITY = 10;

public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();    //该方法确定当前运行的线程是否有权修改该线程,如果当前线程不允许访问该线程则抛出SecurityException异常
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {    //判断是否在1-10之间
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {    //设置的大于组内最大优先级,则使之为组内最大优先级
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);    //注意这句,就是调用句
        }
    }

  参数newPriority,可以设置为1-10任何一个,也可以设置上面三个常量。

(10)对于stop0(Object o)suspend0(),resume0(),这三个函数已经过时,不建议使用,可以点他们本身链接进行查看。

3、构造方法

构造方法比较多,这里只讲最常用的Thread(),Thread(Runable thread),Thread(String  threadName)这三个

 public Thread() {
        //nextThreadNum()就是从0开始递增的,如果用户没有给线程起名字的话,就会调用系统给的从Thread-0开始的。
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
//这个就是用户给线程起的线程名字
public Thread(String name) {    
        init(null, null, name, 0);
    }
//上面他们两个同时调用的是同一个init
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null);
    }
//最主要是理解这个init
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {    //为null检查
            throw new NullPointerException("name cannot be null");
        }
        this.name = name.toCharArray();    //this.name为char[],所以输入的name为String,需要转换

        Thread parent = currentThread();    //该parent为当前正在运行的线程
        SecurityManager security = System.getSecurityManager();    //获得系统权限
        if (g == null) {        //线程组null检查
            if (security != null) {
                g = security.getThreadGroup();    //获得线程组
            }
            if (g == null) {
                g = parent.getThreadGroup();    //获得线程组
            }
        }
        g.checkAccess();    //检查线程组是否有权限修改,如果没有抛出异常
        if (security != null) {//如果系统权限不为空检查线程权限
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
        g.addUnstarted();    //在线程组里面添加一个未启动的线程数量
        this.group = g;    //将g放到线程本身的线程组里面
        this.daemon = parent.isDaemon();    //设置该线程的守护进程,如果当前运行的线程是则是,不是则不是
        this.priority = parent.getPriority();    //该线程的优先级,优先级为当前运行的线程的优先级
        if (security == null || isCCLOverridden(parent.getClass()))//设置该线程的ContextClassLoader
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;    //设置该线程的Runable为传进来的Runable,否则就没有
        setPriority(priority);    //将优先级设置进去
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        this.stackSize = stackSize;    //设置线程栈大小,如果参数用有,就设置进去,如果没有就有JVM自己设置
        tid = nextThreadID();    //设置线程ID,nextThreadID()每次调用都会++threadSeqNumber
    }

   对于他的所有构造,实际上都是调用最后的init方法,对它的成员变量进行初始化,注意Runable target,里面并没有启用它,只是将她赋值给this.target。

4、其他方法

(1)join()

//这个是大家经常用的join方法,其实是调用参数为0的第二个join方法
/*Waits for this thread to die.*///停止当前运行线程,运行调用join方法的线程,并等它到死,其他线程才能开始
public final void join() throws InterruptedException {
        join(0);
    }
//注意:该方法有synchronized会进行同步
public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {    //这个就是经常调用的join()实现
            while (isAlive()) {    
                wait(0);
            }
        } else {            //其他的有时间的join()
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

(2)还有重写的toString()、getState()、getId()、getName()等不经常用可以自己去看源码。

猜你喜欢

转载自blog.csdn.net/xiao1_1bing/article/details/81198667