Java基础Java学习笔记Android 技术开发

java多线程基础

2017-01-12  本文已影响107人  零度沸腾_yjz

线程的五种状态

1、新建状态(New):线程对象创建后,就进入新建状态。
2、就绪状态(Runnable):就绪状态又称可执行状态,线程被创建后通过调用start()方法,从而启动线程。就绪状态的线程,随时有可能被CPU调度运行。
3、运行状态(Running):线程获取CPU权限进行执行。只有就绪状态的线程才能进入运行状态。
4、阻塞状态(Blocked):线程因为某种原因放弃CPU使用权,停止运行。直到线程进入就绪状态,才可以再到运行状态。
阻塞状态三种情况:
(1)、等待阻塞:通过调用线程的wait()方法,让线程等待某工作完成
(2)、同步阻塞:线程获取同步锁synchronized同步锁失败(因为锁正在被其它线程使用),进入同步阻塞。
(3)、其它阻塞:通过调用线程的sleep()、join()或发出I/O请求,线程进入阻塞状态。当sleep()状态超时、join()等待终止或超时、或者I/O处理完毕时,线程重新进入就休状态。
5、死亡状态(Dead):线程正常直行完成或者因为异常原因退出run()方法,该线程生命周期结束。

Paste_Image.png

通过Thread和Runnable创建线程

1、Runnable实现

java.lang.Runnable是一个接口,里面只定义了run()抽象方法。如果要实现多线程,可以实现Runnable接口,然后通过Thread thread = new Thread(new Xxx()),其中Xxx是实现Runnable接口的类。

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

Runnable方式实现多线程

public class RunnableTest implements Runnable{
    int num = 10;
    @Override
    public void run() {
        for(int i=0;i<20;i++){
            if(this.num > 0){
                System.out.println(Thread.currentThread().getName()+" num:" +this.num-- );
            }
        }
    }
}
class Test{
    public static void main(String[] args){
        RunnableTest runnable = new RunnableTest();
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        Thread thread3 = new Thread(runnable);
        thread1.start();//只有start()后才进入就绪状态
        thread2.start();
        thread3.start();
    }
}

运行结果

结论:三个线程共享num资源,共同对num相减十次。

2、Thread实现

java.lang.Thread是一个类,实现了Runnable接口。如果要实现多线程,需要继承Thread类,然后通过创建实现类对象来启动线程。

public class Thread implements Runnable {
    ...
}

Thread方式实现多线程

class ThreadTest extends Thread{
    int num = 10;
    @Override
    public void run() {
        for(int i=0;i<20;i++){
            if(this.num > 0){
                System.out.println(this.getName() + " num:" + this.num--);
            }
        }
    }
}
class Test{
    public static void main(String[] args){
        ThreadTest threadTest1 = new ThreadTest();
        ThreadTest threadTest2 = new ThreadTest();
        ThreadTest threadTest3 = new ThreadTest();
        threadTest1.start();
        threadTest2.start();
        threadTest3.start();
    }
}

运行结果

结论:通过继承Thread类创建的线程,每个线程直接不会资源共享。每个线程都会各自对num进行10次相减。

3、Runnable和Thread区别

4、Thread中start()和run()方法

start()和run()都是Thread中的方法,start()会启动一个新线程,新线程会去执行相应的run()方法,start()不能重复调用。run()和普通成员方法一样,单独调用run()方法,不会启动新线程,而是使用当前的线程执行run()方法,这个run()和普通方法一样,可以被重复调用。
run()源代码:

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

target是一个Runnable对象,run()方法直接调用Thread线程的Runnable的run()方法,并不会新建一个线程。

start()源代码:

public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)//如果线程不是处于就绪状态,抛出异常
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);//将当前线程添加到线程组里

    boolean started = false;
    try {
        start0();//启动线程
        started = true;//启动标志位
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);//启动失败,添加到失败队列里
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

private native void start0();

start()通过调用本地start0()方法,启动一个线程,新线程会调用run()方法。

synchronized同步锁

原理:每个对象都有且只有一个同步锁,同步锁依赖于对象存在。当调用某个对象的synchronized方法时,就获取该对象的同步锁。例如synchronized(obj)就是获取了obj的同步锁,不同线程对同步锁访问是互斥的。就是说一个时间点,对象的同步锁只能被一个线程调用,其它线程如果要使用,需要等待正在使用同步锁的线程释放掉后才能使用。

synchronized规则:

public class RunnableTest implements Runnable{
    @Override
    public void run() {
        synchronized(this){
            for(int i=0;i<3;i++){
                System.out.println(Thread.currentThread().getName()+" num:" + i );
            }
        }
    }
}

class Test{
    public static void main(String[] args){
        RunnableTest runnable = new RunnableTest();
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        Thread thread3 = new Thread(runnable);
        thread1.start();//只有start()后才进入就绪状态
        thread2.start();
        thread3.start();
    }
}

运行结果

结论:
当一个线程访问对象的synchronized方法或者代码块,其它线程将会被阻塞。Thread0、Thread1、Thread2共用RunnableTest实现Runnable接口的同步锁,当一个线程运行synchronized()代码块时候,其它线程需要等待正在运行的线程释放同步锁后才能运行。

再来看下使用Thread方式实现多线程的获取同步锁的执行流程

class ThreadTest extends Thread{
    int num = 10;
    @Override
    public void run() {
        synchronized(this){
            for(int i=0;i<3;i++){
                System.out.println(Thread.currentThread().getName()+" num:" + i );
            }
        }

    }
}

class Test{
    public static void main(String[] args){
        ThreadTest threadTest1 = new ThreadTest();
        ThreadTest threadTest2 = new ThreadTest();
        ThreadTest threadTest3 = new ThreadTest();
        threadTest1.start();
        threadTest2.start();
        threadTest3.start();
    }
}

运行结果

结论:
发现并没有我们之前说的Thread0、Thread1、Thread2阻塞顺序执行,这个主要是和Thread形式创建多线程有关,trhreadTest1、trhreadTest2、trhreadTest3是三个不同的对象,它们是通过new ThreadTest()创建的三个对象,这里synchronized(this)是指的ThreadTest对象,所以threadTest1、threadTest2、threadTest3是获取的三个不同的同步锁。而上面使用RunnableTest方式实现的多线程,this是指的RunnableTest,这样三个线程使用的是同一个对象的同步锁。

当一个进程访问对象的同步锁时,其它线程可以访问这个对象的非synchronize代码块

class ThreadTest2{
    public void synMethod(){
        synchronized (this){
            for(int i=0;i<3;i++){
                System.out.println(Thread.currentThread().getName() + " num: " + i);
            }
        }
    }

    public void nonSynMethod(){
        for(int i=0;i<3;i++){
            System.out.println(Thread.currentThread().getName() + " num:" + i);
        }
    }
}
class Test{
    public static void main(String[] args){
        final ThreadTest2 threadTest = new ThreadTest2();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadTest.synMethod();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadTest.nonSynMethod();
            }
        });
        thread1.start();
        thread2.start();
    }
}

返回结果

结论:
thread1访问对象的synchronize代码块,thread2访问非synchronized代码块。thread2并没有因为thread1受阻。

当一个线程访问一个对象的synchronized方法或代码块,其它线程访问这个对象的其它synchronized也是受阻的。

class ThreadTest2{
    public void synMethod1(){
        synchronized (this){
            for(int i=0;i<3;i++){
                System.out.println(Thread.currentThread().getName() + " num: " + i);
            }
        }
    }

    public void synMethod2(){
        synchronized (this){
            for(int i=0;i<3;i++){
                System.out.println(Thread.currentThread().getName() + " num:" + i);
            }
        }
    }
}
class Test{
    public static void main(String[] args){
        final ThreadTest2 threadTest = new ThreadTest2();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadTest.synMethod1();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadTest.synMethod2();
            }
        });
        thread1.start();
        thread2.start();
    }
}

返回结果

结论:thread1、thread2都会调用ThreadTest2的synchronized(this)代码块,而这个this都是ThreadTest2,所以线程2需要等到线程1执行完synchronized才能执行。

synchronized方法和synchronized代码块

synchronized方法是用synchronized修饰类方法,synchronized代码块是用synchronized修饰代码块的。synchronized代码块可以更精准的控制限制区域,有时效率也是比synchronized方法高的。

class ThreadTest2{
    public synchronized void synMethod1(){
            for(int i=0;i<3;i++){
                System.out.println(Thread.currentThread().getName() + " num: " + i);
            }
    }

    public void synMethod2(){
        //this获取当前对象的同步锁,如果修改成xxx,则获取xxx的同步锁
        synchronized (this){
            for(int i=0;i<3;i++){
                System.out.println(Thread.currentThread().getName() + " num:" + i);
            }
        }
    }
}

实例锁和全局锁

class SomeLock{
    public synchronized void intanceLockA(){}
    public synchronized void instanceLockB(){};
    public static synchronized void globalLockA(){};
    public static synchronized void globalLockB(){};
}
  1. x.instaceLockA()和x.instanceLockB(),二者不能同时被访问,因为二者都是访问的都是x的实例锁。
  2. x.instaceLockA()和y.instaceLockA(),二者可以同时被访问,因为二者访问的不是同一个对象的锁。
  3. x.globalLocckA()和y.globalLockB(),二者不能同时访问,因为y.globalLockB()相当于SomeLock.globalLockB(),x.globalLockA()相当于SomeLock.globalLockA(),二者使用的是同一个同步锁,所以不能同时被访问。
  4. x.instaceLocakA()和b.globalLockA(),二者可以同时被访问,因为一个是示例的锁,一个是类的锁。

线程的等待与唤醒

线程的等待与唤醒使用了Object类中的wait()、wait(long timeout)、wait(long timeout,int nanos)、notify()、notifyAll()

注意:
wait()的作用是让当前线程等待,当前线程指的是正在cpu运行的线程,而不是调用wait()方法的线程。wait()、notify()、notifyAll()都是属于Object类下边的方法,之所以在Object下面而没有在Thread类下面,主要原因就是同步锁。

wait()和notify()都是对对象的同步锁进行操作,同步锁是对象持有的,并且每个对象有且仅有一个。

线程让步yield()

yield()的作用是让步。让当前线程由运行状态进入就绪状态,从而让其他具有高优先级的线程获取cpu执行。但是并不会保证当前线程调用yield()后,其它同等级线程一定获取到cpu执行权。也有可能当前线程又进入到运行状态。

yield()与wait()的区别

线程休眠sleep()

sleep()在Thread.class类中定义,让当前线程由运行状态进入休眠状态(阻塞状态)。sleep()需要指定休眠时间,线程休眠时间会大于等于该休眠时间;线程被重新唤醒时会由阻塞状体进入就绪状态。

sleep()与wait()区别

sleep()和wait()都会让线程由运行状态进入阻塞状态,但是wait()会释放对象同步锁,而sleep()不会释放同步锁。

线程join()

join()在Thread.class类中定义,让主线程等待子线程结束后才能继续运行。

// 主线程
public class Father extends Thread {
    public void run() {
        Son s = new Son();
        s.start();
        s.join();
        ...
    }
}
// 子线程
public class Son extends Thread {
    public void run() {
        ...
    }
}

Son线程是在Father线程中创建的,并且调用了s.join(),这样Father线程要等到Son线程执行完成后,才会执行。可以查看下join()的源代码:

public final void join() throws InterruptedException {
    join(0);
}
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) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

join()中通过wait()进行等待,所以即使是子线程调用的join(),而真实等待的是正在执行的父进程。

线程中断

Thread中自带的stop()和suspend()由于不安全(从JDK2,开始不建议使用),所以已经过时不在建议使用了。线程中断可以从阻塞状体和运行状态说起。

阻塞状态

当线程通过wait()、sleep()、join()等进入了阻塞状态,若此时调用线程的interrupt()会将线程中断标记为true,由于处于阻塞状态,中断标记会被清除,同时产生一个InterruptedException异常。所以可以将InterruptedException放在适当的位置就能终止进程。

public void run(){
    try {
        while (true){
            ....
        }
    }catch (InterruptedException ie){
        //产生InterruptedException,退出while(true)循环,线程终止
    }
}

如果将异常处理放在while()中,这样while(true)不会被停止,还需要在catch手动break一下。

运行状态

public void run(){
    while (!thread.isInterrupted()){
    ...
    }
}

isInterrupted()是判断线程中断标记是否为true。当线程处于运行状态时候,通过调用interrupted()方法将线程中断标记设置为true,这样判读isInterrupted()就可以退出线程了。

interrupted()并不会终止处于运行状态的线程,只是将中断标记设置为true。

综合线程处于阻塞状态和运行状态,可以使用通用方式:

public void run(){
    try {
        //1、通过中断标记终止线程
        while (!thread.isInterrupted()){

        }
    }catch (InterruptedException ie){
        //2、通过InterruptedException产生异常,终止线程
    }

}

interrupted()和isInterrupted()区别

二者都能检测对象的中断标记,但是interrupted()除了返回中断状态外,如果线程处于阻塞状态还会清除中断标记(设置为false),而isInterrupted()只是返回中断标记。

线程优先级

java中线程优先级从1~10,默认是5,值越大优先级越高,高优先级与优先低优先级线程执行。每个线程都会标记为一个守护线程或非守护线程。主线程创建的子线程与其具有相同的优先级。当且仅当父线程是守护线程,子线程才是守护线程。java虚拟机启动时候就是启动一个非守护线程(通过main方法启动),它会监听:

java中线程有两种:守护线程(用户线程)和守护线程。可通过isDaemon()方法来区别。用户线程一般用户执行用户任务,守护线程也称为后台进程,一般用来执行后台任务。java虚拟机在用户线程都结束后会退出。如果要设置为守护线程可以通过setDaemon(true)来操作。

上一篇 下一篇

猜你喜欢

热点阅读