Java多线程

Java多线程

Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机, Java 仍是企业和开发人员的首选开发平台。
  

课程内容的介绍

1. 线程中的概念
2. 多线程的实现方式
3. 线程中的常用方法
4. 线程的生命周期
5. 线程同步
6. 其他
  

一、线程的相关概念

1.进程和线程
进程:一个独立的正在执行的程序。
线程:一个进程的最基本的执行单位,执行路径。

  
多进程:在操作系统中,同时运行多个程序。
        多进程的好处:可以充分利用CPU ,提高 CPU 的使用率。
多线程:在同一个进程 ( 应用程序 ) 中同时执行多个线程。
        多线程的好处:提高进程的执行使用率,提高了CPU 的使用率。
  
注意:
1. 在同一个时间点一个 CPU 中只可能有一个线程在执行。
2. 多线程不能提高效率、反而会降低效率,但是可以提高 CPU 的使用率。
3. 一个进程如果有多条执行路径,则称为多线程程序。
4. Java 虚拟机的启动至少开启了两条线程,主线程和垃圾回收线程。
5. 一个线程可以理解为进程的子任务。
    

   

二、线程的实现方式

线程 是程序中执行的线程。 Java 虚拟机允许应用程序同时执行多个执行线程。
  
1.第一种创建方式
实现的步骤:
1. 创建 Thread 类的子类
2. 重写 run 方法
3. 创建线程对象
4. 启动线程
  
案例代码
package com.bobo.thread;

public class ThreadDemo02 {

    /**
     * 线程的第一种实现方式
     *     通过创建Thread类的子类来实现
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("main方法执行了1...");
        // Java中的线程 本质上就是一个Thread对象
        Thread t1 = new ThreadTest01();
        // 启动一个新的线程
        // start方法并不能直接开启一个新的线程,真正开启线程的是 CPU,
        // 当CPU空闲或者分配到此任务的时候就会创建一个新的线程,然后执行run方法中的代码
         t1.start();
         // t1.start(); 线程不能够启动多次 IllegalThreadStateException
        // 如果要创建多个线程,就创建多个Thread对象即可
        Thread t2 = new ThreadTest01();
        t2.start();
        // t1.run(); // 这个是显示的调用Thread的run方法 并没有开启新的线程
        for(int i = 0 ; i< 10 ; i++){
            System.out.println("main方法的循环..."+i);
        }
        System.out.println("main方法执行结束了3...");
    }
}

/**
 * 第一个自定义的线程类
 *    继承Thread父类
 *    重写run方法
 */
class ThreadTest01 extends Thread{

    /**
     * 当线程启动的时候会执行run方法中的代码
     */
    @Override
    public void run() {
        System.out.println("我们的第一个线程执行了2....");
        for(int i = 0 ; i < 10 ; i ++){
            System.out.println("子线程:"+i);
        }
    }
}
  
输出结果
main方法执行了1...
我们的第一个线程执行了2....
我们的第一个线程执行了2....
子线程:0
子线程:1
子线程:2
子线程:3
子线程:4
子线程:5
子线程:6
子线程:7
子线程:8
子线程:9
子线程:0
子线程:1
子线程:2
main方法的循环...0
main方法的循环...1
main方法的循环...2
main方法的循环...3
main方法的循环...4
main方法的循环...5
main方法的循环...6
main方法的循环...7
子线程:3
子线程:4
子线程:5
子线程:6
子线程:7
子线程:8
子线程:9
main方法的循环...8
main方法的循环...9
main方法执行结束了3...
  
通过输出结果我们也能看出来 子线程中的代码和主线程中的代码是 并行 执行的。

  
注意点:
1. 启动线程是使用 start 方法而不是 run 方法。
2. 线程不能启动多次,如果要创建多个线程,那么就需要创建多个 Thread 对象。
  

   
课堂案例
在主线程中打印 1 100 ,然后创建一个子线程实现大文件的复制工作。
package com.bobo.thread;

import java.io.*;

public class ThreadDemo03 extends Thread{

    /**
     * 在主线程中打印1到100,然后创建一个子线程实现大文件的复制工作。
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("主线程开始了...");
        // 创建线程对象
        ThreadDemo03 t1 = new ThreadDemo03();
        t1.start();
        printNum(1,100);//主线程
        System.out.println("主线程结束了...");
    }

    @Override  //重写run方法,
    public void run() {
        long start = System.currentTimeMillis();
        System.out.println("子线程开始文件复制...");
        // 实现文件的复制
        try {
            copyFile(new File("D:/IO/TEST.png"),new File("D:/NewIO/TEST.png"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("子线程结束文件复制...耗时:" + (end-start));
    }

    /**
     * 通过循环打印数字
     * @param start
     * @param end
     */
    public static void printNum(int start , int end){
        //通过循环打印数字
        for (int i = start ; i <= end ; i ++){
            System.out.print(i + "\t");
            if(i % 5 == 0){
                System.out.println();
            }
        }
    }

    /**
     * 实现文件的复制操作
     * @param srcFile  要复制的源文件
     * @param descFile  要复制到的目标文件
     */
    public static void copyFile(File srcFile,File descFile) throws  Exception{

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
        //通过缓冲流去读
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(descFile));
        byte[] bytes = new byte[1024*1024];
        int num = 0 ;
        while((num = bis.read(bytes)) != -1){
            bos.write(bytes,0,num);
        }
        bos.flush();;
        bos.close();
        bis.close();

    }
}
   
通过内部类的方式简化我们的线程的创建。
package com.bobo.thread;

public class ThreadDemo04 {

    /**
     * 如果我们创建的线程类 在程序中我们只需要创建一个线程就不需要再使用的情况下
     *    我们可以通过内部类的方式来简化操作
     * @param args
     */
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override  //重写run方法
            public void run() {
                System.out.println("子线程执行了...");
            }
        };
        t1.start();
    }
}
   
或者
package com.bobo.thread;

public class ThreadDemo05 {

    /**
     * 如果我们创建的线程类 在程序中我们只需要创建一个线程就不需要再使用的情况下
     *    我们可以通过内部类的方式来简化操作
     * @param args
     */
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                System.out.println("子线程执行了.......");
            }
        }.start();
    }
}
  
2.第二种创建方式
在第一种实现方式中,我们是将线程的创建和线程执行的业务都封装在了 Thread 对象中,我们可以通过Runable 接口来实现线程程序代码和数据有效的分离。
Thread(Runnable target)
分配一个新的 Thread对象。
  
实现的步骤:
1. 创建 Runable 的实现类。
2. 重写 run 方法。
3. 创建 Runable 实例对象 ( 通过实现类来实现 )。
4. 创建 Thread 对象,并把第三部的 Runable 实现作为 Thread 构造方法的参数。
5. 启动线程。
  
package com.bobo.runable;

public class RunableDemo01 {

    /**
     * 线程的第二种方式
     *     本质是创建Thread对象的时候传递了一个Runable接口实现
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("main执行了...");
        // 创建一个新的线程  Thread对象
        Runnable r1 = new RunableTest();
        //有别于第一种实现方式,通过Runable接口实现的线程可以多个线程同时操作一个Runable接口对象
        Thread t1 = new Thread(r1);
        // 启动线程
        t1.start();
        // 创建一个新的线程
        System.out.println("main结束了...");
    }
}

/**
 * 线程的第二种创建方式
 *   创建一个Runable接口的实现类
 */
class RunableTest implements Runnable{  //实现一个Runnable接口

    @Override
    public void run() {
        System.out.println("子线程执行了...");
    }
}
  
输出结果:
main执行了...
main结束了...
子线程执行了...
   
有别于第一种实现方式,通过 Runable 接口实现的线程可以多个线程同时操作一个 Runable 接口对象。
/**
* 线程的第二种方式
*     本质是创建Thread对象的时候传递了一个Runable接口实现
* @param args
*/
public static void main(String[] args) {
    System.out.println("main执行了...");
    // 创建一个新的线程  Thread对象
    Runnable r1 = new RunableTest();
    //有别于第一种实现方式,通过Runable接口实现的线程可以多个线程同时操作一个Runable接口对象
    Thread t1 = new Thread(r1);
    // 启动线程
    t1.start();
    // 创建一个新的线程
    Thread t2 = new Thread(r1);
    t2.start();
    System.out.println("main结束了...");
}
  
实现 Runable 接口的好处:
1. 可以避免 Java 单继承带来的局限性。
2. 适合多个相同的程序代码处理同一个资源的情况,把线程同程序的代码和数据有效的分离,较好的体现了面向对象的设计思想。
     
通过 Runable 接口实现的简写方式:
package com.bobo.runable;

public class RunableDemo02 {

    /**
     * 通过Runable接口的方式实现的线程的简写方式  内部类方式
     * @param args
     */
    public static void main(String[] args) {
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程执行了..");
            }
        };
        Thread t1 = new Thread(r1);
        t1.start();

        Thread t2 = new Thread(r1);
        t2.start();
    }
}
  
更简洁的方式
package com.bobo.runable;

public class RunableDemo03 {

    /**
     * 通过Runable接口的方式实现的线程的简写方式  内部类方式
     * @param args
     */
    public static void main(String[] args) {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程执行了..");
            }
        });
        t1.start();

        // 更加的简洁
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程执行了..");
            }
        }).start();
    }
}
  
课堂案例
在主线程中打印 1 100 ,然后创建一个子线程实现大文件的复制工作。
package com.bobo.runable;

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

public class RunableDemo04 {

    /**
     * 课堂案例
     *
     *   在主线程中打印1到100,然后创建一个子线程实现大文件的复制工作。
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("main方法执行了");
        printNum(1,100);
        new Thread(new Runnable() {//用接口实现
            @Override
            public void run() {
                long start = System.currentTimeMillis();
                System.out.println("子线程开始执行");
                try {
                    RunableDemo04.copyFile(new File("d:/IO/TEST.png"),new File("d:/NewIO/TEST.png"));
                }catch (Exception e){
                    e.printStackTrace();
                }//调取异常
                long end = System.currentTimeMillis();//耗时计算
                System.out.println("子线程执行结束,耗时:" + (end - start));
            }
        }).start();
        System.out.println("main方式执行结束");
    }

    /**
     * 通过循环打印数字
     * @param start
     * @param end
     */
    public static void printNum(int start , int end){
        for (int i = start ; i <= end ; i ++){
            System.out.print(i + "\t");
            if(i % 5 == 0){
                System.out.println();
            }
        }
    }

    /**
     * 实现文件的复制操作
     * @param srcFile  要复制的源文件
     * @param descFile  要复制到的目标文件
     */
    public static void copyFile(File srcFile, File descFile) throws  Exception{

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(descFile));
        byte[] bytes = new byte[1024*1024];
        int num = 0 ;
        while((num = bis.read(bytes)) != -1){
            bos.write(bytes,0,num);
        }
        bos.flush();;
        bos.close();
        bis.close();

    }
}
    
3.第三种创建方式
前面我们介绍的两种创建线程的方式都是重写 run 方法,而且 run 方法是没有返回结果的,也就是main方法是不知道开启的线程什么时候开始执行,什么时候结束执行,也获取不到对应的返回结果。而且run 方法也不能把可能产生的异常抛出。在 JDK1.5 之后推出了通过实现 Callable 接口的方式来创建新的线程,这种方式可以获取对应的返回结果。
package com.bobo.callable;

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

public class CallableDemo01 {

    /**
     * 创建线程的第三种实现方式:
     *    Callable方式
     */
    public static void main(String[] args) throws  Exception {
        // 创建一个Callable实例
        Callable<Integer> callable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        // 获取一个线程 肯定是要先创建一个Thread对象  futureTask本质上是Runable接口的实现
        Thread t1 = new Thread(futureTask);
        System.out.println("main方法start....");
        t1.start(); // 本质还是执行的 Runable中的run方法,只是 run方法调用了call方法罢了
        // 获取返回的结果
        System.out.println(futureTask.get()); // 获取开启的线程执行完成后返回的结果
        System.out.println("main方法end ....");

    }
}

/**
 * 创建Callable的实现类
 *    我们需要指定Callable的泛型,这个泛型是返回结果的类型
 */
class MyCallable implements Callable<Integer>{

    /**
     * 线程自动后会执行的方法
     * @return
     * @throws Exception
     */
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for(int i = 1 ; i <= 100 ; i ++){
            sum += i;
        }
        return sum;
    }
}
    
输出结果:
main方法start....
5050
main方法end ....
  
实现 Runnable 接口和实现 Callable 接口的区别:
1. Runnable 是自从 java1.1 就有了,而 Callable 1.5 之后才加上去的。
2. Callable 规定的方法是 call(),Runnable 规定的方法是 run()。
3. Callable 的任务执行后可返回值,而 Runnable 的任务是不能返回值 ( void)。
4. call 方法可以抛出异常, run 方法不可以。
5. 运行 Callable 任务可以拿到一个 Future 对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future 对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
6. 加入线程池运行, Runnable 使用 ExecutorService execute 方法, Callable 使用 submit 方法。
  
其实 Callable 接口底层的实现就是对 Runable 接口实现的封装,线程启动执行的也是 Runable 接口实现中的run 方法,只是在 run 方法中有调用 call 方法罢了。
    

三、线程中常用的方法

1.start方法
start 方法是我们开启一个新的线程的方法,但是并不是直接开启,而是告诉 CPU 我已经准备好了,快点运行我,这是启动一个线程的唯一入口。
void start()
// 导致此线程开始执行; Java虚拟机调用此线程的run方法。
   
2.run方法
线程的线程体,当一个线程开始运行后,执行的就是 run 方法里面的代码,我们不能直接通过线程对象来调用run 方法。因为这并没有产生一个新的线程。仅仅只是一个普通对象的方法调用。
void run()
// 如果这个线程使用单独的Runnable运行对象构造,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回。
  
3.getName方法
获取线程名称的方法。
String getName()
返回此线程的名称。
    
案例代码
package com.bobo.fun;

public class ThreadFunDemo01 {

    /**
     * 线程中的常用的方法
     * @param args
     */
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + " main执行了");
        Runnable runnable = new MyRunable();
        // 创建并启动多个线程
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
    }
}

class MyRunable implements  Runnable{
    @Override
    public void run() {
        // Thread.currentThread() 获取当前线程对象
        System.out.println(Thread.currentThread().getName() + "  执行了...");
    }
}
   
输出结果:
main main执行了
Thread-1  执行了...
Thread-0  执行了...
Thread-6  执行了...
Thread-2  执行了...
Thread-3  执行了...
Thread-5  执行了...
Thread-4  执行了...
   
默认的主方法的线程名称是 main, 而其他子线程的名称默认的是 Thread- 编号,我们不难发现默认的线程名称的识别度不高,那么我们是否可以自定义线程名称呢?显然是可以的。
    
继承Thread
package com.bobo.fun;

public class ThreadFunDemo02{

    /**
     * 线程中的常用的方法
     * @param args
     */
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + " main执行了");
        // 创建并启动多个线程
        Thread t1 = new MyThread01("线程A");
        t1.start();
        Thread t2 = new MyThread01("线程B");
        t2.start();
        Thread t3 = new MyThread01("线程C");
        t3.start();


    }
}

class MyThread01 extends Thread{


    public MyThread01(String threadName){
        // 指定线程的名称
        super(threadName);
    }
    @Override
    public void run() {
        System.out.println(this.getName() + " 执行了...");
    }
}


    
输出结果:
main main执行了
线程A 执行了...
线程C 执行了...
线程B 执行了...
     
实现 Runable 接口,本质还是 Thread 有参的构造方法实现的名称重命名。
package com.bobo.fun;

public class ThreadFunDemo03 {

    public static void main(String[] args) {
        Runnable runnable = new MyRunable01();
        // 重命名 线程的名称
        Thread t1 = new Thread(runnable,"线程I");
        t1.start();
        Thread t2 = new Thread(runnable,"线程II");
        t2.start();
        Thread t3 = new Thread(runnable,"线程III");
        t3.start();
    }
}

class MyRunable01 implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "  执行了...");
    }
}
    
输出结果
线程II  执行了...
线程III  执行了...
线程I  执行了...
  
4.优先级
我们创建的多个线程的执行顺序是由 CPU 决定的。 Java 中提供了一个线程调度器来监控程序中启动后进入就绪状态的所有的线程,优先级高的线程会获取到比较多运行机会。
/**
* 最小的优先级是 1
*/
public final static int MIN_PRIORITY = 1;

/**
* 默认的优先级都是5
*/
public final static int NORM_PRIORITY = 5;

/**
* 最大的优先级是10
*/
public final static int MAX_PRIORITY = 10;
    
案例代码
package com.bobo.fun;

public class ThreadFunDemo05 {

    /**
     * 线程的优先级
     * @param args
     */
    public static void main(String[] args) {
        Runnable runnable = new MyRunable02();
        for(int i = 1 ; i <= 10 ; i ++){
            Thread t1 = new Thread(runnable,"" + i);
            t1.setPriority(i); // 设置优先级大小
            t1.start();
        }
    }
}

class MyRunable02 implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+" 执行了 优先级:" + Thread.currentThread().getPriority());
    }
}
    
输出结果:
9 执行了 优先级:9
6 执行了 优先级:6
3 执行了 优先级:3
5 执行了 优先级:5
4 执行了 优先级:4
10 执行了 优先级:10
8 执行了 优先级:8
7 执行了 优先级:7
2 执行了 优先级:2
1 执行了 优先级:1
  
大家会发现,设置了优先级后输出的结果和我们预期的并不一样,这是为什么呢?优先级在 CPU 调动线程执行的时候会是一个参考因数,但不是决定因数。
    
5.sleep方法
将当前线程暂定指定的时间。
static void sleep(long millis)
// 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
    
演示案例
package com.bobo.fun;

public class ThreadFunDemo06 {

    /**
     * sleep 方法
     *    休眠
     * @param args
     */
    public static void main(String[] args) throws Exception{
        System.out.println("main ... start");
        Thread.sleep(1000);
        Runnable runnable = new MyRunable03();
        new Thread(runnable).start();
        Thread.sleep(1000);
        System.out.println("main .... end ");
    }
}

class MyRunable03 implements Runnable{

    @Override
    public void run() {
        for (int i = 1 ; i < 50 ; i ++){
            // 每循环一次 休眠 100毫秒
            try {
                // 将当前线程休眠指定的时间
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " :" + i);
        }
    }
}
  
课堂案例:
练习 1 :设计一个线程类:创建 3 个子线程,每个线程分别打印数字,分别睡 100,200,300
package com.bobo.fundemo;

public class FunDemo01 {

    /**
     * 练习1:设计一个线程类:创建3个子线程,每个线程分别打印数字,分别睡100,200,300
     * @param args
     */
    public static void main(String[] args) {
        new Thread(new MyRunable01(100),"A").start();
        new Thread(new MyRunable01(200),"B").start();
        new Thread(new MyRunable01(300),"C").start();
    }
}

class MyRunable01 implements Runnable{

    private int sleepTime;

    public MyRunable01(int sleepTime){
        this.sleepTime = sleepTime;
    }

    @Override
    public void run() {
        for(int i = 0 ; i < 10; i ++){
            System.out.println(Thread.currentThread().getName() + " :" + i);
            // 休眠指定的时间
            try {
                Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
     
输出结果:
B :0
A :0
C :0
A :1
B :1
A :2
C :1
A :3
B :2
A :4
A :5
C :2
B :3
A :6
A :7
B :4
A :8
C :3
A :9
B :5
B :6
C :4
B :7
C :5
B :8
B :9
C :6
C :7
C :8
C :9
     
练习 2 :设计一个线程:运行 10 秒后被终止 ( 掌握线程的终止方法 )
package com.bobo.fundemo;

import java.util.Date;

public class FunDemo02 {

    /**
     * 练习2:设计一个线程:运行10秒后被终止(掌握线程的终止方法)
     * @param args
     */
    public static void main(String[] args)  throws Exception{
        MyRunable02 runnable = new MyRunable02();
        new Thread(runnable).start();
        Thread.sleep(10000); // 主线程休眠10秒钟
        runnable.flag = false;
        System.out.println("main、  end ...");
    }
}

class MyRunable02 implements Runnable{

    boolean flag = true;

    @Override
    public void run() {
        while(flag){
            try {
                Thread.sleep(1000);
                System.out.println(new Date());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " 执行完成");
    }
}
     
练习 3 :设计一个线程类,启动 2 个子线程,一个子线程每打印 10 个数字睡眠,另一个线程每打印 20 个睡眠。。
package com.bobo.fundemo;

public class FunDemo03 {

    /**
     * 练习3:设计一个线程类,启动2个子线程,一个子线程每打印10个数字睡眠,另一个线程每打印20个睡眠。。
     * @param args
     */
    public static void main(String[] args) {
        new Thread(new MyRunableO3(),"A").start();
        new Thread(new MyRunableO3(),"B").start();
    }
}

class MyRunableO3 implements Runnable{

    private int i = 0;

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        while (i <  1000){
            if("A".equals(name)){
                if(i % 10==0 && i != 0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }else if("B".equals(name)){
                if(i%20 ==0 && i!= 0){
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println(name + "\t" + i++);
        }


    }
}
     
6.isAlive
获取线程的状态。
package com.bobo.fundemo;

public class FunDemo04 {

    /**
     * isAlive方法
     * @param args
     */
    public static void main(String[] args) {

        System.out.println("main  start ...");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " .... ");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        System.out.println("线程的状态:"+t1.isAlive());
        t1.start();
        System.out.println("线程的状态:"+t1.isAlive());
        System.out.println("main  end ...");
    }
}
  
输出结果
main  start ...
线程的状态:false
线程的状态:true
main  end ...
Thread-0 .... 
   
7.join
调用某线程的该方法,将当前线程和该线程合并,即等待该线程结束,在恢复当前线程的运行。
package com.bobo.fundemo;

public class FunDemo05 {

    /**
     * 线程的合并
     *     join方法
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("main  start ...");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0 ; i < 10; i++){
                    System.out.println(Thread.currentThread().getName() + " 子线程执行了...");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        });
        t1.start();
        try {
            t1.join(); // 线程的合并,和主线程合并  相当于我们直接调用了run方法
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main end ...");
    }
}
    
输出结果:
main  start ...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
main end ...
  
8.yield
让出 CPU ,当前线程进入就绪状态。
package com.bobo.fundemo;

public class FuneDemo06 extends Thread{

    public FuneDemo06(String threadName){
        super(threadName);
    }

    /**
     * yield方法  礼让
     *
     * @param args
     */
    public static void main(String[] args) {
        FuneDemo06 f1 = new FuneDemo06("A1");
        FuneDemo06 f2 = new FuneDemo06("A2");
        FuneDemo06 f3 = new FuneDemo06("A3");

        f1.start();
        f2.start();
        f3.start();
    }

    @Override
    public void run() {
        for(int i = 0 ; i < 100; i ++){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if(i%10 == 0 && i != 0){
                System.out.println(Thread.currentThread().getName()+" 礼让:" + i);
                Thread.currentThread().yield(); // 让出CPU
            }else{
                System.out.println(this.getName() + ":" + i);
            }
        }
    }
}
  
9.waitnotify/notifyAll
阻塞和唤醒的方法,是 Object 中的方法,我们在数据同步的时候会介绍到。
    

四、线程的生命周期

1. 生命周期
生命周期:对象从创建到销毁的全过程。
线程的生命周期:线程对象(Thread)从开始到销毁的全过程。

  
线程的状态:
1. 创建 Thread 对象。
2. 就绪状态 执行 start 方法后线程进入可运行的状态。
3. 运行状态 CPU 运行。
4. 阻塞状态 运行过程中被中断 ( 等待阻塞,对象锁阻塞,其他阻塞 )。
5. 终止状态 线程执行完成。
    
2. 线程的中断
2.1 设置标志位
如果线程的 run 方法中执行的是一个重复执行的循环,可以提供一个标记来控制循环是否继续。
package com.bobo.fundemo;

import java.util.Date;

public class FunDemo02 {

    /**
     * 练习2:设计一个线程:运行10秒后被终止(掌握线程的终止方法)
     * @param args
     */
    public static void main(String[] args)  throws Exception{
        MyRunable02 runnable = new MyRunable02();
        new Thread(runnable).start();
        Thread.sleep(10000); // 主线程休眠10秒钟
        runnable.flag = false;
        System.out.println("main、  end ...");
    }
}

class MyRunable02 implements Runnable{

    boolean flag = true;

    @Override
    public void run() {
        while(flag){
            try {
                Thread.sleep(1000);
                System.out.println(new Date());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " 执行完成");
    }
}
  
2.2 利用中断标志位
在线程中有个中断的标志位,默认是 false ,当我们显示的调用 interrupted 方法或者 isInterrupted 方法是会修改标志位为true 。我们可以利用此来中断运行的线程。
package com.bobo.fundemo;

public class FunDemo07 extends Thread{

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new FunDemo07();
        t1.start();
        Thread.sleep(3000);
         t1.interrupt(); // 中断线程 将中断标志由false修改为了true
        // t1.stop(); // 直接就把线程给kill掉了
        System.out.println("main .... ");
    }

    @Override
    public void run() {
        System.out.println(this.getName() + " start...");
        int i = 0 ;
        // Thread.interrupted() 如果没有被中断 那么是false 如果显示的执行了interrupt 方法就会修改为 true
         while(!Thread.interrupted()){
        //while(!Thread.currentThread().isInterrupted()){
            System.out.println(this.getName() + " " + i);
            i++;
        }

        System.out.println(this.getName()+ " end .... ");

    }
}
  
2.3 利用InterruptedException
如果线程因为执行 join(),sleep() 或者 wait() 而进入阻塞状态,此时要想停止它,可以让他调用interrupt(),程序会抛出 InterruptedException 异常。我们利用这个异常可以来终止线程。
package com.bobo;

public class FunDemo08 extends Thread{

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new FunDemo08();
        t1.start();
        Thread.sleep(3000);
         t1.interrupt(); // 中断线程 将中断标志由false修改为了true
        // t1.stop(); // 直接就把线程给kill掉了
        System.out.println("main .... ");
    }

    @Override
    public void run() {
        System.out.println(this.getName() + " start...");
        int i = 0 ;
        // Thread.interrupted() 如果没有被中断 那么是false 如果显示的执行了interrupt 方法就会修改为 true
         while(!Thread.interrupted()){
        //while(!Thread.currentThread().isInterrupted()){
             try {
                 Thread.sleep(10000);
             } catch (InterruptedException e) {
                 // e.printStackTrace();
                 System.out.println("中断线程");
                 break;
             }
             System.out.println(this.getName() + " " + i);
            i++;
        }

        System.out.println(this.getName()+ " end .... ");

    }
}

   

  
利用异常 InterruptedException 来中断线程。
  

    

五、线程数据安全问题

多个线程操作同一份数据会出现的安全问题。
    
1. 数据安全问题分析
1.1 继承Thread方式
通过继承 Thread 父类的方式,多个子线程操作对象的成员变量不会出现数据共享的问题。
package com.bobo.thread1;

public class ThreadDemo01 {

    /**
     * 数据安全问题
     *     线程的创建方式有两种
     *        继承自Thread类实现
     *        实现Runable接口
     * @param args
     */
    public static void main(String[] args) {
        MyThread01 t1 = new MyThread01();
        t1.setName("A");
        MyThread01 t2 = new MyThread01();
        t2.setName("B");
        MyThread01 t3 = new MyThread01();
        t3.setName("C");

        t1.start();
        t2.start();
        t3.start();
    }


}

class MyThread01 extends Thread{

    private Integer count = 0;

    @Override
    public void run() {
        //System.out.println(Thread.currentThread().getName()+ "  执行了");
        // 操作count
        while(count < 10){
            count++;
            System.out.println(Thread.currentThread().getName()+ "  执行了" + count);
        }

    }
}


   
线程之间相互操作的是独立的数据。

  

    
如果多个子线程操作的是静态属性,那么这时多个线程会共享该数据。
package com.bobo.thread1;

public class ThreadDemo02{

    /**
     * 数据安全问题
     *     线程的创建方式有两种
     *        继承自Thread类实现
     *        实现Runable接口
     * @param args
     */
    public static void main(String[] args) {
        MyThread02 t1 = new MyThread02();
        t1.setName("A");
        MyThread02 t2 = new MyThread02();
        t2.setName("B");
        MyThread02 t3 = new MyThread02();
        t3.setName("C");

        t1.start();
        t2.start();
        t3.start();
    }


}

class MyThread02 extends Thread{

    private static Integer count = 0;

    @Override
    public void run() {
        //System.out.println(Thread.currentThread().getName()+ "  执行了");
        // 操作count
        while(count < 10){
            count++;
            System.out.println(Thread.currentThread().getName()+ "  执行了" + count);
        }

    }
}


  
输出结果

     
1.2 实现Runable接口
我们通过 Runable 接口的方式创建的线程,如果多个子线程操作的是同一个 Runable 对象那么同一个对象的普通成员变量是共享的。
package com.bobo.thread1;

public class ThreadDemo03 {

    public static void main(String[] args) {
        Runnable runnable = new MyRunable();
        Runnable runnable1 = new MyRunable();
        Thread t1 = new Thread(runnable,"A");
        Thread t2 = new Thread(runnable,"B");
        Thread t3 = new Thread(runnable,"C");
        // t4 线程操作的是另外一个Runable对象
        Thread t4 = new Thread(runnable1,"D");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

class MyRunable implements Runnable{

    private Integer count = 0;

    @Override
    public void run() {
        while(count < 10){
            count ++;
            System.out.println(Thread.currentThread().getName() + " :" + count);
        }
    }
}
    
输出结果
C :2
C :4
A :1
C :5
A :6
C :7
A :8
C :9
A :10
D :1
D :2
D :3
D :4
D :5
D :6
D :7
B :3
D :8
D :9
D :10
  
前面三个线程共享第一个 Runable 对象的属性,第四个操作的是第二个 Runable 对象的属性。

     
2.数据完全问题的原因
案例代码:
package com.bobo.thread1;

public class ThreadDemo04 {

    /**
     * 通过多线程模拟火车站售票的场景
     *    有100张票  3个窗口售卖
     * @param args
     */
    public static void main(String[] args) {
        // 火车票资料
        Runnable runnable = new MyRunable04();
        Thread t1 = new Thread(runnable,"窗口A");
        Thread t2 = new Thread(runnable,"窗口B");
        Thread t3 = new Thread(runnable,"窗口C");

        t1.start();
        t2.start();
        t3.start();
    }
}

class MyRunable04 implements Runnable{

    // 火车票
    private Integer ticket = 10;

    @Override
    public void run() {

        while(ticket > 0){
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" 卖出了第:" + ticket-- + "张票" );
        }
    }
}
    
输出结果
窗口B 卖出了第:9张票
窗口A 卖出了第:10张票
窗口C 卖出了第:10张票
窗口B 卖出了第:8张票
窗口A 卖出了第:8张票
窗口C 卖出了第:7张票
窗口A 卖出了第:6张票
窗口B 卖出了第:5张票
窗口C 卖出了第:6张票
窗口A 卖出了第:3张票
窗口B 卖出了第:4张票
窗口C 卖出了第:4张票
窗口B 卖出了第:2张票
窗口A 卖出了第:0张票
窗口C 卖出了第:1张票
  
我们一共只有 10 张票,但是确卖出去了 18 张票,这个显然出现了数据完全问题。

  
多个子线程操作飞原子的代码,出现的问题,其实就是不能保证原子性出现的问题。
  
在多线程中出现数据安全问题的本质原因有三个:
1. 原子性
2. 可见性
3. 有序性
  
原子性:多个操作要么同时执行要么都不执行。
try {
    Thread.sleep(1);
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 卖出了第:" + ticket-- + "张票" );
  
3.数据安全问题的解决方式
3.1 同步代码块
在我们需要保证原子性的代码前后加上同步代码块。
synchroized(obj){
    // 需要同步的代码
}
package com.bobo.thread1;

public class ThreadDemo05 {

    /**
     * 通过多线程模拟火车站售票的场景
     *    有100张票  3个窗口售卖
     * @param args
     */
    public static void main(String[] args) {
        // 火车票资料
        Runnable runnable = new MyRunable05();
        Thread t1 = new Thread(runnable,"窗口A");
        Thread t2 = new Thread(runnable,"窗口B");
        Thread t3 = new Thread(runnable,"窗口C");

        t1.start();
        t2.start();
        t3.start();
    }
}

class MyRunable05 implements Runnable{

    // 火车票
    private Integer ticket = 10;

    private Object obj = new Object();

    @Override
    public void run() {

        while(ticket > 0){
            // 这个锁对象必须是 共享对象
            synchronized (obj){
                if(ticket <= 0) continue;
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" 卖出了第:" + ticket-- + "张票" );
            }

        }
    }
}
    
输出结果
窗口A 卖出了第:10张票
窗口A 卖出了第:9张票
窗口A 卖出了第:8张票
窗口A 卖出了第:7张票
窗口A 卖出了第:6张票
窗口A 卖出了第:5张票
窗口A 卖出了第:4张票
窗口A 卖出了第:3张票
窗口A 卖出了第:2张票
窗口A 卖出了第:1张票
    
3.2 同步方法
在方法的声明中添加 cynchronized 关键字,表示整个方法被同步,整个方法同一时间只能够被一个线程访问。
package com.bobo.thread1;

public class ThreadDemo06 {

    /**
     * 通过多线程模拟火车站售票的场景
     *    有100张票  3个窗口售卖
     * @param args
     */
    public static void main(String[] args) {
        // 火车票资料
        Runnable runnable = new MyRunable06();
        Thread t1 = new Thread(runnable,"窗口A");
        Thread t2 = new Thread(runnable,"窗口B");
        Thread t3 = new Thread(runnable,"窗口C");

        t1.start();
        t2.start();
        t3.start();
    }
}

class MyRunable06 implements Runnable{

    // 火车票
    private Integer ticket = 10;

    private Object obj = new Object();

    @Override
    public void run() {

        while(ticket > 0){
            sellTicket();
        }
    }

    /**
     * 同步方法
     *    同步普通方法 锁的是 this
     *    同步静态方法  锁的是 类对象
     */
    public synchronized void sellTicket(){
        if(ticket <= 0) return;
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" 卖出了第:" + ticket-- + "张票" );
    }
}
  
同步方法和同步代码块的区别
1. 锁的对象不同,同步方法(普通方法 this ,静态方法 类对象) 同步代码块 指定的任意的对象。
2. 性能,同步方法将整个方法锁定,而同步代码块更加的灵活。
  
3.3 Lock
Lock 是一种比 Synchroized 更加灵活的一种加锁方式,使用的时候我们必须显示的加锁 lock.lock 然后在释放锁的时候我们也需要显示的 lock.unlock 调用。
package com.bobo.thread1;

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

public class ThreadDemo07 {

    /**
     * 通过多线程模拟火车站售票的场景
     *    有100张票  3个窗口售卖
     * @param args
     */
    public static void main(String[] args) {
        // 火车票资料
        Runnable runnable = new MyRunable07();
        Thread t1 = new Thread(runnable,"窗口A");
        Thread t2 = new Thread(runnable,"窗口B");
        Thread t3 = new Thread(runnable,"窗口C");

        t1.start();
        t2.start();
        t3.start();
    }
}

class MyRunable07 implements Runnable{

    // 火车票
    private  Integer ticket = 10;


    private Lock lock = new ReentrantLock();

    @Override
    public void run() {

        while(ticket > 0){
            // 加锁
            lock.lock();
            if(ticket <= 0) break;
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" 卖出了第:" + ticket-- + "张票" );
            // 释放锁
            lock.unlock();
        }
    }


}
  
AQS源码分析:
J.U.C核心
Abstract Queued Synchronizer  AQS丝滑般流畅
  
好了,我们来开始今天的内容,首先我们来看下AQS是什么,全称是
AbstractQueuedSynchronizer 翻译过来就是【抽象队列同步】对吧。通过名字我们也能看出这是个抽象类

   
而且里面定义了很多的方法。

  
里面这么多方法,咱们当然不是一个个去翻。里面还有很多的抽象方法,咱们还得找它的实现多麻烦对不对。所以我们换个方式来探索。
  
场景模拟
  我们先来看下这样一个场景

  
在这里我们有一个能被多个线程共享操作的资源,在这个场景中应该能看出我们的数据是不安全的,因为我们并不能保证我们的操作是原子操作对吧。基于这个场景我们通过代码来看看效果。
package com.example.demo;

public class AtomicDemo {

    // 共享变量
    private static int count = 0;

    // 操作共享变量的方法
    public static void incr(){
        // 为了演示效果  休眠一下子
        try {
            Thread.sleep(1);
            count ++;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000 ; i++) {
            new Thread(()->AtomicDemo.incr()).start();
        }

        Thread.sleep(4000);
        System.out.println("result:" + count);
    }

}

  

通过执行发现,执行的结果是一个不确定的值,但总是会小于等于1000,至于原因,是因为incr() 方法不是一个原子操作。为什么不是原子操作这个咱们今天就不深究此处了。

  
迎合今天的主题,我们通过Lock来解决

package com.example.demo;

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

public class AtomicDemo {

    // 共享变量
    private static int count = 0;

    private static Lock lock = new ReentrantLock();

    // 操作共享变量的方法
    public static void incr(){
        // 为了演示效果  休眠一下子
        try {
            lock.lock();
            Thread.sleep(1);
            count ++;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000 ; i++) {
            new Thread(()->AtomicDemo.incr()).start();
        }

        Thread.sleep(4000);
        System.out.println("result:" + count);
    }

}

  

然后我们运行发现结果都是 1000了,这也就是1000个线程都去操作这个 count 变量,结果符合我们的预期了。那lock到底是怎么实现的呢?

   
需求分析
  我们先来分析分析

  

这样的图片看着比较复杂,咱们简化下。

  

我们自己假设下,如果要你去设计这样的方法,你应该要怎么设计,他们需要实现哪些功能。

  

首先是lock方法,它是不是要满足这几个功能。

  

需求清楚了,那我们怎么设计呢?

  
第一个互斥怎么做,也就是多个线程只有一个线程能抢占到资源,这个时候我们可以这样设置。

// 给一个共享资源
Int state = 0 ; // 0表示资源没有被占用,可以抢占
if(state == 0 ){
   // 表示可以获取锁
}else{
   // 表示锁被抢占 需要阻塞等待
}
   

  

然后就是没有抢占到锁的线程的存储,我们可以通过一个队列,利用FIFO来实现存储。

   
最后就是线程的阻塞和唤醒。大家说说有哪些阻塞线程的方式呀?

1.wait/notify: 不合适,不能唤醒指定的线程
2.Sleep:休眠,类似于定时器
3.Condition:可以唤醒特定线程
4.LockSupport:
  LockSupport.park():阻塞当前线程
  LockSupport.unpark(Thread t):唤醒特定线程

  
结合今天的主题,我们选择LockSupport来实现阻塞和唤醒。

  

好了,到这儿我们已经猜想到了Lock中的实现逻辑,但是在探究源码之前我们还有个概念需要先和大家讲下,因为这个是我们源码中会接触到的一个,先讲了,看的时候就比较轻松了对吧。

    
什么是重入锁?
  我们先来看看重入锁的场景代码

package com.example.demo;

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

public class AtomicDemo {

    // 共享变量
    private static int count = 0;

    private static Lock lock = new ReentrantLock();

    // 操作共享变量的方法
    public static void incr(){
        // 为了演示效果  休眠一下子
        try {
            lock.lock();
            Thread.sleep(1);
            count ++;
            // 调用了另外一个方法。
            decr();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public static void decr(){
        try {
            // 重入锁
            lock.lock();
            count--;
        }catch(Exception e){

        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000 ; i++) {
            new Thread(()->AtomicDemo.incr()).start();
        }

        Thread.sleep(4000);
        System.out.println("result:" + count);
    }

}
  
首先大家考虑这段代码会死锁吗? 大家给我个回复,我看看大家的理解的怎么样
  
好了,有说会 死锁的,有说不会,其实这儿是不会死锁的,而且结果就是0.为什么呢?
 
这个其实是锁的一个嵌套,因为这两把锁都是同一个 线程对象,我们讲共享变量的设计是当state=0;线程可以抢占到资源 state =1; 如果进去嵌套访问 共享资源,这时 state = 2 如果有多个嵌套 state会一直累加,释放资源的时候, state–,直到所有重入的锁都释放掉 state=0,那么其他线程才能继续抢占资源,说白了重入锁的设计目的就是为了防止 死锁
  
AQS类图

  
通过类图我们可以发现右车的业务应用其实内在都有相识的设计,这里我们只需要搞清楚其中的一个,其他的你自己应该就可以看懂~,好了我们就具体结合前面的案例代码,以ReentrantLock为例来介绍AQS的代码实现。
  
源码分析
  在看源码之前先回顾下这个图,带着问题去看,会更轻松

  
Lock.lock()
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
     

这个方法逻辑比较简单,if条件成立说明 抢占锁成功并设置 当前线程为独占锁,else 表示抢占失败,acquire(1) 方法我们后面具体介绍。

compareAndSetState(0, 1):用到了CAS 是一个原子操作方法,底层是UnSafe.作用就是设置 共享操作的 state 由0到1. 如果state的值是0就修改为1。

setExclusiveOwnerThread:代码很简单,进去看一眼即可。

  

acquire方法

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

  

1.tryAcquire()尝试直接去获取资源,如果成功则直接返回(这里体现了非公平锁,每个线程获取锁时会尝试直接抢占加塞一次,而CLH队列中可能还有别的线程在等待);
2.addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
3.acquireQueued()使线程阻塞在等待队列中获取资源,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。

  
当然这里代码的作用我是提前研究过的,对于大家肯定不是很清楚,我们继续里面去看,最后大家可以回到这儿再论证。

  

tryAcquire(int)
  再次尝试抢占锁

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
//再次尝试抢占锁
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
// 重入锁的情况
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
// false 表示抢占失败
    return false;
}
   
addWaiter
  将阻塞的线程添加到双向链表的结尾。
private Node addWaiter(Node mode) {
    //以给定模式构造结点。mode有两种:EXCLUSIVE(独占)和SHARED(共享)
    Node node = new Node(Thread.currentThread(), mode);

    //尝试快速方式直接放到队尾。
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }

    //上一步失败则通过enq入队。
    enq(node);
    return node;
}

    

enq(Node)

private Node enq(final Node node) {
    //CAS"自旋",直到成功加入队尾
    for (;;) {
        Node t = tail;
        if (t == null) { // 队列为空,创建一个空的标志结点作为head结点,并将tail也指向它。
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {//正常流程,放入队尾
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

    

第一个if语句

    

else语句

  

线程3进来会执行如下代码

  

那么效果图

  

acquireQueued(Node, int)
OK,通过tryAcquire()和addWaiter(),该线程获取资源失败,已经被放入等待队列尾部了。聪明的你立刻应该能想到该线程下一部该干什么了吧:进入等待状态休息,直到其他线程彻底释放资源后唤醒自己,自己再拿到资源,然后就可以去干自己想干的事了。没错,就是这样!是不是跟医院排队拿号有点相似~~acquireQueued()就是干这件事:在等待队列中排队拿号(中间没其它事干可以休息),直到拿到号后再返回。这个函数非常关键,还是上源码吧:

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;//标记是否成功拿到资源
    try {
        boolean interrupted = false;//标记等待过程中是否被中断过

        //又是一个“自旋”!
        for (;;) {
            final Node p = node.predecessor();//拿到前驱
            //如果前驱是head,即该结点已成老二,那么便有资格去尝试获取资源(可能是老大释放完资源唤醒自己的,当然也可能被interrupt了)。
            if (p == head && tryAcquire(arg)) {
                setHead(node);//拿到资源后,将head指向该结点。所以head所指的标杆结点,就是当前获取到资源的那个结点或null。
                p.next = null; // setHead中node.prev已置为null,此处再将head.next置为null,就是为了方便GC回收以前的head结点。也就意味着之前拿完资源的结点出队了!
                failed = false; // 成功获取资源
                return interrupted;//返回等待过程中是否被中断过
            }

            //如果自己可以休息了,就通过park()进入waiting状态,直到被unpark()。如果不可中断的情况下被中断了,那么会从park()中醒过来,发现拿不到资源,从而继续进入park()等待。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;//如果等待过程中被中断过,哪怕只有那么一次,就将interrupted标记为true
        }
    } finally {
        if (failed) // 如果等待过程中没有成功获取资源(如timeout,或者可中断的情况下被中断了),那么取消结点在队列中的等待。
            cancelAcquire(node);
    }
}

到这里了,我们先不急着总结acquireQueued()的函数流程,先看看shouldParkAfterFailedAcquire()和parkAndCheckInterrupt()具体干些什么。

      
shouldParkAfterFailedAcquire(Node, Node)

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;//拿到前驱的状态
    if (ws == Node.SIGNAL)
        //如果已经告诉前驱拿完号后通知自己一下,那就可以安心休息了
        return true;
    if (ws > 0) {
        /*
         * 如果前驱放弃了,那就一直往前找,直到找到最近一个正常等待的状态,并排在它的后边。
         * 注意:那些放弃的结点,由于被自己“加塞”到它们前边,它们相当于形成一个无引用链,稍后就会被保安大叔赶走了(GC回收)!
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
         //如果前驱正常,那就把前驱的状态设置成SIGNAL,告诉它拿完号后通知自己一下。有可能失败,人家说不定刚刚释放完呢!
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

      

整个流程中,如果前驱结点的状态不是SIGNAL,那么自己就不能安心去休息,需要去找个安心的休息点,同时可以再尝试下看有没有机会轮到自己拿号。

      

parkAndCheckInterrupt()
如果线程找好安全休息点后,那就可以安心去休息了。此方法就是让线程去休息,真正进入等待状态。

 private final boolean parkAndCheckInterrupt() {
     LockSupport.park(this);//调用park()使线程进入waiting状态
     return Thread.interrupted();//如果被唤醒,查看自己是不是被中断的。
 }

    

好了,我们可以小结下了。

看了shouldParkAfterFailedAcquire()和parkAndCheckInterrupt(),现在让我们再回到acquireQueued(),总结下该函数的具体流程:

1.结点进入队尾后,检查状态,找到安全休息点;
2.调用park()进入waiting状态,等待unpark()或interrupt()唤醒自己;
3.被唤醒后,看自己是不是有资格能拿到号。如果拿到,head指向当前结点,并返回从入队到拿到号的整个过程中是否被中断过;如果没拿到,继续流程1。

     
最后我们再回到前面的acquire方法来总结下

public final void acquire(int arg) {
     if (!tryAcquire(arg) &&
         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
         selfInterrupt();
 }

    

总结下它的流程吧

1.调用自定义同步器的tryAcquire()尝试直接去获取资源,如果成功则直接返回;
2.没成功,则addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
3.acquireQueued()使线程在等待队列中休息,有机会时(轮到自己,会被unpark())会去尝试获取资源。获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
4.如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。

   

Lock.unlock()
好了,lock方法看完后,我们再来看下unlock方法。

  

release(int)
它会释放指定量的资源,如果彻底释放了(即state=0),它会唤醒等待队列里的其他线程来获取资源。这也正是unlock()的语义,当然不仅仅只限于unlock()。

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;//找到头结点
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);//唤醒等待队列里的下一个线程
        return true;
    }
    return false;
}

  

ryRelease(int)
此方法尝试去释放指定量的资源。下面是tryRelease()的源码:

 public final boolean release(int arg) {
        if (tryRelease(arg)) {//这里是先尝试释放一下资源,一般都可以释放成功,除了多次重入但只释放一次的情况。
            Node h = head;
            //这里判断的是 阻塞队列是否还存在和head节点是否是tail节点,因为之前说过,队列的尾节点的waitStatus是为0的
            if (h != null && h.waitStatus != 0)
                //到这里就说明head节点已经释放成功啦,就先去叫醒后面的直接节点去抢资源吧
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

private void unparkSuccessor(Node node) {
    //这里,node一般为当前线程所在的结点。
    int ws = node.waitStatus;
    if (ws < 0)//置零当前线程所在的结点状态,允许失败。
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;//找到下一个需要唤醒的结点s
    if (s == null || s.waitStatus > 0) {//如果为空或已取消
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev) // 从后向前找。
            if (t.waitStatus <= 0)//从这里可以看出,<=0的结点,都是还有效的结点。
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);//唤醒
}

  

这个函数并不复杂。一句话概括:用unpark()唤醒等待队列中最前边的那个未放弃线程,这里我们也用s来表示吧。此时,再和acquireQueued()联系起来,s被唤醒后,进入if (p == head && tryAcquire(arg))的判断(即使p!=head也没关系,它会再进入shouldParkAfterFailedAcquire()寻找一个安全点。这里既然s已经是等待队列中最前边的那个未放弃线程了,那么通过shouldParkAfterFailedAcquire()的调整,s也必然会跑到head的next结点,下一次自旋p==head就成立啦),然后s把自己设置成head标杆结点,表示自己已经获取到资源了,acquire()也返回了。

好了,到这我们就因为把源码看完了,再回头来看下这张图。

  

是不是就清楚了AQS到底是怎么实现的我们上面的猜想的了吧。那么对应的下课后让你自己去看。

  
4. 死锁
在多线程中要尽量避免 死锁 的情况出现,出现死锁程序就直接卡主了。造成死锁的原因是 线程 1 锁住了资源A 等待资源 B ,线程 2 锁住了资源 B 等待资源 A ,两个线程都在等待自己需要的资源,而这些资源被另外的线程锁住,这些线程你等我,我等你,谁也不愿意让出资源,这样死锁就产生了。
   
案例代码
package com.bobo.thread1;

public class ThreadDemo08 {

    /**
     * 同步的嵌套
     * @param args
     */
    public static void main(String[] args) throws InterruptedException {
        MyRunable08 runnable = new MyRunable08();
        Thread t1 = new Thread(runnable,"A");

        Thread t2 = new Thread(runnable,"B");
        runnable.flag = true;
        t1.start();
        Thread.sleep(10);
        runnable.flag = false; // 改变状态
        t2.start();
    }
}

class MyRunable08 implements Runnable{

    private Object obj1 = new Object();
    private Object obj2 = new Object();

    public boolean flag = false;

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 执行了");
        if(flag){
            while(true){
                synchronized (obj1){ // 加一把锁
                    System.out.println(Thread.currentThread().getName() + " 执行了1 ** start");

                    synchronized (obj2){
                        System.out.println(Thread.currentThread().getName() + " 执行了2 ** start");

                        System.out.println(Thread.currentThread().getName() + " 执行了2 ** end");
                    }
                    System.out.println(Thread.currentThread().getName() + " 执行了1 ** end");

                }
            }
        }else{
            while(true){
                synchronized (obj2){ // 加一把锁
                    System.out.println(Thread.currentThread().getName() + " 执行了1 -- start");

                    synchronized (obj1){
                        System.out.println(Thread.currentThread().getName() + " 执行了2 -- start");

                        System.out.println(Thread.currentThread().getName() + " 执行了2 -- end");
                    }
                    System.out.println(Thread.currentThread().getName() + " 执行了1 --  end");

                }
            }
        }


    }
}

   

  
原理分析图

  
5. ThreadLocal
这个类提供线程局部变量。 这些变量与其正常的对应方式不同,因为访问一个的每个线程(通过其get 或 set 方法)都有自己独立初始化的变量副本。 ThreadLocal 实例通常是希望将状态与线程关联的类中的私有静态字段(例如,用户ID 或事务 ID )。
    
案例代码
package com.bobo.thread1;

public class ThreadDemo10 {

    /**
     * ThreadLocal
     *    线程变量
     * @param args
     */
    public static void main(String[] args) throws Exception{
        Runnable ru = new MyRunable10();
        Thread t1 = new Thread(ru,"A");
        Thread t2 = new Thread(ru,"B");
        t1.start();
        Thread.sleep(100);
        t2.start();

    }
}

class MyRunable10 implements Runnable{

    // 创建一个ThreadLocal变量
    private ThreadLocal<Users> myThreadLocal = new ThreadLocal<>();

    @Override
    public void run() {
        // 将User对象保存在了 当前线程的局部变量中
        myThreadLocal.set(new Users("root",((int)(Math.random()*100)) + ""));
        System.out.println(Thread.currentThread().getName() + ":" + myThreadLocal.get());
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 从当前线程的局部变量中获取保存的对象
        System.out.println(Thread.currentThread().getName() + ":" + myThreadLocal.get());
        fun1();
    }

    public void fun1(){
        System.out.println(Thread.currentThread().getName() + ":" + myThreadLocal.get());
    }
}

class Users{
    private String userName;

    private String password;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Users(String userName, String password) {
        this.userName = userName;
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "userName='" + userName + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
  
输出结果:
A:User{userName='root', password='30'}
B:User{userName='root', password='65'}
A:User{userName='root', password='30'}
A:User{userName='root', password='30'}
B:User{userName='root', password='65'}
B:User{userName='root', password='65'}
  

  
ThreadLocal 的应用场景: ORM 中的 Session 管理, Web 服务中的 Session 管理。
  
6.设计模式
6.1 什么是设计模式
设计模式( Design pattern )是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。通过对这些设计模式的合理使用能够是我们的系统更加的健壮。
  
6.2 六大设计原则

  
6.3 单例模式
保证一个类只有一个实例,并且提供一个访问实例的全局访问点。
  
6.3.1 饿汉式
也就是类加载的时候立即实例化对象。
案例代码:
package com.bobo.singleton;

public class SingetonInstance1 {

    /**
     * 单例的第一种实现方式
     *     饿汉式
     *  如果我们提供的有 共有的构造方法,那么外界可以随时的通过 new 构造器 的方式创建很多个实例对象
     *  饿汉式的实现步骤:
     *     1.私有化构造方法  不让外界直接创建对象
     * @param args
     */
    public static void main(String[] args) {
        User user1 = User.getUser();
        System.out.println(user1);
        User user2 = User.getUser();
        System.out.println(user2);
    }
}

class User{
    // 2. 声明类型的变量,并实例化,当类被加载的时候就完成了类的实例化,并保存在了内存中
    private static final User user = new User();

    /**
     * 1.私有化构造器 不让外界直接创建对象
     */
    private User(){

    }

    /**
     * 3.提供一个对外的静态方法来提供User实例
     * @return
     */
    public static User getUser(){
        return user;
    }
}
  
输出结果:
com.bobo.singleton.User@3b07d329
com.bobo.singleton.User@3b07d329
  
饿汉式单例模式代码, static 变量会在类加载的时候初始化,此时不会涉及到多个线程对象访问该对象的问题,虚拟机保证只会装载一次该类,肯定不会发生并发问题,因此可以省略掉。
  
synchroized 关键字
问题:如果只是加载了本类,而不是要调用 getUser(); 甚至永远没有调用,那么就会造成资源的浪费。
  
6.3.2 懒汉式
针对饿汉式的情况,懒汉式在声明静态变量的时候就不直接实例化了,而是在对外提供的方法中添加了逻辑判断。
package com.bobo.singleton;

public class SingetonInstance2 {

    /**
     * 懒汉式
     * @param args
     */
    public static void main(String[] args) {
        Student stu1 = Student.getStudent();
        System.out.println(stu1);
        Student stu2 = Student.getStudent();
        System.out.println(stu2);

    }
}

class Student{

     // 声明此类的实例变量,但是没有实例化
    private static  Student stu = null;

    // 私有化构造器 防止外界通过new 直接来实例化
    private Student(){

    }

    // 对外提供一个获取实例的方法
    public static Student getStudent(){
        if(stu == null){
            stu = new Student();
        }
        return stu;
    }
}
    
以上方式我们在单线程中是没有问题,但是在多线程中就会出现问题。
package com.bobo.singleton;

public class SingetonInstance3 {

    /**
     * 懒汉式
     * @param args
     */
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                Student1 stu1 = Student1.getStudent();
                System.out.println(stu1);
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                Student1 stu2 = Student1.getStudent();
                System.out.println(stu2);
            }
        }.start();


    }
}

class Student1{

     // 声明此类的实例变量,但是没有实例化
    private static  Student1 stu = null;

    // 私有化构造器 防止外界通过new 直接来实例化
    private Student1(){

    }

    // 对外提供一个获取实例的方法
    public synchronized static Student1 getStudent(){
        if(stu == null){
            stu = new Student1();
        }
        return stu;
    }
}
  
效果:
com.bobo.singleton.Student1@26f1b9c7
com.bobo.singleton.Student1@26f1b9c7
   
造成这个原因是因为多线程操作共享的数据。

  
通过同步方法来解决

   
我们通过同步方法的方式来实现会造成每次获取实例对象都要同步,会对系统造成非常大的影响,针对这种情况我们可以使用同步代码块来解决。
package com.bobo.singleton;

public class SingetonInstance4 {

    /**
     * 懒汉式
     * @param args
     */
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                Student2 stu1 = Student2.getStudent();
                System.out.println(stu1);
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                Student2 stu2 = Student2.getStudent();
                System.out.println(stu2);
            }
        }.start();


    }
}

class Student2{

     // 声明此类的实例变量,但是没有实例化
    private static  Student2 stu = null;

    // 私有化构造器 防止外界通过new 直接来实例化
    private Student2(){

    }

    // 对外提供一个获取实例的方法
    public  static Student2 getStudent(){
        if(stu == null){
            synchronized (Student2.class){
                if(stu == null){
                    stu = new Student2();
                }
                return stu;
            }
        }
        return stu;
    }
}
com.bobo.singleton.Student2@4809778b
com.bobo.singleton.Student2@4809778b
  

六、生产者和消费者模型

1.案例代码实现
案例:
生产者 (Producer) 将产品交给店员 (Clerk) ,而消费者 (Consumer) 从店员处取走产品,店员一次只能持有固定数量的产品,如果生产者生产了过多的产品,店员叫生产者等一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
   
店员
package com.bobo.thread2;

/**
 * 店员
 */
public class Clerk {

    // 属性  记录产品的数量
    private int product ;

    // 进货 从生产者处获取商品
    public synchronized void addProduct(){
        if(product >= 10){
            // 表示商品数量超过了店铺能够放下的容量
            System.out.println("货满了,暂停进货...");
            // 阻塞操作
            try {
                wait();// 阻塞
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            // 还有空间,继续进货
            product++;
            System.out.println(Thread.currentThread().getName() + "生产了一个商品:" + product);
            // 唤醒 阻塞的消费者
            notify();
        }
    }

    // 卖货 将商品售卖给消费者
    public synchronized void sellProduct(){
        if(product <=0){
            // 表示没有商品了
            System.out.println("没有商品了 .....");

            try {
                // 阻塞 当线程执行再此处的时候,那么就停住
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            // 表示有商品,那就可以售卖了
            System.out.println(Thread.currentThread().getName()+"消费了一个商品:" + product--);
            // 商品有减少  那么我们可以尝试 解除之前阻塞的线程 继续进货
            // 唤醒 阻塞的线程
            notify();

        }

    }
}
  
生产者
package com.bobo.thread2;

import javax.management.relation.RoleUnresolved;

/**
 * 生产者
 *
 */
public class Producer implements Runnable {

    // 关联的店员
    private Clerk clerk;

    public Producer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while(true){
            // 生产一个商品给店员
            clerk.addProduct();
            try {
                Thread.sleep((int)(Math.random()*1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
    
消费者
package com.bobo.thread2;

/**
 * 消费者
 */
public class Consumer implements Runnable{

    // 关联店员
    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }


    @Override
    public void run() {
        while(true){
            // 消费者购买了一个商品
            clerk.sellProduct();
            try {
                Thread.sleep((int)(Math.random()*1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
  
测试
package com.bobo.thread2;

public class ProductTest {

    /**
     * 生产者(Producer)将产品交给店员(Clerk),而消费者(Consumer)从店员处取走产品,
     * 店员一次只能持有固定数量的产品,如果生产者生产了过多的产品,店员叫生产者等一下,
     * 如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,
     * 店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
     *
     * 基于面向对象的分析:
     *    生产者 Producer
     *       功能:
     *          生产商品
     *
     *    消费者  Consumer
     *       功能:
     *          购买商品
     *
     *    店员  Clerk
     *      属性:
     *          商品的数量
     *      功能:
     *          进货
     *          卖货
     * @param args
     */
    public static void main(String[] args) throws InterruptedException {
        // 获取一个店员对象
        Clerk clerk = new Clerk();
        // 获取生产者对象
        Producer producer = new Producer(clerk);
        // 获取一个消费者对象
        Consumer consumer = new Consumer(clerk);

        // 创建两个线程
        Thread t1 = new Thread(producer,"生产者:");
        Thread t2 = new Thread(consumer,"消费者:");

        // 启动线程
        t1.start(); // 生产商品
        //Thread.sleep(10);
        t2.start(); // 消费商品

    }
}
  
2.线程的等待
线程等待【阻塞】的方式。
  
2.1 sleep方法
阻塞到休眠时间结束。
   
2.2 joinyield方法
join 合并,等待合并的线程执行完成, yield 礼让的方法。
  
2.3 wait方法
Object 类中的 wait() throws InterruptedException 方法,导致当前线程等待,直到其他线程调用notify方法或者 notifyAll 方法唤醒。 wait 方法和 notify notifyAll 方法都必须写在同步代码块或者同步方法中。

   

七、守护线程

1.守护线程的概念
在后台运行的线程,为其他的线程服务的线程。 Daemon Thread, 基于这个特点,当虚拟机中的用户线程全部退出的时候,守护线程没有服务的对象后,JVM 也就退出了。
  
怎么得到一个守护线程呢?
Runnable r1 = new MyRunable();
Thread t1 = new Thread(r1,"A");
// 将一个普通线程设置为一个守护线程
t1.setDaemon(true); // 这个设置必须在start方法执行之前设置
t1.start();
  
守护线程有什么作用:
经常可以用来作为任务结束后的善后处理工作。
守护线程的优先级是非常低的。
如果没有 用户线程 在执行, JVM 将退出,守护线程将被自动终止。我们在实际使用过程中应该避免通过守护线程来操作一些类似于文件、数据库等固有的资源。
  
2.TimerTask
守护线程的应用。在应用开发中,经常需要一些周期性的操作。比如没 5 分钟执行某些操作,或者每天晚上12 点执行某些操作。对于这些操作最方便、高效的实现方式就是使用 Java 为我们提供的计时器的工具类,即Timer TimerTask
  
任务类
class MyTimerTask extends TimerTask {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":" + new Date());
    }
}
  
测试类
package com.bobo.thread4;

import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;

public class ThreadDemo01 {

    /**
     * Timer
     * @param args
     */
    public static void main(String[] args) throws Exception {
        // 获取一个Timer对象
        Timer timer = new Timer("A");
        // 获取任务对象
        MyTimerTask myTimerTask = new MyTimerTask();
        // 任务调度 指定的任务  开始的时间  下次执行的间隔时间
        //timer.schedule(myTimerTask,1000,1000);
        timer.schedule(myTimerTask,getDate(),1000);
        Thread.sleep(5000);
        System.out.println("main 结束了");

    }

    /**
     * 获取定时任务执行的时间
     * @return
     */
    public static Date getDate(){
        Calendar c = Calendar.getInstance();
        c.set(Calendar.HOUR_OF_DAY,14);
        c.set(Calendar.MINUTE,44);
        c.set(Calendar.SECOND,0);
        return c.getTime();
    }
}

/**
 * 定时器对应的要执行的任务
 */
class MyTimerTask extends TimerTask {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":" + new Date());
    }
}

   

  

八、线程池

1.线程池的相关概念
线程池就是首先创建一些线程,使用线程池可以很好的提高性能,线程池在系统启动的时候创建了大量的空闲的线程。程序将一个任务传给线程池,线程池就会启动一个线程来执行这个任务,执行任务结束后,该线程不会消亡,而是返回线程池中状态为更新为空闲,等待执行下一个任务。

    
2.线程池的具体使用
2.1 newCachedThreadPool
可缓存的线程池,先看池中有没有以前建立的线程,如果有,就直接使用,如果没有的话,就新建一个新的线程加入线程池中,缓存型连接池通常用于一些生存很短的异步性的任务。
package com.bobo.threadpool;

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

public class ThreadPoolDemo01 {

    /**
     * 线程池的实现
     * @param args
     */
    public static void main(String[] args) {
        // 创建一个可缓存的线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        MyRunable myRunable = new MyRunable();
        // 从线程池中获取一个线程 执行 Runable任务
        executorService.execute(myRunable);
        executorService.execute(myRunable);
        executorService.execute(myRunable);
    }
}

class MyRunable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":执行了...");
    }
}
pool-1-thread-1:执行了...
pool-1-thread-3:执行了...
pool-1-thread-2:执行了...
    
2.2 newFixedThreadPool
创建一个可重用的固定个数的线程池,以共享的无界队列方式来运行这些线程。
package com.bobo.threadpool;

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

public class ThreadPoolDemo02 {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0 ; i < 10; i++){
            executorService.execute(new MyRunable02());
        }
    }
}

class MyRunable02 implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 执行了...");
    }
}

pool-1-thread-1 执行了...
pool-1-thread-1 执行了...
pool-1-thread-1 执行了...
pool-1-thread-1 执行了...
pool-1-thread-1 执行了...
pool-1-thread-1 执行了...
pool-1-thread-2 执行了...
pool-1-thread-3 执行了...
pool-1-thread-5 执行了...
pool-1-thread-4 执行了...
    
2.3 newScheduledThreadPool
创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行。
package com.bobo.threadpool;

import java.util.concurrent.*;

public class ThreadPoolDemo03 {

    public static void main(String[] args) {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
        for (int i = 0 ; i < 10 ; i ++){
            executorService.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ":延长一秒执行");
                }
            }, 1, TimeUnit.SECONDS);
        }
    }
}
pool-1-thread-4:延长一秒执行
pool-1-thread-5:延长一秒执行
pool-1-thread-1:延长一秒执行
pool-1-thread-2:延长一秒执行
pool-1-thread-3:延长一秒执行
pool-1-thread-4:延长一秒执行
pool-1-thread-5:延长一秒执行
pool-1-thread-2:延长一秒执行
pool-1-thread-1:延长一秒执行
pool-1-thread-3:延长一秒执行
   
2.4 newSingleThreadExecutor
创建一个单线程话的线程池,它只会用唯一的工作线程来执行任务,保证所有的任务按照指定的顺序执行。
package com.bobo.threadpool;

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

public class ThreadPoolDemo04 {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for(int i =0 ;i < 5 ; i++){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+":执行了...");
                }
            });
        }
    }
}
pool-1-thread-1:执行了...
pool-1-thread-1:执行了...
pool-1-thread-1:执行了...
pool-1-thread-1:执行了...
pool-1-thread-1:执行了...
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方寸之间不太闲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值