Java多线程详细总结!

文章目录

  • 1、进程与线程
  • 2、创建多线程
    • 2.1、继承Thread类
    • 2.2、实现Runnable接口
    • 2.3、使用匿名内部类实现
    • 2.4、使用Lambda表达式实现
    • 2.5、实现Runnable接口的好处
    • 2.6、使用Callable和Future创建线程
  • 3、线程的生命周期
  • 4、几种特殊线程
    • 4.1、join线程
    • 4.2、守护线程
    • 4.3、休眠线程
  • 5、线程安全与线程同步
    • 5.1、安全问题
    • 5.2、解决方案一 ---- 同步代码块
    • 5.3、解决方案二 ---- 同步方法
  • 6、线程通信
    • 6.1、API
    • 6.2、案例
  • 7、线程池
    • 7.1、线程池介绍
    • 7.2、线程池的使用

1、进程与线程

   进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。线程(Thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
   程序启动运行main方法的时候,Java虚拟机会启动一个进程,并创建主线程main。随着调用Thread对象的 start(),另外一个新的线程也启动了,整个应用就在多线程下运行。 所以,一个进程可以包括多个线程。而多进程是对于操作系统和CPU而言的,比如电脑同时启动了QQ、微信、音乐软件等应用程序时,每个程序都是一个进程,此时CPU就是多进程的。

2、创建多线程

2.1、继承Thread类

/**
 * @author RuiMing Lin
 * @date 2020-03-12 14:19
 */
public class MyThread extends Thread {
     
    
    @Override
    public void run() {
     
        System.out.println("mythread1");
    }
    
    public static void main(String[] args) {
     
        MyThread mt = new MyThread();
        mt.start();
    }
}

2.2、实现Runnable接口

/**
 * @author RuiMing Lin
 * @date 2020-03-12 14:22
 */
public class MyRunnable implements Runnable{
     

    @Override
    public void run() {
     
        System.out.println("MyRunnable 1");
    }

    public static void main(String[] args) {
     
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }
}

2.3、使用匿名内部类实现

/**
 * @author RuiMing Lin
 * @date 2020-03-12 14:25
 */
public class Demo1 {
     
    public static void main(String[] args) {
     
        Thread thread1 = new Thread(new Runnable() {
     
            @Override
            public void run() {
     
                System.out.println("使用匿名内部类实现");
            }
        });
        thread1.start();
    }
}

2.4、使用Lambda表达式实现

/**
 * @author RuiMing Lin
 * @date 2020-12-26 0:35
 * @description
 */
public class MyRunnableLambda {
     
    public static void main(String[] args) {
     
        Thread thread = new Thread(() -> System.out.println("thread..."));
        thread.start();
    }
}

2.5、实现Runnable接口的好处

  实现Runnable接口比继承Thread类所具有的优势:

  1. 可以避免java中的单继承的局限性;
  2. 增加程序的健壮性,实现解耦操作;
  3. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

2.6、使用Callable和Future创建线程

  Callable是一个接口,有点类似于Runnable接口,不过较之更加强大。Callable提供了一个类似于run方法的call(),但call()更加强大。主要体现在两点:call()可以有返回值,call()可以抛出异常。
  理论上我们可以类似于上述匿名内部类的方法将Callable接口传递给Thread的构造方法,但实际上不行,因为Thread类提供的构造方法没有传递Callable参数类型的,而Callable又不是Runnable的子接口。所以就有了Future接口,该接口是Runnable的子接口,并有一个实现类FutureTask封装了Callable接口。
Java多线程详细总结!_第1张图片

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

/**
 * @author RuiMing Lin
 * @date 2020-03-12 14:25
 */
public class Demo1 {
     
    public static void main(String[] args) throws Exception{
     
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
     
            @Override
            public Integer call() throws Exception {
     
                int i = 0;
                for ( ;i < 10; i++){
     
                    System.out.println(Thread.currentThread().getName() + "i = " + i);
                }
                return i;
            }
        });
        new Thread(futureTask).start();
        Integer integer = futureTask.get();//返回call()的返回值,必须等到子栈结束后才会得到返回值,会导致程序阻塞
        System.out.println("integer = " + integer);
    }
}

3、线程的生命周期

新建态: 当一个线程被new出来,也就是被创建出来的时候;
就绪态: 被创建的线程调用start()方法
     阻塞态线程休眠时间到或者获得同步锁等等;
运行态: 就绪态线程获得CPU资源;
阻塞态: 运行态线程调用sleep()方法进行休眠或者等待同步锁中;
死亡态: 线程被关闭或者出现异常等等。
Java多线程详细总结!_第2张图片

4、几种特殊线程

4.1、join线程

Thread的join()可以让一个线程等待调用该方法的线程完全完成的方法:

/**
 * @author RuiMing Lin
 * @date 2020-03-13 20:39
 */
public class MyRunnable1 implements Runnable{
     
    @Override
    public void run() {
     
        for (int i = 0; i < 20; i++) {
     
            System.out.println(Thread.currentThread().getName() + "执行了" + i + "次");
        }
    }
    public static void main(String[] args) {
     
        for (int i = 0; i < 100; i++) {
     
            if (i == 20){
     
                Thread thread = new Thread(new MyRunnable1());
                thread.setName("我的线程");
                thread.start();
                try {
     
                    thread.join();
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
            }
            System.out.println("主线程main方法执行了" + i + "次");
        }
    }
}

结果输出为:
Java多线程详细总结!_第3张图片
如果不使用join()方法:

/**
 * @author RuiMing Lin
 * @date 2020-03-13 20:39
 */
public class MyRunnable1 implements Runnable{
     
    @Override
    public void run() {
     
        for (int i = 0; i < 20; i++) {
     
            System.out.println(Thread.currentThread().getName() + "执行了" + i + "次");
        }
    }
    public static void main(String[] args) {
     
        for (int i = 0; i < 100; i++) {
     
            if (i == 20){
     
                Thread thread = new Thread(new MyRunnable1());
                thread.setName("我的线程");
                thread.start();
//                try {
     
//                    thread.join();
//                } catch (InterruptedException e) {
     
//                    e.printStackTrace();
//                }
            }
            System.out.println("主线程main方法执行了" + i + "次");
        }
    }
}

结果输出为:
Java多线程详细总结!_第4张图片
  原因分析:当主线程的i = 20时,创建自定义线程,开启自定义线程并调用join方法,此时主线程main()被挂起,直到自定义线程完全执行结束后才放行让主线程执行。

4.2、守护线程

当JVM中没有非守护的线程在运行的时候,JVM就会关闭。也就是说无论守护线程有没有执行结束,只要非守护线程执行结束了,守护线程都会结束。

/**
 * @author RuiMing Lin
 * @date 2020-12-22 20:47
 * @description
 */
public class Daemon {
     
    public static void main(String[] args) {
     
        Thread threadOut = new Thread(() -> {
     
            //创建线程,校验守护线程内创建线程是否为守护线程
            Thread threadInner = new Thread(() -> {
     
                System.out.println("threadInner: " + (Thread.currentThread().isDaemon() ? "守护线程" : "非守护线程"));
            });
            threadInner.start();
            int i = 1;
            while(true) {
     
                try {
     
                    Thread.sleep(200);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
                System.out.println("threadOut: " + (Thread.currentThread().isDaemon() ? "守护线程" : "非守护线程") + " , 执行次数 : " + i);
                if (i++ >= 10) {
     
                    break;
                }
            }
        });
        //setDaemon(true) 必须在 start() 之前设置,否则会抛出IllegalThreadStateException异常,该线程仍默认为用户线程,继续执行
        threadOut.setDaemon(true);
        threadOut.start();
        try {
     
            Thread.sleep(1000);     // 别让主线程那么快结束
        } catch (InterruptedException e) {
     
            e.printStackTrace();
        }finally {
     
            System.out.println("主线程结束");
        }
    }
}

结果输出为:
Java多线程详细总结!_第5张图片

4.3、休眠线程

运行态的线程调用sleep()进入休眠线程,调用方法:

Thread.sleep(1000);

5、线程安全与线程同步

5.1、安全问题

先抛出一个线程之间的安全问题:如今有十张火车票,两个窗口同时售卖

/**
 * @author RuiMing Lin
 * @date 2020-03-13 15:57
 */
public class Ticket1 implements Runnable{
     
    
    private int ticket = 10;     // 表示有十张火车票

    @Override
    public void run() {
     
        for (int i = 0; i < 10; i++) {
     
            if (ticket > 0){
     
                System.out.println(Thread.currentThread().getName()
                        + "卖了编号为" + ticket + "的火车票");
                ticket --;
                try {
     
                    Thread.sleep(500);
                }catch (InterruptedException e){
     
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
     
        Ticket1 ticket = new Ticket1();
        Thread thread1 = new Thread(ticket);
        Thread thread2 = new Thread(ticket);
        thread1.setName("窗口A");
        thread2.setName("窗口B");
        thread1.start();
        thread2.start();
    }
}

结果输出为:

窗口A卖了编号为10的火车票
窗口B卖了编号为9的火车票
窗口A卖了编号为8的火车票
窗口B卖了编号为8的火车票
窗口A卖了编号为6的火车票
窗口B卖了编号为6的火车票
窗口B卖了编号为4的火车票
窗口A卖了编号为4的火车票
窗口B卖了编号为2的火车票
窗口A卖了编号为2的火车票

  很明显,这样的结果是不满足需求的。原因分析:因为多线程是CPU随机执行的,当线程一执行System.out.println(Thread.currentThread().getName()+ “卖了编号为” + ticket + “的火车票”)语句时,还未执行ticket --跳转到线程二,此时就会出现数据没有及时更新、同时售卖同一张票的情况。

5.2、解决方案一 ---- 同步代码块

  对需要连续执行的代码加一把锁,称为同步代码块。代码代码块中的代码必须执行完才会切换到其他线程,这样就能保证了线程之间的安全。

/**
 * @author RuiMing Lin
 * @date 2020-03-13 15:57
 */
public class Ticket2 implements Runnable{
     

    private int ticket = 10;     // 表示有十张火车票

    @Override
    public void run() {
     
        for (int i = 0; i < 10; i++) {
     
            synchronized (this){
     
                if (ticket > 0){
     
                    System.out.println(Thread.currentThread().getName()
                            + "卖了编号为" + ticket + "的火车票");
                    ticket --;
                    try {
     
                        Thread.sleep(400);
                    }catch (InterruptedException e){
     
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
     
        Ticket2 ticket = new Ticket2();
        Thread thread1 = new Thread(ticket);
        Thread thread2 = new Thread(ticket);
        thread1.setName("窗口A");
        thread2.setName("窗口B");
        thread1.start();
        thread2.start();
    }
}

结果输出为:

窗口A卖了编号为10的火车票
窗口B卖了编号为9的火车票
窗口B卖了编号为8的火车票
窗口B卖了编号为7的火车票
窗口B卖了编号为6的火车票
窗口B卖了编号为5的火车票
窗口B卖了编号为4的火车票
窗口B卖了编号为3的火车票
窗口B卖了编号为2的火车票
窗口A卖了编号为1的火车票

可以看出,这样就满足需求了。

5.3、解决方案二 ---- 同步方法

/**
 * @author RuiMing Lin
 * @date 2020-03-13 15:57
 */
public class Ticket2 implements Runnable{
     

    private int ticket = 10;     // 表示有十张火车票
    private synchronized void sale(){
     
        if (ticket > 0){
     
            System.out.println(Thread.currentThread().getName()
                    + "卖了编号为" + ticket + "的火车票");
            ticket --;
            try {
     
                Thread.sleep(400);
            }catch (InterruptedException e){
     
                e.printStackTrace();
            }
        }
    }

    @Override
    public void run() {
     
        for (int i = 0; i < 10; i++) {
     
            sale();
        }
    }

    public static void main(String[] args) {
     
        Ticket2 ticket = new Ticket2();
        Thread thread1 = new Thread(ticket);
        Thread thread2 = new Thread(ticket);
        thread1.setName("窗口A");
        thread2.setName("窗口B");
        thread1.start();
        thread2.start();
    }
}

结果输出:

窗口A卖了编号为10的火车票
窗口A卖了编号为9的火车票
窗口A卖了编号为8的火车票
窗口A卖了编号为7的火车票
窗口A卖了编号为6的火车票
窗口A卖了编号为5的火车票
窗口B卖了编号为4的火车票
窗口A卖了编号为3的火车票
窗口B卖了编号为2的火车票
窗口A卖了编号为1的火车票

6、线程通信

6.1、API

wait():导致当前线程等待;
notify():唤醒共享当前同一把锁的线程其中之一;
notifyAll():唤醒共享当前同一把锁的所有线程。

6.2、案例

需求:实现1与2交替出现

/**
 * @author RuiMing Lin
 * @date 2020-03-13 21:03
 */
public class Communication {
     
    public static Object lock = new Object();   // 定义一把锁

    public static void main(String[] args) {
     
        MyThread1 myThread1 = new MyThread1();
        MyThread2 myThread2 = new MyThread2();
        myThread1.start();
        myThread2.start();

    }
}

// 定义通讯类一
class MyThread1 extends Thread{
     
    @Override
    public void run() {
     
        for (int i = 0; i < 10; i++) {
     
            synchronized (Communication.lock){
           //互相通信的线程必须共享同一把锁
                System.out.print(1);
                Communication.lock.notify();
                try {
     
                    Communication.lock.wait();
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
            }
        }
    }
}
class MyThread2 extends Thread{
     
    @Override
    public void run() {
     
        for (int j = 0; j < 10; j++) {
     
            synchronized (Communication.lock){
     
                System.out.print(2);
                Communication.lock.notify();
                try {
     
                    Communication.lock.wait();
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
            }
        }
    }
}

7、线程池

7.1、线程池介绍

  线程池是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源。这样做有三个好处:1.降低资源消耗,减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务;2. 提高响应速度,当任务到达时,任务可以不需要的等到线程创建就能立即执行;3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内 存,而把服务器累趴下。

7.2、线程池的使用

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

/**
 * @author RuiMing Lin
 * @date 2020-03-13 21:22
 */
public class Pool {
     
    public static void main(String[] args) {
     
        // 1.创建一个容量为5的线程池
        ExecutorService service = Executors.newFixedThreadPool(5);
        // 2.创建Runnable对象
        MyRunnable runnable1 = new MyRunnable();
        MyRunnable runnable2 = new MyRunnable();
        MyRunnable runnable3 = new MyRunnable();
        MyRunnable runnable4 = new MyRunnable();
        MyRunnable runnable5 = new MyRunnable();
        // 3.调用线程池方法
        service.submit(runnable1);
        service.submit(runnable2);
        service.submit(runnable3);
        service.submit(runnable4);
        service.submit(runnable5);
        // 4.无需关闭线程,run()结束后自动将线程归还给线程池
        // 5.关闭线程池
        service.shutdown();
    }
}

你可能感兴趣的:(Java,java,多线程,thread,并发编程)