Java多线程

简介

一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。

知识点
  • 多线程的实现
  • 线程变量
  • 线程同步
  • Lock 与 Unlock
  • 死锁
  • 线程生命周期
  • ArrayBlockingQueue
  • 生产者消费者模式
  • 线程池
什么是线程

线程:程序执行流的最小单元。它是进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派 CPU 的基本单位。

线程的生命周期

在这里插入图片描述

多线程实现

1、继承 Thread 类,重写 run() 方法;

2、实现 Runnable 接口,重写 run() 方法;

3、实现 Callable 接口,重写 call() 方法。

调用start() 方法启动线程。

线程变量

ThreadLocal,即线程变量,是一个以 ThreadLocal 对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个 ThreadLocal 对象查询到绑定在这个线程上的一个值。 可以通过 set(T) 方法来设置一个值,在当前线程下再通过 get() 方法获取到原先设置的值。

public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        //启动2个线程
        new Thread(threadDemo).start();
        new Thread(threadDemo).start();
}

class ThreadDemo implements Runnable {
    //使用ThreadLocal提供的静态方法创建一个线程变量 并且初始化值为0
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //get方法获取线程变量值
            Integer integer = threadLocal.get();
            integer += 1;
            //set方法设置线程变量值
            threadLocal.set(integer);
            System.out.println(integer);
        }
    }

其结果:

1
1
2
2
3
3
4
4
5
5
6
6
7
7
8
8
9
10
9
10

如果去掉了 ThreadLocal,其他的流程都不改变,使用 2 个线程自增变量会如何呢?

class ThreadDemo implements Runnable {
    private Integer integer = 0;

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            integer++;
            System.out.println(integer);
        }
    }
}

其结果:

1
3
4
5
6
7
8
9
10
11
2
12
13
14
15
16
17
18
19
20
线程同步

当多个线程操作同一个对象时,就会出现线程安全问题,被多个线程同时操作的对象数据可能会发生错误。线程同步可以保证在同一个时刻该对象只被一个线程访问。

Synchronized

它确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,保证了线程对变量访问的可见性和排他性。它有三种使用方法:

  • 对普通方式使用,将会锁住当前实例对象。
  • 对静态方法使用,将会锁住当前类的 Class 对象。
  • 对代码块使用,将会锁住代码块中的对象。
public class SynchronizedDemo {
    private static Object lock = new Object();

    public static void main(String[] args) {
        //同步代码块 锁住lock
        synchronized (lock) {
            //doSomething
        }
    }

    //静态同步方法  锁住当前类class对象
    public synchronized static void staticMethod(){

    }
    //普通同步方法  锁住当前实例对象
    public synchronized void memberMethod() {

    }
}
java.util.concurrent

java.util.concurrent 包是 java5 开始引入的并发类库,提供了多种在并发编程中的适用工具类。包括原子操作类,线程池,阻塞队列,Fork/Join 框架,并发集合,线程同步锁等。

Lock 与 Unlock

JUC 中的 ReentrantLock 是多线程编程中常用的加锁方式,ReentrantLock 加锁比 synchronized 加锁更加的灵活,提供了更加丰富的功能。

死锁和饥饿
死锁

产生死锁的必要条件

1、互斥条件:所谓互斥就是进程在某一时间内独占资源。

2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

3、不剥夺条件: 进程已获得资源,在末使用完之前,不能强行剥夺。

4、循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系。

死锁处理

1、预防死锁

在这里插入图片描述

2、死锁避免

在这里插入图片描述

3、死锁的检测和解除

在这里插入图片描述

饥饿

一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。

Java 中导致饥饿的原因

1、高优先级线程吞噬所有的低优先级线程的 CPU 时间。

2、线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。

3、线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的 wait 方法),因为其他线程总是被持续地获得唤醒。

ArrayBlockingQueue

ArrayBlockingQueue 是由数组支持的有界阻塞队列。位于 java.util.concurrent 包下。

首先看看其构造方法:

构造方法描述
public ArrayBlockingQueue(int capacity)构造大小为 capacity 的队列
public ArrayBlockingQueue(int capacity, boolean fair)指定队列大小,以及内部实现是公平锁还是非公平锁
public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c)指定队列大小,以及锁实现,并且在初始化是加入集合 c

入队常用方法:

入队方法队列已满队列未满
add抛出异常返回 true
offer返回 false返回 true
put阻塞直到插入没有返回值

出队常用方法:

出队方法队列为空队列不为空
remove抛出异常移出并返回队首
poll返回 null移出并返回队首
take阻塞直到返回移出并返回队首
生产者消费者模式

生产者消费者模式是多线程编程中非常重要的设计模式,生产者负责生产数据,消费者负责消费数据。生产者消费者模式中间通常还有一个缓冲区,用于存放生产者生产的数据,而消费者则从缓冲区中获取,这样可以降低生产者和消费者之间的耦合度。

public class PCModel {
    //阻塞队列
    private static LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>();

    public static void main(String[] args) {
        //生产者
        Thread provider = new Thread(() -> {
            Random random = new Random();
            for (int j = 0; j < 5; j++) {
                try {
                    int i = random.nextInt();
                    //注释直到插入数据
                    queue.put(i);
                    System.out.println("生产数据:" + i);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //消费者
        Thread consumer = new Thread(() -> {
            Integer data;
            for (int i = 0; i < 5; i++) {
                try {
                    //阻塞直到取出数据
                    data = queue.take();
                    System.out.println("消费数据:" + data);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //启动线程
        provider.start();
        consumer.start();
    }
}
线程池

线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。

由于 Java 创建和销毁线程都会带来资源上的销毁,所以线程池可以帮助我们复用线程,减少资源消耗。

Java 线程池可以通过 Executors 工具类创建,Executors 常用方法:

  • newFixedThreadPool(int nThreads): 创建一个固定大小为 n 的线程池
  • newSingleThreadExecutor(): 创建只有一个线程的线程池
  • newCachedThreadPool(): 创建一个根据需要创建新线程的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo {
    //使用Executors 创建一个固定大小为5的线程池
    private static ExecutorService executorService = Executors.newFixedThreadPool(5);

    public static void main(String[] args) {
//        提交任务
        executorService.submit(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.print(i + " ");
            }
        });
        //停止线程池 并不会立即关闭 ,而是在线程池中的任务执行完毕后才关闭
        executorService.shutdown();
    }
}

除了使用 Executors 工具类帮助我们创建之外,也可以直接创建线程池。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolDemo2 {
    private static ExecutorService executorService = new ThreadPoolExecutor(
            5, //核心线程数为5
            10,//最大线程数为10
            0L, TimeUnit.MILLISECONDS,//非核心线程存活时间
            new LinkedBlockingQueue<>());//任务队列

    public static void main(String[] args) {
        //提交任务
        executorService.submit(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.print(i + " ");
            }
        });
        //关闭线程池
        executorService.shutdown();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值