java多线程基础

作者: 零度沸腾_yjz | 来源:发表于2017-01-12 12:03 被阅读107次

线程的五种状态

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();
    }
}

运行结果

  • Thread-0 num:10
  • Thread-0 num:7
  • Thread-2 num:8
  • Thread-1 num:9
  • Thread-1 num:4
  • Thread-1 num:3
  • Thread-1 num:2
  • Thread-1 num:1
  • Thread-2 num:5
  • Thread-0 num:6

结论:三个线程共享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-0 num:10
  • Thread-0 num:9
  • Thread-0 num:8
  • Thread-0 num:7
  • Thread-0 num:6
  • Thread-2 num:10
  • Thread-2 num:9
  • Thread-1 num:10
  • Thread-1 num:9
  • Thread-0 num:1
  • ....

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

3、Runnable和Thread区别

  • 通过实现Runnable接口能够解决单继承问题,如果实现Thread类则不能继承其它类了。
  • 通过Runnable接口方式实现多线程,能够起到资源共享的目的,因为多个线程共用一个Runnable实现类,而Thread是继承Runnable接口,每个Thread都会实现各自的Runnable接口。

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规则:

  • 当一个线程访问某对象的synchronized方法或synchronized代码块时,其它线程对该对象的该synchronized方法或者synchronized代码块访问将被受阻;
  • 当一个线程访问某个对象的synchronized方法或synchronized代码块时,其它线程可以访问该对象的非synchronized方法或synchronized代码块;
  • 当一个线程访问某个对象的synchronized方法或synchronized代码块时,当其它对象访问该对象的synchronized方法或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();
    }
}

运行结果

  • Thread-0 num:0
  • Thread-0 num:1
  • Thread-0 num:2
  • Thread-2 num:0
  • Thread-2 num:1
  • Thread-2 num:2
  • Thread-1 num:0
  • Thread-1 num:1
  • Thread-1 num:2

结论:
当一个线程访问对象的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();
    }
}

运行结果

  • Thread-0 num:0
  • Thread-0 num:1
  • Thread-0 num:2
  • Thread-2 num:0
  • Thread-1 num:0
  • Thread-2 num:1
  • Thread-1 num:1
  • Thread-2 num:2
  • Thread-1 num:2

结论:
发现并没有我们之前说的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();
    }
}

返回结果

  • Thread-0 num:0
  • Thread-0 num:1
  • Thread-1 num:0
  • Thread-0 num:2
  • Thread-1 num:1
  • Thread-1 num:2

结论:
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();
    }
}

返回结果

  • Thread-0 num:0
  • Thread-0 num:1
  • Thread-0 num:2
  • Thread-1 num:0
  • Thread-1 num:1
  • Thread-1 num:2

结论: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);
            }
        }
    }
}

实例锁和全局锁

  • 实例锁:如果锁在某一个实例上面,那么该锁就是实例锁。如果这个类是单例,那么这个锁也具有全局锁的概念。实例锁使用synchronized关键字。
  • 全局锁:如果锁针对的是一个类上面,无论多少个实例共享这个锁。全局锁使用static synchronized,或者锁在该类的class或classloader上面。
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():使线程进入等待状态(等待阻塞),直到其它线程调用该对象的 notify()或notifyAll(),当前线程会被唤醒(进入就绪状态)。
  • wait(long timeout):使线程进入等待状态(等待阻塞),直到其它线程调用该对象的notify()或notifyAll()或超过了指定时间,当前线程会被唤醒(进入就绪状态)。
  • wait(long timeout,int nanos):使线程进入等待状态(等待阻塞),直到其它线程调用该对象的notify()或notifyAll()或超过了指定时间或被其它线程中断,当前线程会被唤醒(进入就绪状态)。
  • notify():唤醒在此对象监视器(同步锁的实现原理)上等待的单个线程。
  • notifyAll():唤醒在此对象监视器上等待的多个线程。

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

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

线程让步yield()

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

yield()与wait()的区别

  • wait()会由运行状态进入等待状态(阻塞状态),而yield()会从运行状态进入就绪状态。
  • wait()会释放对象的同步锁,而yield()是不会释放对象的同步锁的。

线程休眠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方法启动),它会监听:

  • 是否调用exit(),并且exit()被执行;
  • 所有的非守护线程都死了(jvm中只有守护线程)

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

相关文章

  • android 多线程 — 线程的面试题和答案

    这里都是我从各个地方找来的资料,鸣谢: Java多线程干货系列—(一)Java多线程基础 JAVA多线程和并发基础...

  • 技术体系

    一,java核心 java基础,jvm,算法,多线程,设计模式 Java基础:java基础相关,全栈java基础 ...

  • Java多线程目录

    Java多线程目录 Java多线程1 线程基础Java多线程2 多个线程之间共享数据Java多线程3 原子性操作类...

  • Java基础

    Java基础 集合基础 集合框架 多线程基础 多线程框架 反射 代理 集合基础 ArrayList LinkedL...

  • java多线程相关

    (一) 基础篇 01.Java多线程系列--“基础篇”01之 基本概念 02.Java多线程系列--“基础篇”02...

  • Java多线程高级特性(JDK8)

    [TOC] 一、Java多线程 1.Java多线程基础知识 Java 给多线程编程提供了内置的支持。一条线程指的是...

  • Java多线程系列目录(共43篇)-转

    最近,在研究Java多线程的内容目录,将其内容逐步整理并发布。 (一) 基础篇 Java多线程系列--“基础篇”0...

  • Android中的多线程

    1. Java多线程基础 Java多线程,线程同步,线程通讯 2. Android常用线程 HandlerThre...

  • Java架构师阅读书单

    一、内功心法 Java基础: 《Java核心技术》《Java编程思想》《Effective Java》 多线程...

  • java学习路线

    javaSE java基础语法 java文件操作 java网络操作 java多线程 java数据库操作 java ...

网友评论

    本文标题:java多线程基础

    本文链接:https://www.haomeiwen.com/subject/jdurbttx.html