多线程

线程创建方式:

  • 继承 Thread 线程类。

  • 实现 Runnable 接口。

  • 实现 Callable 接口。

1、继承 Thread 线程类方式

继承Thread类,重写Thread类的run()方法并将此线程执行的操作声明在run()方法中,调用的时候,创建Thread类的子类的对象,通过此对象调用start()方法启动线程。

基本类:

package com.xiaocai.juc.threads;

public class ThreadStyle01 extends Thread{

    public ThreadStyle01(String name){
        super(name);
    }

    @Override
    public void run() {
        System.out.println("这里写线程任务实现");
    }
}

基本类:

package com.xiaocai.juc.threads;
/**
 * @description: TODO 功能角色说明:
 * TODO 描述:
 * @author: 张小菜
 * @date: 2020/12/24 15:58
 * @version: v1.0
 */
public class ThreadStyle01Test {

    public static void main(String[] args) {
        Thread thread = new ThreadStyle01("thread-style-01");
        thread.start();
    }
}

2、实现 Runnable 接口方式

创建一个实现Runnable接口的实现类,实现类去实现Runnable中的抽象run()方法。调用的时候,创建实现类的对象并将此对象作为参数传递到Thread类的构造器中来创建Thread类的对象,通过Thread类的对象调用start()方法启动线程。

(1)外部式

package com.xiaocai.juc.threads;

public class ThreadStyle02 implements Runnable{
    private String name ;

    public ThreadStyle02(String name){
        this.name = name ;
    }

    @Override
    public void run() {
        System.out.println("这里写线程任务实现");
    }
}

调用类:

package com.xiaocai.juc.threads;

/**
 * @description: TODO 功能角色说明:
 * TODO 描述:
 * @author: 张小菜
 * @date: 2020/12/24 16:02
 * @version: v1.0
 */
public class ThreadStyle02Test {

    public static void main(String[] args) {
        ThreadStyle02 run01 = new ThreadStyle02( "thread-style-02");
        Thread thread = new Thread(run01);
        thread.start();
    }
}

(2)匿名内部类

package com.xiaocai.juc.threads;

/**
 * @description: TODO 功能角色说明:
 * TODO 描述:
 * @author: 张小菜
 * @date: 2020/12/24 16:15
 * @version: v1.0
 */
public class ThreadStyle03 {

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("这里写线程任务实现");
            }
        });
        thread.start();
    }
}

(3)Lamda表达式

package com.xiaocai.juc.threads;

/**
 * @description: TODO 功能角色说明:
 * TODO 描述:
 * @author: 张小菜
 * @date: 2020/12/24 16:18
 * @version: v1.0
 */
public class ThreadStyle04 {

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
                System.out.println("这里写线程任务实现");
        });
        thread.start();
    }
}

3、实现 Callable 接口

Callable<V> 提供返回数据,根据需要返回不同类型V。

package com.xiaocai.juc.threads;

import java.util.concurrent.Callable;

/**
 * @description: TODO 功能角色说明:
 * TODO 描述:
 * @author: 张小菜
 * @date: 2020/12/24 16:30
 * @version: v1.0
 */
public class ThreadStyle05 implements Callable<String> {
    
    @Override
    public String call() throws Exception {
        System.out.println("这里写线程任务实现");
        return "hello world";
    }
}

基本调用:

package com.xiaocai.juc.threads;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @description: TODO 功能角色说明:
 * TODO 描述:
 * @author: 张小菜
 * @date: 2020/12/24 16:30
 * @version: v1.0
 */
public class ThreadStyle05Test {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<String> tc = new ThreadStyle05();
        FutureTask<String> task = new FutureTask<String>(tc);
        new Thread(task).start();
        String result = task.get();
        System.out.println(result);
    }
}

输出结果:

这里写线程任务实现
hello world

Thread 和 Runnable 关系

关系:

public class Thread implements Runnable{

}

相同点:两种方式都需要重写run()方法,将线程要执行的逻辑声明在run()中。

实际开发中一般优先选择实现Runnable接口的方式。因为实现的方式没有类的单继承性的局限性,实现的方式更适合来处理多个线程有共享数据的情况。

(1)在多线程的设计之中,使用了代理设计模式的结构,用户自定义的线程主体只是负责项目核心功能的实现,而所有的辅助实现全部交由(代理类)Thread类来处理。

(2)在进行Thread启动多线程的时候调用的是start()方法,而后找到的是run()方法,但通过Thread类的构造方法传递了一个Runnable接口对象的时候,那么该接口对象将被Thread类中的target属性所保存,start()方法执行的时候会调用Thread类中的run()方法。而这个run()方法去调用实现了Runnable接口的那个类所重写过run()方法,进而执行相应的逻辑。

(3)多线程开发的本质实际上是在于多个线程可以进行同一资源的抢占,那么Thread主要描述的是线程,而资源的描述是通过Runnable完成的。如下图所示:

Thread 源码

Runnable 接口源码

@FunctionalInterface
public interface Runnable {

	public abstract void run();
}

Thread类的部分源码

public class Thread implements Runnable {
	// some code ...

	// Thread类中的target属性,就是我们要执行的目标线程
	private Runnable target; 
 
	
	//构造方法的代码从上往下顺序执行
	public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    
	private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }
    
	private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

    /* 由于这里是通过继承Thread类来实现的线程,那么target这个东西就是Null。
     * 但是因为你继承了Runnable接口并且重写了run(),所以最终还是调用子类的run()
     * */
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
 
}

Thread 构造方法

  • 创建线程对象Thread,默认有一个线程名,以Thread-开头,从0开始计数,如”Thread-0、Thread-1、Thread-2 …”。

  • 如果没有传递Runnable或者没有覆写Thread的run方法,该Thread不会调用任何方法。

  • 如果传递Runnable接口的实例或者覆写run方法,则会执行该方法的逻辑单元(逻辑代码)。

  • 如果构造线程对象时,未传入ThreadGroup,Thread会默认获取父线程的ThreadGroup作为该线程的ThreadGroup,此时子线程和父线程会在同一个ThreadGroup中。

  • stackSize可以提高线程栈的深度,放更多栈帧,但是会减少能创建的线程数目。

  • stackSize默认是0,如果是0,代表着被忽略,该参数会被JNI函数调用,但是注意某些平台可能会失效,可以通过”-Xss10m”设置。

Thread 线程启动

Threadstart() 方法源码:

    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) {
	            /* do nothing. If start0 threw a Throwable then
	              it will be passed up the call stack */
	        }
	    }
	}

	private native void start0();

当多次调用start(),会抛出IllegalThreadStateException 异常。也就是每一个线程类的对象只允许启动一次,如果重复启动则就抛出此异常。

Thread 核心逻辑

1、如果Thread类的构造方法传递了一个Runnable接口对象

①那么该接口对象将被Thread类中的target属性所保存。

②在start()方法执行的时候会调用Thread类中的run()方法。因为target不为nulltarget.run()就去调用实现Runnable接口的子类重写的run()

2、如果Thread类的构造方传没传Runnable接口对象

Thread类中的target属性保存的就是null

②在start()方法执行的时候会调用Thread类中的run()方法。因为targetnull,只能去调用继承Thread的子类所重写的run()

ThreadLocal

Thread类中还有一个比较经典的ThreadLocal类。

ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。

Thread类是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据。其实就是我们常说的线程数据隔离。

ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组tableThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置。

线程生命周期

Java 源码中枚举的生命周期

线程状态变化图

注意:图中 wait到 runnable状态的转换中,join实际上是Thread类的方法,但这里写成了Object。

1、线程创建之后它将处于 NEW(新建) 状态,调用 start() 方法后开始运行,线程这时候处于 READY(可运行) 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 RUNNING(运行) 状态。

2、操作系统隐藏 Java 虚拟机(JVM)中的 READYRUNNING 状态,它只能看到 RUNNABLE 状态,所以 Java 系统一般将这两个状态统称为 RUNNABLE(运行中) 状态 。

3、调用线程的sleep()方法,会进入Blocked状态。 sleep()结束之后,Blocked状态首先回到的是Runnable状态中的Ready(也就是可运行状态,但并未运行)。只有拿到了cpu的时间片才会进入Runnable中的Running状态。

获取当前存活的线程数:public int activeCount() 获取当前线程组的线程的集合:public int enumerate(Thread[] list)