【多线程】多线程基础

目录

一、程序、进程、线程、协程

二、线程的实现方式

2.1 三种方式对比

2.2 继承 Thread 类

2.3 Runnable

2.4 Callable

三、线程的常用方法和状态

3.1 常用方法

3.2 状态

四、守护线程

4.1 概念

4.2 作用

4.3 使用 demo

五、线程锁

5.1 线程锁

5.2 synchronized 声明方法

5.3 synchronized 代码块

5.4 死锁

5.5 Lock

六、线程通信

6.1 常用的线程通信方法

6.2 管程法

6.3 信号灯法

七、线程池

7.1 概念

7.2 为什么要使用线程池

7.3 默认提供API(不推荐使用)

7.3.1 API描述

7.3.2 创建的线程池类型

7.3.3 使用


一、程序、进程、线程、协程

程序

 一段静态的代码,一段指令的有序集合,他是一个静态的实体,是应用程序执行的蓝本

进程

进程就是程序的一次动态执行,是一个动态的实体。它拥有自己的生命周期

● 线程

线程是进程的组成部分。一个进程可以有多个线程,而这多个线程必须依赖于同一个父进程。线程可以拥有自己的堆栈、程序计数器和局部变量,但不能拥有自己独立的系统资源。一个进程下的所有线程,都共享该进程的所有资源。

 

二、线程的实现方式

2.1 三种方式对比

● 继承 Thread 类

描述:继承 Thread 类,重写 run 方法,在实例化对象后调用 start 启动线程

优点:直接继承就可以使用,代码简单清晰

缺点:由于 JAVA 是单继承的,继承了 Thread 类就无法继承其他类

 

● Runnable

描述:通过使用 Runnable 接口,、重写 run 方法,在实例化对象后,将对象装载到其他 Thread 对象中使用

优点:解决了 Thread 不能继承其他类的问题

           一个实例可以被多个线程加载

 

● Callable

描述:通过使用 callable 接口,重写 call 方法,在实例化对象后,将对象装载到 FutureTask 中,并将 FutureTask 传入线程实例中,通过 FutureTask 对象的 get 方法获取到调用结果,并处理

优点:可以实现线程的回调以及抛出异常

 

2.2 继承 Thread 类

/**
 * @author tom
 * @date 2019-11-01 09:37
 * @Description 通过继承 Thread 类来创建线程
 */
public class MainThread extends Thread{
 
    public static void main(String[] args) {
 
        // 新建了一个 MainThread 的类实例,这个类继承了 Thread
        MainThread thread1 = new MainThread();
        // 设置这个实例的名称(可重复)
        thread1.setName("tom thread");
        // 启动线程,此时线程开始跑 run 方法
        thread1.start();
 
        System.out.println("main over");
 
        // 运行 main 函数的线程叫主线程,循环打印主线程的内容
        // 由于主线程和其他线程存在竞争关系,故可以在打印台看到两种线程存在交互情况
        for (int i=0; i<50; i++) {
            System.out.println(Thread.currentThread().getName() + " i = " + i);
        }
    }
 
 
    /**
     * @author tom
     * @date 2019/11/1 9:39
     * @params
     * @return
     * @Description 继承了 Thread 类,必须重写 run 方法,来让这一类的线程做自己想要让他做的事情
    */
    @Override
    public void run() {
        // 循环打印出这个线程的名字
        for (int i=0; i<50; i++) {
            System.out.println(Thread.currentThread().getName() + " i = " + i);
        }
    }
}

 

2.3 Runnable

 
/**
 * @author tom
 * @date 2019-11-01 09:59
 * @Description 使用 Runnable 接口创建子线程
 */
public class MainRunnable implements Runnable {
 
    public static void main(String[] args) {
 
        // 创建 Runnable 实例
        MainRunnable mRu = new MainRunnable();
 
        // 创建 Thread 并传入 Runnable 实例
        Thread th1 = new Thread(mRu);
        // 设置子线程名称
        th1.setName("children thread");
        // 开启子线程
        th1.start();
 
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + " i = " + i);
        }
 
    }
 
    /**
     * @author tom
     * @date 2019/11/1 10:00
     * @params
     * @return
     * @Description this is a function
    */
    @Override
    public void run() {
        // 打印子线程信息
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + " i = " + i);
        }
    }
}

 

2.4 Callable

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
 
/**
 * @author tom
 * @date 2019-11-01 10:08
 * @Description
 */
public class MainCallable implements Callable<Integer> {
 
    public static void main(String[] args) {
 
        // 构建 Callable 实例类
        MainCallable mCall = new MainCallable();
        // 通过 FutureTask 类来实现回调
        // 需要指定回调类型,此时回调类型是 Integer,并将call实例类添加到 FutureTask 实例类中
        FutureTask<Integer> mFutureTask = new FutureTask<>(mCall);
 
        // 构建线程,并在构建时将 FutureTask 实例传入
        Thread mThread = new Thread(mFutureTask);
        // 设置子线程名
        mThread.setName("children thread");
        // 开启线程
        mThread.start();
 
        // 重要的一步,通过 FutureTask 实例的 get() 方法获取到线程返回的结果
        try {
            int count  = mFutureTask.get();
            System.out.println("callable count = " + count);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
 
    }
 
    /**
     * @author tom
     * @date 2019/11/1 10:08
     * @params
     * @return
     * @Description this is a function
    */
    @Override
    public Integer call() throws Exception {
        int count = 0;
        for (int i=0; i<50; i++) {
            count++;
            System.out.println(Thread.currentThread().getName() + " i = " + i);
        }
        return count;
    }
}

 

三、线程的常用方法和状态

3.1 常用方法

方法描述
setPriority、getPriority设置/获取 优先级,0为最低级,10为最高级
sleep在指定毫秒内休眠,但不会让位置
yield礼让,挂起当前的线程,与其他线程再次竞争
join插队,挂起当前线程,先运行插队线程,插队线程运行完后,再运行原理挂起的线程
isAlive查看线程是否处于活跃状态
getStatus获取线程状态
interrupt中断线程

https://www.jianshu.com/p/e0ff2e420ab6

 

3.2 状态

状态描述
new尚未启动的线程的状态
runnable在执行中的线程的状态
blocked被阻塞等待的线程的状态
waiting等待另一个线程执行特定动作的线程的状态
timed_waiting正在等待另一个线程执行特定动作的线程的状态,如 sleep 等
terminated已退出的线程的状态

 

四、守护线程

4.1 概念

线程分为用户线程和守护线程,一般直接创建出来的线程是用户线程

虚拟机必须确保用户线程执行完毕,不用等待守护线程执行完毕

 

4.2 作用

守护线程常用来后台记录操作日志、监控内存、垃圾回收等

 

4.3 使用 demo

public class TestDaemon {

    public static void main(String[] args) {
        DaemonThread daemonThread = new DaemonThread();
        NormalThread normalThread = new NormalThread();

        // 启动守护线程
        Thread thread = new Thread(daemonThread);
        thread.setDaemon(true);
        thread.start();

        // 启动正常线程
        Thread thread1 = new Thread(normalThread);
        thread1.start();
    }
}

class DaemonThread implements Runnable {

    @Override
    public void run() {
        while (true){
            System.out.println("this is DaemonThread Run");
        }
    }
}

class NormalThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 36000; i++) {
            System.out.println("正常线程");
        }
    }
}

 

五、线程锁

5.1 线程锁

线程锁又称为同步方法,通过 synchronized 关键字声明,可以用于声明方法或代码块

 

5.2 synchronized 声明方法

5.2.1 示例

public synchronized void methodName(int args){}

5.2.2 缺点

若将一个大的方法申明为 synchronized,则会影响效率。且默认锁的是当前这个对象this,可能不适合一些需求

5.2.3 demo代码

package com.tom.demo01.sync;

/**
 * @File: UnsafeBuyTIicket
 * @Description:
 * @Author: tom
 * @Create: 2020-07-30 17:04
 **/
public class UnsafeBuyTIicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

        new Thread(buyTicket, "A").start();
        new Thread(buyTicket, "B").start();
        new Thread(buyTicket, "C").start();
    }
}

class BuyTicket implements Runnable {
    private int ticketNums = 10;
    boolean flag = true;

    @Override
    public void run() {
        while (flag) {
            buy();
        }
    }

    private synchronized void buy() {
        if(ticketNums <= 0) {
            flag = false;
            return;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--);
    }
}

 

5.3 synchronized 代码块

示例代码

public class UnsafeBuyTIicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

        new Thread(buyTicket, "A").start();
        new Thread(buyTicket, "B").start();
        new Thread(buyTicket, "C").start();
    }
}

class BuyTicket implements Runnable {
    private int ticketNums = 10;
    boolean flag = true;

    @Override
    public void run() {
        while (flag) {
            buy();
        }
    }

    private void buy() {
        synchronized (this) {
            if(ticketNums <= 0) {
                flag = false;
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--);
        }
    }
}

 

5.4 死锁

死锁即两个或两个以上的线程都在等待对方释放资源,结果都被锁住停止执行的情况。当一个同步块用时用于两个及两个以上的对象的锁时,就有可能发生

5.5 Lock

https://blog.csdn.net/e54332/article/details/86577071

 

六、线程通信

6.1 常用的线程通信方法

这些方法只能在同步方法或同步代码块中使用

方法名作用
wait()让线程一直等待,直到其他线程通知。此时会释放锁
wait(long timeout)指定等待的毫秒数
notify()唤醒一个处于等待的线程
notifyAll()唤醒同一个对象上,所有调用了 wait() 方法的线程,优先级别高的线程优先调度

 

6.2 管程法

使用缓存区,类似消费者/生产者模型,一个填入缓冲区,一个从缓冲区取出

public class TestPC {

    public static void main(String[] args) {

        SynContainer container = new SynContainer();
        new Productor(container).start();
        new Consumer(container).start();
    }
}

class Productor extends Thread{
    SynContainer synContainer;

    public Productor(SynContainer container) {
        this.synContainer = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("生产了 " + i + "只鸡");
            synContainer.push(new Chicken(i));
        }
    }
}

class Consumer extends Thread{
    SynContainer synContainer;

    public Consumer(SynContainer container) {
        this.synContainer = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了 " + synContainer.pop().id + "只鸡");
        }
    }
}

class Chicken{
    int id;

    public Chicken(int id) {
        this.id = id;
    }
}

class SynContainer{
    Chicken[] chickens = new Chicken[10];

    int count = 0;
    public synchronized void push(Chicken chicken) {
        if (count == chickens.length) {
            try {
                System.out.println("push wait");
                this.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        chickens[count]=chicken;
        count++;

        this.notifyAll();
    }

    public synchronized Chicken pop() {
        if (count == 0) {
            try {
                System.out.println("pop wait");
                this.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        count--;
        Chicken chicken = chickens[count];

        this.notifyAll();

        return chicken;
    }
}

 

6.3 信号灯法

通过设置 flag 值,判断哪个需要的等待

public class TestPC2 {
    public static void main(String[] args) {
        TV tv = new TV();

        new Player(tv).start();
        new Watcher(tv).start();
    }
}

class Player extends Thread{
    TV tv;
    public Player(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i % 2 == 0) {
                this.tv.play("快乐大本营播放中");
            } else {
                this.tv.play("抖音记录美好生活");
            }
        }
    }
}

class Watcher extends Thread {
    TV tv;
    public Watcher(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }
}

class TV{
    String voice;
    boolean flag = true;

    public synchronized void play(String voice) {
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了: " +  voice);
        this.notifyAll();
        this.voice = voice;
        this.flag = !this.flag;
    }

    public synchronized  void watch() {
        if (flag) {
            try {
                this.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println("观众观看了:" + voice);

        this.notifyAll();
        this.flag = !this.flag;
    }
}

 

七、线程池

7.1 概念

线程池就是提前创建好多个线程放入池中,使用时直接获取,使用完放回池中。这样可以避免频繁创建和销毁,实现重复利用。

 

7.2 为什么要使用线程池

由于创建线程的代价和维护十分昂贵

  • JVM 中默认一个线程需要使用256k~1M的内存
  • 加重 GC 的回收压力

使用线程池的好处:

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗
  • 便于线程管理

 

7.3 默认提供API(不推荐使用)

7.3.1 API描述

java 提供了相关的API: ExecutorService 和 Executors

● ExecutorService

常见子类: ThreadPoolExecutor

方法:

void execute(Runnable command):执行任务,无返回值

Future submit(Callable task):执行任务,有返回值

void shutdown():关闭线程池

● Executors

线程池的工厂类,用于创建并返回不同类型的线程池

 

7.3.2 创建的线程池类型

● newCachedThreadPool (ThreadPoolExecutor)

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

newFixedThreadPool (ThreadPoolExecutor)

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

newSingleThreadExecutor (ThreadPoolExecutor)

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

newScheduledThreadPool (ScheduledThreadPoolExecutor)

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

newSingleThreadScheduledExecutor (ScheduledThreadPoolExecutor)

创建一个单线程用于定时以及周期性执行任务的需求。

newWorkStealingPool (1.8 ForkJoinPool)

创建一个工作窃取

对应的实现类

newCachedThreadPoolThreadPoolExecutor
newFixedThreadPoolThreadPoolExecutor
newSingleThreadExecutorThreadPoolExecutor
newScheduledThreadPoolScheduledThreadPoolExecutor
newSingleThreadScheduledExecutorScheduledThreadPoolExecutor
newWorkStealingPoolForkJoinPool

 

7.3.3 使用

public class TestPool {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        service.shutdown();
    }
}

class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值