Java笔记21——多线程

Java学习——多线程


一、线程

线程是程序中执行的线程。 Java虚拟机允许应用程序同时运行多个执行线程。
每个线程都有优先权。 具有较高优先级的线程优先于具有较低优先级的线程执行。 每个线程可能也可能不会被标记为守护进程。 当在某个线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初设置为等于创建线程的优先级,并且当且仅当创建线程是守护进程时才是守护进程线程。

当Java虚拟机启动时,通常会有一个非守护进程线程(通常调用某个指定类的名为main的方法)。 Java虚拟机继续执行线程,直到发生以下任一情况:

  • 已调用类Runtime的exit方法,并且安全管理器已允许执行退出操作。
  • 通过调用run方法返回或抛出超出run方法传播的异常,所有非守护程序线程的线程都已死亡。

有两种方法可以创建新的执行线程。

  • 一种是将类声明为Thread的子类。 此子类应覆盖类Thread的run方法。 然后可以分配和启动子类的实例。
  • 创建线程的另一种方法是声明一个实现Runnable接口的类。 该类然后实现run方法。
    然后可以分配类的实例,在创建Thread时作为参数传递,然后启动。

(1). 继承Thread与实现Runnable

继承Thread示例:
新建一个MyThread类 继承Thread

public class MyThread extends Thread{

    /**
     * run方法就是线程要执行的方法
     */
    @Override
    public void run() {
        //这里表示一条新的执行路径
        //这个执行方式发触发方式,不是调用run方法,而是通过这个继承了Thread类的对象调用start方法启动任务
        for (int i = 0;i < 10;i++) {
            System.out.println("都在吃西瓜" + i);
        }
    }
}

在main方法中:

public class Demo1 {
    public static void main(String[] args) {
        MyThread m = new MyThread();
        m.start();
        for (int i = 0;i < 10;i++) {
            System.out.println("一去二三里" + i);
        }
    }
}

结果,可以很明显的看到,Java线程的抢占式运行:
在这里插入图片描述

实现Runnable示例:
创建一个实现接口Runnable 的类:

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        //线程的任务
        for (int i = 0;i < 10;i++) {
            System.out.println("移动也不动" + i);
        }
    }
}

main方法中:

public class Demo2 {
    public static void main(String[] args) {
        //实现Runnable
        //创建一个任务对象
        MyRunnable r = new MyRunnable();
        //创建一个线程,并为其分配一个任务
        Thread t = new Thread(r);
        //执行这个线程
        t.start();
        for (int i = 0;i < 10;i++) {
            System.out.println("中国移动" + i);
        }
    }

运行结果:
在这里插入图片描述
上述使用匿名内部类,进行代码优化:

public class Demo2 {
    public static void main(String[] args) {
        new Thread() {  //使用匿名内部类
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("没有那回事" + i);
                }
            }
        }.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("中国移动" + i);
        }

    }
}

实现Runnable 与继承Thread相比有如下优势:

  • 1.通过创建任务,然后给线程分配的方式来实现的多线程.更适合多个线程同时执行相同任务的情况.
  • 2.可以避免单继承所带来的局限性.
  • 3.任务与线程本身是分离的,提高了程序的健壮性.
  • 4.后续学习的线程池技术,接受Runnable类型的任务,不接收Thread类型的线程.

(2). Callable 与 Runnable

接口定义:

 //Callable接口 
public interface Callable<V> {
    V call() throws Exception;
}

//Runnable接口 
public interface Runnable {
    public abstract void run();
}

Callable使用步骤

//1.编写类实现Callable接口 ,实现call方法
class XXX implements Callable<T> {
    @Override
    public <T> call() throws Exception {
        return T;
    }
}

//2.创建FutureTask对象 ,并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable);

//3.通过Thread,启动线程
new Thread(future).start();

示例:

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

public class Demo4 {
    public static void main(String[] args) {
        Callable<Integer> c = new MyCallable();
        FutureTask<Integer> task = new FutureTask<>(c);     //创建FutureTask对象 , 并传入第一步编写的Callable类对象
        new Thread(task).start();       //通过Thread,启动线程
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }


    static class MyCallable implements Callable<Integer> {      //编写类实现Callable接口 , 实现call方法

        @Override
        public Integer call() throws Exception {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "  " + i);
            }
            return 100;
        }
    }

}

结果,可以看到两个线程是交替打印输出的:
在这里插入图片描述
当我们再代码中调用方法get时:
在这里插入图片描述
结果如下:
在这里插入图片描述

Runnable 与 Callable的相同点

  • 都是接口
  • 都可以编写多线程程序
  • 都采用Thread.start()启动线程

Runnable 与 Callable的不同点

  • Runnable没有返回值;Callable可以返回执行结果
  • Callable接口的call()允许抛出异常;Runnable的run()不能抛出

Callable获取返回值
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

(3). 设置和获取线程名称

通过调用Thread类中的方法达到目的。
示例:

public class Demo2 {
    public static void main(String[] args) {
        //获取当前线程的名称,此时是main线程
        System.out.println(Thread.currentThread().getName());
        Thread t = new Thread(new MyRunnable());    //若没有给线程设置名称则是系统默认名
        t.setName("王大善人线程");
        t.start();
        new Thread(new MyRunnable()).start();
        new Thread(new MyRunnable()).start();
        new Thread(new MyRunnable()).start();
    }

    static class MyRunnable implements Runnable{

        @Override
        public void run() {
            //获当前线程的名称
            System.out.println(Thread.currentThread().getName());
        }
    }
}

结果:
在这里插入图片描述

(4). 线程的休眠与中断

休眠
示例:

public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0;i < 10;i++) {
            System.out.println(i);
            Thread.sleep(1000); //线程休眠1000毫秒
            Thread.sleep(1000,500); //线程休眠1000毫秒500纳秒
        }
    }
}

结果,可以看到,每过2秒零500纳秒输出数字
在这里插入图片描述

中断
示例:

/**
 * 线程的休眠与中断
 */
public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new MyRunnable());
        t1.start();
        for (int i = 0;i < 5;i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            Thread.sleep(1000); //线程休眠1000毫秒
        }
        //当main方法中想你换走完执行到此处,标记t1线程中断,
        t1.interrupt(); //程序发现了标记,进入到t1的异常处理catch块
    }

    static class MyRunnable implements Runnable{

        @Override
        public void run() {
            for (int i = 0;i < 10;i++) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {  //线程中断异常
//                    e.printStackTrace();
                    System.out.println("发现了线程中断标记,线程暂时不作回应,并输出了这句话");
//                    System.out.println("发现了线程中断标记,线程自杀");
//                    return; //线程自杀操作
                }
            }
        }
    }
}

结果:
在这里插入图片描述
我们取消catch块的return操作的注释:
输出结果,可以看到catch的处理操作成功
在这里插入图片描述

(5). 守护线程

线程:分为守护线程和用户线程
用户线程:当一个进程不包含任何的存活的用户线程时,进程结束。
守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。

示例:

/**
 * 守护线程
 */
public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new MyRunnable());
        t1.setDaemon(true);     //需要在线程启动之前,设置为守护线程
        t1.start();
        for (int i = 0;i < 5;i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            Thread.sleep(1000);     //线程休眠1000毫秒
        }
    }

    static class MyRunnable implements Runnable{

        @Override
        public void run() {
            for (int i = 0;i < 10;i++) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {  //出现线程中断异常
//                    e.printStackTrace();
                    System.out.println(".........");
                }
            }
        }
    }
}

结果,在main线程完全结束后,守护进程才会跟着结束:
在这里插入图片描述

(6). 线程的wait与唤醒

示例:

public class Test001 {
    static Object o = new Object();    //可以是任意一个对象,或者自定义的对象

    public static void main(String[] args) {
        ThreadB b = new ThreadB();
        b.start();
        synchronized (o)   // 主线程获取o的对象锁
        {
            System.out.println("正处于同步代码块的线程:   " + Thread.currentThread().getName());
            try {
                System.out.println("进入等待状态,等待他人唤醒。。。");
                o.wait();  //  o的对象锁释放,主线程进入等待状态
                System.out.println(Thread.currentThread().getName() + "   被唤醒");
            } catch (InterruptedException e) {
            }
        }
        System.out.println("程序结束-----------Total is :" + b.total);
    }
    
    static class ThreadB extends Thread {
        int total;
        @Override
        public void run() {
            synchronized (o) {     //  ThreadB获取o的对象锁
                System.out.println(Thread.currentThread().getName() + "     正在运行");
                for (int i = 0; i < 5; i++) {
                    total++;
                    System.out.println("total  =  " + total);
                }
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    System.out.println("线程sleep出错。。。。。。。");
                }
                System.out.println("唤醒所有等待线程。。。");
                o.notify();    //  ThreadB释放o的对象锁,通知其他等待o对象锁的线程继续运行
            }
        }
    }
}

结果:
在这里插入图片描述

二、线程安全问题

(1). 线程不安全解决方案

解决线程安全问题方案一:

同步代码块synchronized

我们在线程争抢资源部分的代码块进行了synchronized的锁机制,每个线程进行争抢时,先查看该代码块是否有锁对象。

public class Demo3 {
    public static void main(String[] args) {
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable {
        private int count = 10;         //票数
        private Object o = new Object();    //锁对象必须定义在外面,才能让各线程拥有同一把锁

        //同步代码块synchronized方法,解决线程安全问题方案一
        @Override
        public void run() {
            while (true) {
                synchronized (o) {  //synchronized
                    if (count > 0) {
                        //卖票
                        System.out.println("正在准备卖票");
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
                    } else break;
                }
            }
        }

    }
}

结果,没有出现资源混乱的情况,但是由于实在一个while的代码块内执行,所以基本上时先争抢到资源的线程一致直在资源抢占:
在这里插入图片描述
方案2 :同步方法
我们将上面需要的解决线程需要解决的代码块抽成一个方法sale(),对方法名进行 synchronized 修饰:

需要注意的是:
被synchronized修饰的方法,也会有锁对象的概念,不过若是方法被static修饰为静态方法,那么它的锁对象是 类名.class ;
否则就是对象this本身。

示例,若是我们将线程创建时,同时new 的三个Ticket的对象进行线程操作,会出现线程安全问题,因为相当于创建了三把锁,就不能同时锁住了 :

public class Demo3 {
    public static void main(String[] args) {
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable {
        private int count = 10;         //票数

        //同步代码块synchronized方法,解决线程安全问题方案一
        @Override
        public void run() {
            while (true) {
                boolean flag = sale();
                if (!flag) {
                    break;
                }
            }
        }

        //对会出现线程安全问题的方法进行synchronized修饰
        public synchronized boolean sale() {
            if (count > 0) {
                //卖票
                System.out.println("正在准备卖票");
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
                return true;
            } else {
                return false;
            }
        }
    }
}

结果如同方案一:
在这里插入图片描述

解决方案3: 显式锁 Lock
而上面的synchronized 是隐式锁。

示例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo3 {
    public static void main(String[] args) {
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable {
        private int count = 10;         //票数
        //显式锁 Lock
        Lock l = new ReentrantLock();

        @Override
        public void run() {
            while (true) {
                l.lock();  //加锁
                if (count > 0) {
                    //卖票
                    System.out.println("正在准备卖票");
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
                } else break;
            }
            l.unlock();     //解锁
        }

    }
}

Java并发之显式锁和隐式锁的区别
一:出身不同
从sync和lock的出身(原始的构成)来看看两者的不同。

Sync:Java中的关键字,是由JVM来维护的。是JVM层面的锁。

Lock:是JDK5以后才出现的具体的类。使用lock是调用对应的API。是API层面的锁

sync是底层是通过monitorenter进行加锁(底层是通过monitor对象来完成的,其中的wait/notify等方法也是依赖于monitor对象的。只有在同步块或者是同步方法中才可以调用wait/notify等方法的。因为只有在同步块或者是同步方法中,JVM才会调用monitory对象的);通过monitorexit来退出锁的。

而lock是通过调用对应的API方法来获取锁和释放锁的。

二:使用方式不同
Sync是隐式锁。Lock是显示锁

所谓的显示和隐式就是在使用的时候,使用者要不要手动写代码去获取锁和释放锁的操作。

我们大家都知道,在使用sync关键字的时候,我们使用者根本不用写其他的代码,然后程序就能够获取锁和释放锁了。那是因为当sync代码块执行完成之后,系统会自动的让程序释放占用的锁。Sync是由系统维护的,如果非逻辑问题的话话,是不会出现死锁的。

在使用lock的时候,我们使用者需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。手动获取锁方法:lock.lock()。释放锁:unlock方法。需要配合tyr/finaly语句块来完成。

三:等待是否可中断
Sync是不可中断的。除非抛出异常或者正常运行完成

Lock可以中断的。中断方式:

1:调用设置超时方法tryLock(long timeout ,timeUnit unit)

2:调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断

lock 与 lockInterruptibly比较区别在于:
lock 优先考虑获取锁,待获取锁成功后,才响应中断。
lockInterruptibly 优先考虑响应中断,而不是响应锁的普通获取或重入获取。

详细区别:
ReentrantLock.lockInterruptibly 允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回,这时不用获取锁,而会抛出一个InterruptedException。

ReentrantLock.lock方法不允许Thread.interrupt中断,即使检测到Thread.isInterrupted,一样会继续尝试获取锁,失败则继续休眠。只是在最后获取锁成功后再把当前线程置为interrupted状态,然后再中断线程。

生活中小case来理解这一区别:官二代一般不会做饭。都会去餐厅点餐等待着餐厅出餐。普通人的你既可以去餐厅等待,如果等待时间长的话,你就可以回去自己做饭了。

四:加锁的时候是否可以公平
Sync;非公平锁

lock:两者都可以的。默认是非公平锁。在其构造方法的时候可以传入Boolean值。

true:公平锁

false:非公平锁

生活中小case来理解这个区别:官二代一般都不排队,喜欢插队的。普通人的你虽然也喜欢插队。但是如果遇到让排队的情况下,你还是会排队的。

Lock的公平锁和非公平锁:

在这里插入图片描述

(2). Lock的公平锁和非公平锁:

公平锁: 先来先得
不公平锁:抢占式,上面讲的都是不公平锁。
公平锁就是在ReentrantLock 方法里添加参数fair为 true ,此时便成 公平锁

在这里插入图片描述
输出结果:
在这里插入图片描述

三、线程的状态

网上由两种说法:六种状态或五种状态。

六种线程状态。 线程可以处于以下状态之一:

  1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
    线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
  3. 阻塞(BLOCKED):表示线程阻塞于锁。
  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
  5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
  6. 终止(TERMINATED):表示该线程已经执行完毕。

线程在给定时间点只能处于一种状态。 这些状态是虚拟机状态,不反映任何操作系统线程状态。
在这里插入图片描述
状态详细说明

  1. 初始状态(NEW)
    实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。

  2. 2.1. 就绪状态(RUNNABLE之READY)
    就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。
    调用线程的start()方法,此线程进入就绪状态。
    当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
    当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。
    锁池里的线程拿到对象锁后,进入就绪状态。
    2.2. 运行中状态(RUNNABLE之RUNNING)
    线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一的一种方式。

  3. 阻塞状态(BLOCKED)
    阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。

  4. 等待(WAITING)
    处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。

  5. 超时等待(TIMED_WAITING)
    处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。

  6. 终止状态(TERMINATED)
    当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

参考: Java线程的6种状态及切换(透彻讲解)



线程的五种状态:

1、新建状态(New):新创建了一个线程对象。

2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。

3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

阻塞的情况分三种:

(1)、等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,

(2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。

(3)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

线程变化的状态转换图如下:
在这里插入图片描述
注:拿到对象的锁标记,即为获得了对该对象(临界区)的使用权限。即该线程获得了运行所需的资源,进入“就绪状态”,只需获得CPU,就可以运行。因为当调用wait()后,线程会释放掉它所占有的“锁标志”,所以线程只有在此获取资源才能进入就绪状态。

下面小小的作下解释:

1、线程的实现有两种方式,一是继承Thread类,二是实现Runnable接口,但不管怎样,  当我们new了这个对象后,线程就进入了初始状态; 
2、当该对象调用了start()方法,就进入就绪状态; 
3、进入就绪后,当该对象被操作系统选中,获得CPU时间片就会进入运行状态; 
4、进入运行状态后情况就比较复杂了 
 4.1、run()方法或main()方法结束后,线程就进入终止状态; 
 4.2、当线程调用了自身的sleep()方法或其他线程的join()方法,进程让出CPU,然后就会进入阻塞状态(该状态既停止当前线程,但并不释放所占有的资源即调用sleep ()函数后,线程不会释放它的“锁标志”。)。当sleep()结束或join()结束后,该线程进入可运行状态,继续等待OS分配CPU时间片。典型地,sleep()被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止。
 4.3、线程调用了yield()方法,意思是放弃当前获得的CPU时间片,回到就绪状态,这时与其他进程处于同等竞争状态,OS有可能会接着又让这个进程进入运行状态; 调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间片从而需要转到另一个线程。yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
 4.4、当线程刚进入可运行状态(注意,还没运行),发现将要调用的资源被synchroniza(同步),获取不到锁标记,将会立即进入锁池状态,等待获取锁标记(这时的锁池里也许已经有了其他线程在等待获取锁标记,这时它们处于队列状态,既先到先得),一旦线程获得锁标记后,就转入就绪状态,等待OS分配CPU时间片;

 4.5. suspend() 和 resume()方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume()被调用,才能使得线程重新进入可执行状态。典型地,suspend()和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume()使其恢复。 
 4.6、wait()和 notify() 方法:当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的所有资源,与阻塞状态不同),进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒(由于notify()只是唤醒一个线程,但我们由不能确定具体唤醒的是哪一个线程,也许我们需要唤醒的线程不能够被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁池,等待获取锁标记。 

wait() 使得线程进入阻塞状态,它有两种形式:

一种允许指定以毫秒为单位的一段时间作为参数;另一种没有参数。前者当对应的
notify()被调用或者超出指定时间时线程重新进入可执行状态即就绪状态,后者则必须对应的
notify()被调用。当调用wait()后,线程会释放掉它所占有的“锁标志”,从而使线程所在对象中的其它synchronized数据可被别的线程使用。waite()和notify()因为会对对象的“锁标志”进行操作,所以它们必须在synchronized函数或synchronizedblock中进行调用。如果在non-synchronized函数或non-synchronizedblock中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。

参考: 线程的几种状态转换

创建线程的开销

JVM 在背后帮我们做了哪些事情:

  1. 它为一个线程栈分配内存,该栈为每个线程方法调用保存一个栈帧
  2. 每一栈帧由一个局部变量数组、返回值、操作数堆栈和常量池组成
  3. 一些支持本机方法的 jvm 也会分配一个本机堆栈
  4. 每个线程获得一个程序计数器,告诉它当前处理器执行的指令是什么
  5. 系统创建一个与Java线程对应的本机线程
  6. 将与线程相关的描述符添加到JVM内部数据结构中
  7. 线程共享堆和方法区域

四、线程池

线程池 Executors
线程池的好处

  • 降低资源消耗。
  • 提高响应速度。
  • 提高线程的可管理性。

Java中的四种线程池 . ExecutorService

1. 缓存线程池

缓存线程池.

  • (长度无限制)
  • 执行流程:
    1. 判断线程池是否存在空闲线程
    1. 存在则使用
    1. 不存在,则创建线程 并放入线程池, 然后使用

示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Thread1 {
    public static void main(String[] args) {
        //新建一个缓存线程池对象newCachedThreadPool()
        ExecutorService service = Executors.newCachedThreadPool();
        //指挥线程池执行虚新的任务
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "一去二三里");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "一去二三里");
            }
        });
        try {
            //使主线程休眠一秒,此时线程池有两个线程
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //主线程休眠,使用的是缓存线程池里的线程
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "一去二三里");
            }
        });

    }
}

运行结果,可以看到使用的主线程休眠并未产生新的线程,而是使用的缓存线程池的线程:
在这里插入图片描述

2. 定长线程池

定长线程池.

  • (长度是指定的数值)
  • 执行流程:
    1. 判断线程池是否存在空闲线程
    1. 存在则使用
    1. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
    1. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程

示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Thread1 {
    public static void main(String[] args) {
        //新建一个定长线程池对象  newFixedThreadPool(参数 nThread 指定线程池长度);
        ExecutorService service = Executors.newFixedThreadPool(2);
        //指挥线程池执行虚新的任务
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "一去二三里");
                try {
                    //使主线程休眠三秒,此时线程池有两个线程,需要等待三秒
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "一去二三里");
                try {
                    //使主线程休眠三秒,此时线程池有两个线程,需要等待三秒
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //让线程池里的所有线程休眠三秒,这里便会会等待三秒,等待有空闲线程处理
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "一去二三里");
            }
        });

    }
}
3. 单线程线程池

效果与定长线程池 创建时传入数值1 效果一致.
单线程线程池.
执行流程:

    1. 判断线程池 的那个线程 是否空闲
    1. 空闲则使用
    1. . 不空闲,会等待池中的单个线程空闲后使用
///新建一个单线线程池对象  newSingleThreadExecutor();
ExecutorService service = Executors.newSingleThreadExecutor();
4. 周期性线程池

周期任务 定长线程池.

  • 执行流程:
    1. 判断线程池是否存在空闲线程
    1. 存在则使用
    1. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
    1. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程

周期性任务执行时:

定时执行, 当某个时机触发时, 自动执行某任务

示例:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Thread2 {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
        /**
         * 定时执行
         * 参数1. runnable类型的任务
         * 参数2. 时长数字
         * 参数3. 时长数字的单位
         */
        service.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "一去二三里");
            }
        },5, TimeUnit.SECONDS);     //延迟5秒执行该任务

        /**
         *
         * 周期执行
         * 参数1. runnable类型的任务
         * 参数2. 时长数字(延迟执行的时长)
         * 参数3. 周期时长(每次执行的间隔时间)
         * 参数4. 时长数字的单位
         */
        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "俩人相视一笑~ 嘿嘿嘿");
            }
        }, 5, 2, TimeUnit.SECONDS);     //周期性执行,5秒后开始执行,每间隔1秒执行该任务
    }
}

运行结果,可以看到线程一二在5秒后,同时开始执行任务,线程二每隔1 秒周期性执行任务:
在这里插入图片描述

线程池的状态

线程池的5种状态:Running、ShutDown、Stop、Tidying、Terminated。
在这里插入图片描述
RUNNING

状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。

状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态

SHUTDOWN

状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。

状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。

STOP

状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。

状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

TIDYING

状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。

状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。

TERMINATED

状态说明:线程池彻底终止,就变成TERMINATED状态。

状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING ->
TERMINATED当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

spring 中的线程池[Top]

如果我们需要在 SpringBoot 实现异步编程的话,通过 Spring 提供的两个注解会让这件事情变的非常简单。

  • @EnableAsync:通过在配置类或者Main类上加@EnableAsync开启对异步方法的支持。
  • @Async 可以作用在类上或者方法上,作用在类上代表这个类的所有方法都是异步方法。

五、Lambda表达式

示例:

public class Lambda {
    public static void main(String[] args) {
        //冗余的Runnable代码
        /*
        Thread t = new Thread(new Runnable() {  //更为简洁的 匿名内部类
            @Override
            public void run() {
                System.out.println("三五一十五");
                System.out.println("五七三十五");
            }
        });
        t.start();
        */

        //使用lambda表达式替代,更为简洁
        Thread t = new Thread(() -> {
            System.out.println("三五一十五");
            System.out.println("五七三十五");
        });
        t.start();
    }
}

运行结果均一致。

总结

线程,( ̄︶ ̄)↗ !!!
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值