4-Java多线程编程总结

进程与线程

1. 进程

是指一个内存中 运行 的应用程序,是操作系统进行资源分配和调度的一个独立单位

每个进程都有一个独立的内存空间,一般由程序、数据集合、进程控制块三部分组成。

程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块包含进程的描述信息和控制信息是进程存在的唯一标志。

2. 线程

是进程中的一个 执行路径 共享一个内存空间。

线程之间可以自由切换,并发执行。

一个进程至少有一个线程。

线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。

3. 并发与并行

并发:指两个或多个事件在同一个时间段内发生。

并行:指两个或多个事件在同一时刻发生(同时发生)。

4. 同步与异步

同步:排队执行 , 效率低但是安全。同一个资源按线程排队的顺序依次使用。

异步:同时执行 , 效率高但是数据不安全。同一个资源按线程谁先抢占到谁使用。

5. 线程调度

分时调度:所有线程 轮流 使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

抢占式调度:优先让 优先级 高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性)Java使用的为抢占式调度 。 CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻, 只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

多线程技术

1. 一个线程的生命周期

  • 新建状态:

    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 就绪状态:

    当线程对象调用了 start() 方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  • 运行状态:

    如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态:

    如果一个线程执行了sleep(睡眠)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

    • 等待阻塞:运行状态中的线程执行  wait()  方法,使线程进入到等待阻塞状态。

    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

  • 死亡状态:

    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。线程一旦终止了,就不能复生。

 

2. Java的线程状态 (Thread类:public static enum Thread.State

有一个枚举类 public static enum Thread.State extends Enum<Thread.State>,Thread类中这个嵌套类。

线程可以处于以下状态之一,即Thread.State的值可以为以下五种:(线程在给定时间点只能处于一种状态,这些状态是虚拟机状态,不反映任何操作系统线程状态)

初始(NEW):新创建了一个线程对象,但还没有调用start()方法。

运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的成为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得cpu 时间片后变为运行中状态(running)。线程调度程序从可运行池中选择一个线程作为当前线程所处的状态,这也是线程进入运行状态的唯一一种方式。

阻塞(BLOCKED):表线程阻塞于锁。

等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

超时等待(TIME_WAITING):该状态不同于WAITING,它可以在指定的时间内自行返回。

终止(TERMINATED):表示该线程已经执行完毕。在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

 

线程状态切换

3. 创建线程

Java 提供了三种创建线程的方法:

  • 通过实现 Runnable 接口;
  • 通过继承 Thread 类本身;
  • 通过 Callable 和 Future 创建线程。

1)继承Thread类

创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。

继承类必须 重写 run() 方法(run方法就是线程要执行的任务方法,其方法体内的代码就是一条新的执行路径),该方法是新线程的入口点,必须调用 start() 方法才能执行(虽调用start方法,但触发的是run方法,执行run方法)。

每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程 优先级不能保证线程执行的顺序 ,而且非常依赖于平台。

如:两个线程进行打印

package com.java.day04;

public class Demo04_5 {
    public static void main(String[] args){
        MyThread m = new MyThread();
        //启动一条执行路径
        m.start();
        //这里开始有两条执行路径
        for (int i=0;i<10;i++){
            System.out.println("锄禾日当午"+i);
        }
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            System.out.println("bulabula"+i);
        }

    }
}

执行路径如下:

Java为抢占式调度,哪个线程抢到cpu谁执行,完全随机,有时候可能一个线程执行完毕,另外线程才抢到(如下面右图)。编译运行结果:

守护线程

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。

User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了,因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。守护进程(Daemon)是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件,也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。

守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。

注意: 

(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个 IllegalThreadStateException 异常。你不能把正在运行的常规线程设置为守护线程。

(2) 在Daemon线程中产生的新线程也是Daemon的。 

(3) 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。 因为你不可能知道在所有的User完成之前,Daemon是否已经完成了预期的服务任务。一旦User退出了,可能大量数据还没有来得及读入或写出,计算任务也可能多次运行结果不一样,这对程序是毁灭性的。造成这个结果理由已经说过了:一旦所有User Thread离开了,虚拟机也就退出运行了。 

 

Thread方法

方法名描述
public void start()使该线程开始执行;Java 虚拟机调用该线程的 run 方法
public void run()如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回
public final void setName(String name)改变线程名称,使之与参数 name 相同

public final String getName()

返回此线程的名称

public final void setPriority(int priority)

 更改线程的优先级
public final int getPriority()

返回此线程的优先级

public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程

public final boolean isDaemon()

测试此线程是否为守护程序线程

public final ThreadGroup getThreadGroup()

返回此线程所属的线程组。 如果此线程已死(已停止),则此方法返回null

public long getId()

返回此Thread的标识符

 public Thread.state getState()

返回此线程的状态
public final void join()等待这个线程死亡
public final void join(long millisec)等待该线程终止的时间,最长为 millis 毫秒
public final void join(long millisec,int nanos此线程最多等待 millis毫秒,加上nanos纳秒
public final boolean isAlive()测试线程是否处于活动状态

public void interrupt()

中断线程

public boolean isInterrupted()

测试此线程是否已被中断,线程的中断状态不受此方法的影响

public static boolean interrupted()

测试当前线程是否已被中断,此方法 清除 线程的中断状态,如果连续两次调用此方法,则第二次调用将返回false

public static void yield()暂停当前正在执行的线程对象,并执行其他线程

public static Thread currentThread()

返回对当前正在执行的线程对象的引用。

public static void sleep(long millisec)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
public static void sleep(long millisec,int nanos)导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性

public static int activeCount()

返回当前线程thread group及其子组中活动线程数的 估计值 。 递归迭代当前线程的线程组中的所有子组

返回的值只是一个估计值,因为当此方法遍历内部数据结构时,线程数可能会动态更改,并且可能会受到某些系统线程的影响。 此方法主要用于调试和监视目的

public static boolean holdsLock(Object x)当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
public static void dumpStack()将当前线程的堆栈跟踪打印至标准错误流。

2)实现 Runnable 接口

创建一个线程,最简单的方法是创建一个实现 Runnable 接口的类。Runnable接口应由任何其实例由线程执行的类实现,该类必须定义一个名为run的无参数的方法,此接口旨在为希望在活动时执行代码的对象提供通用协议。

Runnable 提供了一个类活动而不是Thread 。 实现Runnable 类Thread通过实例化Thread例并将其自身作为目标传递而无需子类化Thread。 在大多数情况下,如果只打算覆盖run()方法而不使用其他Thread方法,则应使用Runnable 接口。 这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应对类进行子类化。

在使用Runnable定义的子类中没有start()方法,只有Thread类中才有。Thread类有一个构造方法:public Thread(Runnable targer),可以通过Runnable引用对象构造Thread对象。

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

  • 通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况。

  • 可以避免单继承所带来的局限性。继承Thread后就不能继承其他的类了,Java不支持多继承,但是接口可实现多个。

  • 任务与线程本身是分离的,提高了程序的健壮性。

  • 线程池技术接受Runnable类型的任务,不接收Thread类型的线程。

3)Callable和Future

Callable是一个功能接口,因此可以用作lambda表达式或方法引用的赋值目标。返回结果并可能抛出异常的任务, 实现者定义一个没有参数的单个方法,名为call。

Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行(在得到结果前一直卡在那个位置),如果不调用不会阻塞。

Callable接口类似于Runnable ,因为它们都是为其实例可能由另一个线程执行的类而设计的。 但是, Runnable不会返回结果,也不能抛出已检查的异常。

使用步骤

  • 编写类实现Callable接口 , 实现call方法:
class XXX implements Callable<T> {
@Override
public <T> call() throws Exception {
return T;
}
}
  • 创建FutureTask对象 , 并传入第一步编写的Callable类对象:

FutureTask<Integer> future = new FutureTask<>(callable);
  • 通过Thread,启动线程

new Thread(future).start();
Runnable Callable 的相同点
  • 都是接口
  • 都可以编写多线程程序
  • 都采用Thread.start()启动线程
Runnable Callable 的不同点
  • Runnable没有返回值;Callable可以返回执行结果

  • Callable接口的call()允许抛出异常;Runnablerun()不能抛出

4. 线程安全问题

线程并发执行时,可能会由于不恰当的执行时序而出现不正确的结果。(多个线程去争抢同一个资源,导致线程交替抢占cpu,结果某些线程读到的数据是“脏数据”等不安全情况)。如以下代码:

package com.java.Demo04_05;

public class MyThread {
    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;

        @Override
        public void run() {
            while (count>0){//大机率 三个线程都进入while循环
                //卖票
                System.out.println("正准备卖票");
                try {
                    //让线程休眠,使其他线程更容易抢到cpu 执行
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                e.printStackTrace();
                }
                count--;//到最后count只剩1时 三个线程又依次-- 导致输出不合逻辑的值
                System.out.println("出票成功,余票:"+count);
            }
        }
    }
}

代码编译执行结果(不唯一,下图为两次运行结果):

解决方法:

1)同步代码块(隐式锁:只需要按格式写,不需要自己锁自己开)

关键字为synchronized。​​​​

同步代码块:用一个 锁对象 限制线程,使线程排队。

注:不能每个线程有自己的锁对象,否则每个线程只看自己的锁对象,便没有了效果。因为只有每个对象去使用各自的锁对象,锁对象一定是空闲的。锁对象必须是同一个。

 格式: synchronized(预对象){...}

正确使用示例:

package com.java.Demo04_05;

public class MyThread {
    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();

    @Override
    public void run() {
        while (true) {//三个线程都进入while循环
            synchronized (o) {
                if (count>0) {
                    //卖票
                    System.out.println("正准备卖票");
                    try {
                        //让线程休眠,使其他线程更容易抢到cpu 执行
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
                }else{
                    break;
                }
            }
        }
    }
}

}

代码编译运行结果如下:

错误使用示例:

package com.java.Demo04_05;

public class MyThread {
    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();*/

    @Override
    public void run() {
        //锁对象错误用法 每个线程都只看自己的锁对象 相当无效
        Object o = new Object();
        while (true) {//三个线程都进入while循环
            synchronized (o) {
                if (count>0) {
                    //卖票
                    System.out.println("正准备卖票");
                    try {
                        //让线程休眠,使其他线程更容易抢到cpu 执行
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
                }else{
                    break;
                }
            }
        }
    }
}

}

代码编译运行结果:

2)同步方法(隐式锁)

关键字为synchronized。同步方法,用关键字修饰方法,相当于锁对象就是当前的Ticket对象,即 this;若是为静态方法,则锁对象默认是Ticket.class。

示例(未同步方法):

package com.java.Demo04_05;

public class MyThread {
    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;
        @Override
        public void run() {
            while (true){
                boolean flag = sale();
                if(!flag){
                    break;
                }
            }
        }

        public boolean sale(){
            if (count>0) {
                //卖票
                System.out.println("正准备卖票");
                try {
                    //让线程休眠,使其他线程更容易抢到cpu 执行
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
                return true;
            }
            return false;
        }
    }
}

代码编译运行效果如下:

示例(同步方法)

代码编译运行效果如下:

注:

3)显式锁Lock(自己创建,自己锁自己开)

关键字 Lock,使用其子类 ReentrantLock。

示例:

package com.java.Demo04_05;

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

public class MyThread {
    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 Lock l = new ReentrantLock();
        @Override
        public void run() {
            while (true){
                l.lock();//锁住,只允许一个线程解锁前执行这段代码
                if (count>0) {
                    //卖票
                    System.out.println("正准备卖票");
                    try {
                        //让线程休眠,使其他线程更容易抢到cpu 执行
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
                }else {
                    break;
                }
                l.unlock();//解锁
            }
        }
    }
}

代码编译运行效果如下:

 

synchronized(隐式锁) 与 Lock(显式锁)的区别

——synchronized:

  • 由JVM来维护的,是JVM层面的锁;
  • 程序能够自动获取锁和释放锁。Sync是由系统维护的,如果非逻辑问题的话话,不会出现死锁;
  • 不可中断,除非抛出异常或者正常运行完成;
  • 只能为非公平锁;
  • 不能精确唤醒线程。要么随机唤醒一个线程;要么是唤醒所有等待的线程;
  • 托管给JVM执行,Java1.5中,由于需要调用操作接口,可能导致加锁消耗时间过长,与Lock性比性能低。1.6以后,语义定义更加清晰,有适应自旋、锁粗化、锁消除、轻量级锁、偏向锁等,可进行许多优化,性能提高了,与Lock差不多。

——Lock:

  • 是JDK5以后才出现的具体的类,使用Lock是调用对应的API,是API层面的锁,每条线程会检查Lock是否处于锁住的状态,通过调用对应的API方法来获取锁和释放锁的;
  • 需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。手动获取锁方法:lock.lock()。释放锁:unlock方法。并且需要配合tyr/finaly语句块来完成;
  • 可以中断的。中断方式:(1)调用设置超时方法tryLock(long timeout ,timeUnit unit),(2)调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断;
  • 可以是公平锁也可以是非公平锁的。默认是非公平锁。在其构造方法的时候可以传入Boolean值。true:公平锁、false:非公平锁;
  • 用来实现分组唤醒需要唤醒的线程,可以精确的唤醒;
  • 是java写的控制锁的代码,性能高。

 

公平锁和非公平锁

公平锁对于线程来说是先请求先调度执行,即FIFO队列,队列头部第一个线程永远先执行,系统吞吐量较低.,例如Java中的ReentrantLock默认是非公平锁,可以通过参数改为公平锁。

非公平锁见名知意,并不是线程先请求就会先被执行,底层是采用某种机制可能是优先权抢占,短线程抢占,来抢锁,好处就是提高了系统的吞吐量,其中短线程抢占算法可能会导致长线程得不到执行产生"饥饿"现象,不过可以采用"老化技术"来解决饥饿问题。例如Java中ReentrantLock默认就是非公平锁,还有synchronized也是非公平锁

5. 线程池

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程,就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间(可能比线程实际执行时间要长的多),线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

好处:

 

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

Java中的四种线程池 . ExecutorService

1)缓存线程池(长度无限制)

流程:

  • 判断线程池是否存在空闲线程
  • 存在则使用
  • 不存在,则 创建线程,并放入线程池, 然后使用
//缓存线程池
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()); 
    } 
  }); 
service.execute(new Runnable() { 
    @Override 
    public void run() { 
        System.out.println("线程的名称:"+Thread.currentThread().getName()); 
    } 
  });

2)定长线程池(长度是指定的数值)

流程:

  • 判断线程池是否存在空闲线程
  • 存在则使用
  • 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
  • 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
//定长线程池
ExecutorService service = Executors.newFixedThreadPool(2); 
//向线程池中 加入 新的任务 
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()); 
    } 
  }); 

 

3. 单线程线程池(一个线程)

效果与定长线程池创建时传入数值1效果一致。

流程:

  • 判断线程池 的那个线程 是否空闲
  • 空闲则使用
  • 不空闲,则等待 池中的单个线程空闲后 使用
//单线程线程池
ExecutorService service = Executors.newSingleThreadExecutor(); 
//向线程池中 加入 新的任务 
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()); 
    } 
  }); 

4. 周期性任务定长线程池

周期任务的定长线程池。

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

流程:

  • 判断线程池是否存在空闲线程
  • 存在则使用
  • 不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用
  • 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
//周期性任务定长线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(2); 
/**
* 定时执行 
* 参数1. runnable类型的任务 
* 参数2. 时长数字 
* 参数3. 时长数字的单位 
*/ 
/*service.schedule(new Runnable() 
{ 
    @Override 
    public void run() { 
        System.out.println("俩人相视一笑~ 嘿嘿嘿"); 
    } 
  },5,TimeUnit.SECONDS); */

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

 

6.Lambda表达式

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中),可以使代码变的更加简洁紧凑。

效果等同匿名内部类。

示例(匿名内部类):

示例(Lambda):

参考

Java 多线程编程:https://www.runoob.com/java/java-multithreading.html

JAVA线程之Thread类详解:https://blog.csdn.net/pengqiaowolf/article/details/80442071

Java中的守护线程:https://www.cnblogs.com/qq1290511257/p/10645106.html

Java线程状态切换以及核心方法:https://www.cnblogs.com/cowboys/p/9315331.html

Java显示锁和隐式锁的区别:https://blog.csdn.net/qq_43570075/article/details/106243873

Java中公平锁和非公平锁:https://blog.csdn.net/weixin_37961431/article/details/107923631

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值