Java天生就是多线程,一个请求过来就可以理解成是一个线程,线程有自己独享的虚拟机栈、本地方法栈、程序计数器,也跟其他线程共享堆内存和方法区。
一个新线程的空间是由它的父线程分配的,属性也是随父线程的属性设置。
一、创建
1、继承Thread类;
2、实现Runnable接口
3、利用FutureTask 配合传入Callable创建有返回值的线程
4、拉姆达表达式创建
5、利用线程池
二、状态
jdk里的线程共有6种状态。一个线程同一时刻只能处于一种状态
状态转换:
1、刚new出来就是新建状态,调用start()方法启动后进入可运行状态;
2、在可运行状态下当获取cpu资源时,它就真正执行run()方法里的业务逻辑,进入RUNNING状态,释放cpu后进入ready状态,这个释放动作可以通过yield()方法触发;
3、当遇到锁,进入阻塞状态;拿到锁重新进入可运行状态;
4、调用了监视器(任意对象)的wait()方法或其他等待方法后进入了等待状态,直到被通知唤醒才重新进入可运行状态;
5、进入等待状态时可以传入时间,等到时间自动唤醒进入可运行状态。
a、阻塞状态是自己等着拿锁,一旦锁被释放了就去争抢,等待状态需要被显示的通知,被通知后也不会立即执行,而是进入阻塞状态,再去等锁。
b、虚拟机的线程其实是映射成系统的线程执行的,Java本身不提供线程的调度,委托给cpu去调度,系统级别的线程runnable状态包含reday和running,所以虚拟机里的说的runnable实际是说系统级别的。
三、线程通讯
多线程之间通讯可以通过共享内存和等待通知机制实现
共享内存:多个线程共同操作堆内存中的数据,为了保证原子性、一致性通常与synchronized、volatile一起用;
等待通知机制:我们最常用的wait、notify,这几个方法的调用必须拿到监视器的锁(如:synchronized),等待通知被定义在Object里,所以任意对象都可以被当作监视器。A线程调用wait后进入的是waitting状态,会释放锁和cpu资源,直到被其他线程唤醒或中断再去抢锁;B线程调用notify后,B线程也不会立即释放锁,等B线程全部执行完才释放锁,A线程才有可能被执行(需要抢到锁)。
A线程wait后进入的是等待队列,被B线程notify后A线程被移动到阻塞队列,等待自己去抢锁。
另外,线程还提供了sleep、join、yeild等方法,他们是围绕线程调度进行通信的,对多个线程之间进行调度、控制,他们不受锁的影响,可以在任意地方使用。sleep是调用的底层的sleep方法(cpu级)、join本身在方法上加了锁,再调用sleep、yeild也是cpu层面上的方法。所以他们的使用与锁无关。
四、其他
线程的方法
start:开启一个线程,实际是映射给系统层级上的线程
run:我们要执行的业务方法
sleep:当前线程主动睡一段时间,释放cpu资源但不释放锁,到时间后自动醒来去执行,调用的底层的sleep方法。
yeild:让正运行的线程暂停一下,换其他线程执行,它的作用是切换时间片,状态不会改变,这其实是对应的系统级别的线程,我们开发大多用不到。
join:及其带时间参数的join方法,底层都是调的wait。线程A调用线程B的jon方法后,线程A就暂停了,等待线程B执行完后再执行线程A。常见的我们再主线程main中调用线程的join方法。它之所以能直接调wait是因为它本身实现上就是加了synchronized锁的。
interrupt:给该线程设置一个中断的标志,中断后线程还是会继续执行,直到运行完才会被中断。A线程里调用B线程的interrupt方法,中断的是B线程,B线程执行完才会中断。
线程的变量
ThreadLocal:线程内部私有的一个变量,是一个Entry类型的map结构,主键是线程本身,值可以是任意类型的数据。他是线程私有的,不会产生线程安全问题,但要注意内存泄漏。
sleep和wait区别:
1、sleep是由Thread类提供;wait是由Object对象提供;
2、sleep可以在任何地方使用,表示当前线程停多长时间;wait只能用在synchronized代码块或synchrnoized修饰的方法上,因为调用它要释放锁,它必须先拿到锁,由被锁的对象调用;
3、sleep不释放锁,只让出cpu;wait既释放锁也会让出CPU
五、代码:
线程的创建:
public class ThreadCreate {
public static void main(String[] args) throws Exception{
create1();
create2();
create3();
create4();
create5();
create6();
}
static void create1(){
MyThread myThread = new MyThread();
myThread.start();
}
static void create2(){
Thread thread = new Thread(new MyRun());
thread.start();
}
//继承Thread类
static class MyThread extends Thread{
@Override
public void run() {
System.out.println("继承Thrad类创建线程");
}
}
//实现runnable接口
static class MyRun implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable接口创建线程");
}
}
//利用futuretask和callable接口实现
static void create3() throws Exception{
FutureTask<String> stringFutureTask = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
return "Callable接口实现线程";
}
});
Thread thread = new Thread(stringFutureTask);
thread.start();
System.out.println(stringFutureTask.get());
}
//兰姆达创建线程
static void create4(){
new Thread(()->{
System.out.println("兰姆达表达式创建线程");
}).start();
}
static void create6(){
new Thread(){
@Override
public void run() {
System.out.println("匿名类创建线程1");
}
}.start();
}
}
线程初始化源码:
/**
* 初始化线程
* 整体来说线程的一些属性是由父线程决定(根据父线程的参数设置)
*/
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
this.name = name;
//获取父线程,也就是这个线程是由哪个线程new的,一般都是main线程
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
//省略设置线程组代码
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);
//由父线程的ThreadLocal初始化线程的ThreadLocal
if (inheritThreadLocals && parent.inheritableThreadLocals != null) {
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
this.stackSize = stackSize;
/* 安全的获取唯一线程编号id*/
tid = nextThreadID();
}