多线程总结

什么是线程,进程,多线程

202109211640577

进程(Process):一个游戏,一个视频软件,一个QQ。进程是程序执行的过程

线程(Thread):游戏的画面,游戏的声音。线程是CPU调度和执行的单位

一个进程至少包含一个线程

线程

202109211652048

线程和进程的区别

  • 地址空间:线程共享本进程的地址空间,而进程之间是独立的地址空间。
  • 资源:线程共享本进程的资源如内存、I/O、cpu等,不利于资源的管理和保护,而进程之间的资源是独立的,能很好的进行资源管理和保护。
  • 健壮性:多进程要比多线程健壮,一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。
  • 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口,执行开销大。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,执行开销小。
  • 可并发性:两者均可并发执行。
  • 切换时:进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。
  • 其他:线程是处理器调度的基本单位,但是进程不是。

创建线程

Thread class-》继承Thread类 重点

Runable接口-》实现runable接口 重点

Callable接口-》实现Callable接口 了解

public class Thread01 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("这是一个测试");
        }
    }

    public static void main(String[] args) {

//        实例化包含多线程的类开启,使用start()方法开启线程
        Thread01 thread01 = new Thread01();
        thread01.start();

        for (int i = 0; i < 500; i++) {
            System.out.println("插入数值");
        }
    }
}

运行结果:

202109211744779

Runnable接口

runnable接口也可以实现多线程

runnable接口就是静态代理类

推荐使用runnable接口,避免单继承的局限性,方便同一个对象被多个线程使用

public class Thread03 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("case:"+i);
        }
    }

    //runnable使用了静态代理
    public static void main(String[] args) {
        //创造线程
        Thread03 thread03 = new Thread03();
        //实例化线程体,把runnable目标导入
        new Thread(thread03).start();

        for (int i = 0; i < 100; i++) {
            System.out.println("test:"+i);
        }
    }

}

并发

初识并发

/**
 * 单个对象,多个线程
 * 火车票模拟,问题:多线程,如果线程太多可能会导致高并发问题,不同的人抢到了同样的票
 */
public class Thread04 implements Runnable{

    private int ticket = 10;

    @Override
    public void run() {
        while (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "->拿到了第" + ticket-- + "张票");
        }
    }

    public static void main(String[] args) {
        Thread04 thread04 = new Thread04();

        new Thread(thread04,"小1").start();
        new Thread(thread04,"小3").start();
        new Thread(thread04,"小2").start();
    }
}

龟兔赛跑

使用runnable接口进行一个对象多个线程比较谁先到终点

/**
 * 多线程龟兔赛跑
 */
public class Race implements Runnable{
//    胜利者
    private static String winner;
//    线程体
    @Override
    public void run() {

        for (int i = 0; i <= 101; i++) {
            //判断比赛是否结束
            boolean flag = gameOver(i);
            if (flag){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
        }
    }

    private boolean gameOver(int steps){
        if (winner!=null){
            return true;
        }else {
            if (steps>=101){
                winner = Thread.currentThread().getName();
                System.out.println("winner is "+winner);
                return true;
            }
        }
        return false;
    }


    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }

}

线程状态

202110050858816 (1)

线程停止

线程停止不建议使用JDK提供的过时的stop(),destroy()

停止线程,自己创建一个标识符用来 停止线程更加的安全

标识符 flag=false,表示线程停止运行

//线程停止运行,标识符
public class ThreadStop implements Runnable{

    //设置标识位
    private Boolean flag = true;

    @Override
    public void run() {
        int i=0;
        while (flag){
            System.out.println("线程停止"+i++);
        }
    }

    public static void main(String[] args) {
        ThreadStop threadStop = new ThreadStop();
        new Thread(threadStop).start();

        for (int i = 0; i < 100; i++) {
            if (i>50){
                threadStop.isStop();
            }
            System.out.println(i);
        }
    }

    //更改标识位
    public void isStop() {
        this.flag=false;
    }

}

线程延迟sleep

线程延迟,可以使用Thread.sleep来进行线程延迟设定

线程需要抛出异常InterruptedException

sleep到了后进入就绪状态

sleep可以用来模拟网络延迟,倒计时等

每个对象都有一个锁,sleep不会释放锁

import java.text.SimpleDateFormat;
import java.util.Date;

//模拟实时获取当前时间
public class ThreadSleep01 {

    public static void main(String[] args) {
        //获取当前时间
        Date date = new Date(System.currentTimeMillis());

        while (true){
            try {
                //线程延迟
                Thread.sleep(1000);
                //更新时间
                date = new Date(System.currentTimeMillis());
                //格式化时间
                System.out.println(new SimpleDateFormat("yyyy:HH:dd HH:mm:ss").format(date));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程礼让yield

优点:

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞

  • 将线程从运行状态转为就绪状态

  • 让cpu重新调度,礼让不一定成功,看cpu心情

//线程礼让,礼让不一定成功
public class ThreadYield implements Runnable{

    public static void main(String[] args) {
        ThreadYield threadYield = new ThreadYield();
        new Thread(threadYield,"小白").start();
        new Thread(threadYield,"小明").start();
        new Thread(threadYield,"小黑").start();

    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"执行线程");
        Thread.yield();  //开启礼让
        System.out.println(Thread.currentThread().getName()+"关闭线程");
    }
}

礼让成功图:

202110051033033

礼让失败图:

202110051032668

线程插队join

join不建议使用,会造成线程阻塞

多线程同步执行,当使用Thread.join()时将会先将Thread.run()执行完毕才会执行其他线程

//join插队解析
public class ThreadJoin implements Runnable{

    @Override
    public void run() {
        //run线程
        for (int i = 0; i < 200; i++) {
            System.out.println("插队的人->>>>>"+i);
        }
    }

    public static void main(String[] args) {
        ThreadJoin threadJoin = new ThreadJoin();
        //静态代理导入对象
        Thread thread = new Thread(threadJoin);
        //开启多线程
        thread.start();
        //创建个主线程
        for (int i = 0; i < 100; i++) {
            System.out.println("排队的人->"+i);
            if (i==50){
                try {
                    //开始插队,执行run线程
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

线程状态state

线程状态。 线程可以处于以下状态之一:

  • NEW
    尚未启动的线程处于此状态。
  • RUNNABLE
    在Java虚拟机中执行的线程处于此状态。
  • BLOCKED
    被阻塞等待监视器锁定的线程处于此状态。
  • WAITING
    正在等待另一个线程执行特定动作的线程处于此状态。
  • TIMED_WAITING
    正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
  • TERMINATED
    已退出的线程处于此状态。

线程生命周期:

NEW(开启)->RUNNABLE(就绪)->TIMED_WAITING(阻塞)->TERMINATED(销毁)

完整代码:

public class ThreadState{
    public static void main(String[] args) {
        //使用lambda表达式执行runnable接口
        Thread thread = new Thread(()->{
            //循环5个线程
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程执行结束");
        });
        //获取线程的状态
        Thread.State state = thread.getState();
        //输入线程状态
        System.out.println(state);
        //开启线程NEW
        thread.start();
        //循环输出线程当前状态
        while (state != Thread.State.TERMINATED){
            try {
                Thread.sleep(100);
                state = thread.getState();
                System.out.println(state);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程优先级priority

线程优先级低,只意味着概率降低,不是按照从低到高来算,具体还是看cpu的调度

线程的优先级范围:1~10

  • Thread.MIN_PRIORITY=1;
  • Thread.MAX_PRIORITY=10;
  • Thtead.NORM_PRIORITY=5;

改变优先级:

  • getPriority().setPriority(int xx)

完整代码:

//获取和设置线程优先级
public class ThreadPriority {
    public static void main(String[] args) {
        //获取主线程的优先级    主函数优先级默认为5
        System.out.println(Thread.currentThread().getName() + "->>" + Thread.currentThread().getPriority());

        MyThread myThread = new MyThread();
        //创建多线程对象
        Thread thread1 = new Thread(myThread);
        Thread thread2 = new Thread(myThread);
        Thread thread3 = new Thread(myThread);
        Thread thread4 = new Thread(myThread);
        //设置线程优先级,然后启动线程
        thread1.setPriority(9);
        thread1.start();

        thread2.setPriority(1);
        thread2.start();

        thread3.setPriority(5);
        thread3.start();

        thread4.setPriority(3);
        thread4.start();

    }
}
class MyThread implements Runnable{
    @Override
    public void run() {
        //输出所有创建的线程的优先级
        System.out.println(Thread.currentThread().getName() + "->>" + Thread.currentThread().getPriority());
    }
}

线程守护daemon

守护线程:

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕,如后台操作日志,监控内存和GC回收

完整代码:

//开启保护线程
public class ThreadDaemon implements Runnable{

    public static void main(String[] args) {
        //开启主线程
        new Thread(new ThreadDaemon()).start();
        
        Thread threadGod = new Thread(new God());
        //修改用户进程为保护进程
        threadGod.setDaemon(true);   // 默认为false 为用户经常,改为true为保护进程
        threadGod.start();
        //开启人的线程
        new Thread(new Man()).start();

    }
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("->>>>>"+i);
        }
    }
}

class God implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("上帝保护着你");
        }
    }
}

class Man implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("你度过了->"+i+"天");
        }
    }
}

线程同步

线程同步形成条件:队列+锁

线程同步就像几个人一起去一个厕所,锁相当于门,锁上其他人就进不去,安全性强,效率会变低

在一个进程带来的多个线程共享一块存储空间,虽然方便,但是会造成冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在一些问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起
  • 在多线程竞争下,加锁,释放锁会导致上下文切换和调度延时,引起性能问题
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

不安全的线程实例:

//不安全的抢票多线程
public class ThreadSafety{
    public static void main(String[] args) {
        Buy buy = new Buy();
        new Thread(buy,"第一人啊").start();
        new Thread(buy,"第二人啊啊").start();
        new Thread(buy,"第三人啊啊啊").start();

    }
}
class Buy implements Runnable{
    private int tickNumber = 10;
    boolean flag = true;

    @Override
    public void run() {
        while (true){
            isBuy();
        }
    }

    public void isBuy(){
        if (tickNumber<0){
            flag=false;
            return;
        }
        System.out.println(Thread.currentThread().getName()+"拿到了"+tickNumber--);
    }
}

结果图:

202110052058202

线程安全

synchronized

同步方法:

synchronized方法:

public synchronized void medhod(int agrs){}

synchronized块:

synchronized (要锁的内容){}

添加synchronized方法和方法快可以完成同步

synchronized方法控制对"对象"的访问,每个对象对应着一把锁,每个synchronized方法都必须获得对象的锁,否则线程会阻塞。方法一旦执行,就独占该锁,直到该方法放回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行

方法分为只读和修改,我们只需要对修改方法进行同步即可

synchronized方法默认锁的是this自己

synchronizad块可以锁任何对象

synchronizad方法完整代码:

package com.hdt.kuangshen.thread;

//线程延迟
public class ThreadSleep implements Runnable{

    //设置票数
    private static int piao =10;

    @Override
    public synchronized void run() {
        while (piao > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "->" + piao-- + "票");
        }

    }

    public static void main(String[] args) {
        ThreadSleep threadSleep = new ThreadSleep();
        new Thread(threadSleep,"小明").start();
        new Thread(threadSleep,"小白").start();
        new Thread(threadSleep,"小黑").start();


    }
}

synchronizad块完整代码:

import java.util.ArrayList;
import java.util.List;

public class ThreadSafety01 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i <= 10000; i++) {
            new Thread(()->{
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
//        try {
//            Thread.sleep(100);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        System.out.println(list.size());
    }
}

CopyOnWriteArrayList(了解)

测试juc安全类的集合

完整代码:

//测试juc安全类的集合
public class ThreadCopyOnWriteArraylist {
    public static void main(String[] args) {
        //创建安全集合
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                //添加进juc安全集合中
                list.add(Thread.currentThread().getName());
            }).start();

            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(list.size());
    }
}

死锁

什么是死锁:

死锁指的是两个或两个以上的进程在执行过程中,由于竞争资源或彼此通信而造成的阻塞,诺没有外力作用,将会无法继续进行

产生死锁的4个重要条件:

  • 互斥条件:一个资源每次只能被一个进程使用。
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

完整代码:

//线程锁,死锁
public class ThreadDieLock {
    public static void main(String[] args) {
        Lock lock1 = new Lock(0,"第一人");
        Lock lock2 = new Lock(2,"第二人");

        new Thread(lock1).start();
        new Thread(lock2).start();
    }
}
//笔
class Pen{}
//笔盖
class Cover{}
//抢锁
class Lock implements Runnable{

    static Pen pen = new Pen();
    static Cover cover = new Cover();

    int choice;
    String pName;

    Lock(int choice,String pName){
        this.choice=choice;
        this.pName=pName;
    }
    @Override
    public void run() {
        try {
            Grab();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //造成锁死
    public void Grab() throws InterruptedException {
        if (choice==0){
            synchronized (pen){
                System.out.println(this.pName+"要笔");
                Thread.sleep(1000);
                synchronized (cover){
                    System.out.println(this.pName+"要盖");
                }
            }
        }else {
            synchronized (cover){
                System.out.println(this.pName+"要盖");
                Thread.sleep(1000);
                synchronized (pen){
                    System.out.println(this.pName+"要笔");
                }
            }
        }
    }
}

Lock锁

可重入锁,通过显式定义同步锁对象来实现同步,从jdk5.0开始

ReentrantLock实现Lock,他与synchronized的功能一样,比较常用ReentrantLock

实例:

class A{
    private final ReentrantLock lock = new ReentrantLock();
    public void b(){
        lock.lock();
        try {
            //需要保护安全线程的代码
        }finally {
            lock.unlock();
            //如果同步代码有异常,要把unlock()写入finally中
        }
    }
}

完整代码:

import java.util.concurrent.locks.ReentrantLock;

//线程锁的使用
public class ThreadLock {
    public static void main(String[] args) {
        LockTest lockTest = new LockTest();
        //定义多个资源获取同一个对象
        new Thread(lockTest).start();
        new Thread(lockTest).start();
        new Thread(lockTest).start();
    }
}

class LockTest implements Runnable{
    private int ticket = 10;
    //锁的类
    private final ReentrantLock reentrantLock = new ReentrantLock();

    @Override
    public void run() {
        try {
            grab();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void grab() throws InterruptedException {
        //锁的启动,锁使用最好加try{}finally{关闭锁}
        reentrantLock.lock();
        try {
            while (ticket>0){
                Thread.sleep(1000);
                System.out.println(ticket--);
            }
        }finally {
            //解锁
            reentrantLock.unlock();
        }
    }
}

Lock与synchronized比较:

  • Lock是显示锁(手动开启和关闭锁),synchronized是隐式锁(出了作用域自动释放)
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序:
    • Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体外)

生产者/消费者问题

202110091607289

  • 在缓冲区为空时,消费者不能再进行消费
  • 在缓冲区为满时,生产者不能再进行生产
  • 在一个线程进行生产或消费时,其余线程不能再进行生产或消费等操作,即保持线程间的同步
  • 注意条件变量与互斥锁的顺序

利用缓冲区解决:管程法

202110091605656

线程池

由于经常创建和销毁,对性能的影响很大

解决:

提前创建多个线程,放入线程池中,使用时直接获取,使用完放回池中

好处:

  • 提高相应速度
  • 降低资源消耗
  • 便于线程管理
  • 线程配置解析
    • int corePoolSize // 核心线程数量
    • int maximumPoolSize // 最大线程数(核心线程数 + 临时线程数)
    • long keepAliveTime // 临时线程的最大空闲时间
    • TimeUnit unit // 时间单位(针对临时线程)
    • BlockingQueue workQueue // 任务队列
    • ThreadFactory threadFactory // 线程工厂(可以为线程起名字),线程池中的线程默认名称为pool-m-thread-n
    • RejectedExecutionHandler handler // 拒绝策略

202110091634380

ExecutorService接口

使用 ExecutorService 接口可以管理线程池,比如提交任务,停止任务

线程池常用接口:

  • void shutdown() 当任务队列中的所有任务都执行完成之后,销毁线程池 (不允许有新的任务提交)

    • 正在运行的任务不会受到影响,继续运行
    • shutdown() 之后提交的任务会抛出 RejectedExecutionException 异常,代表拒绝接收
  • List<Runnable> shutdownNow() (可能会) 停止正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表

    • shutdownNow() 之后提交的任务会抛出RejectedExecutionException 异常,代表拒绝接收
    • 对于正在运行的任务
      • 如果此任务的代码可以抛出 InterruptedException 异常 (如wait、sleep、join方法等),则停止此任务,抛出 InterruptedException 异常
      • 如果此任务的代码中无法抛出 InterruptedException 异常,则此任务会持续运行直到结束
  • <T> Future<T> submit(Callable<T> task) 提交带返回值的任务,返回一个表示该任务的 Future 对象

  • Future<?> submit(Runnable task) 提交 Runnable 任务,并返回一个表示该任务的 Future 对象

  • <T> Future<T> submit(Runnable task, T result) 提交 Runnable 任务,并返回一个表示该任务的 Future 对象

  • Executor 接口中有 execute(Runnable task) 方法,可以用来提交 Runnable 任务

获取接口实例

  • 获取 ExecutorService 实例可以利用 JDK 中的 Executors 类中的静态方法,常用的如下
    • 带缓存线程池 static ExecutorService newCachedThreadPool()
    • 固定大小线程池 static ExecutorService newFixedThreadPool(int nThreads)
    • 单线程线程池 static ExecutorService newSingleThreadExecutor()

使用缓存线程池

  • 使用方法 ExecutorService executorService = Executors.newCachedThreadPool();

特点:

  • 核心线程数为0,所有线程都是临时线程,线程可重复利用
  • 最大线程数为 Integer.MAX_VALUE ,表示 Java 中的最大整数,意味着线程可以无限创建
  • 临时线程的最大空闲时间是60s
  • 任务队列(阻塞队列)的方式为没有容量,来一个任务使用一个线程执行它,任务不会等待
  • 适合任务数密集,但每个任务执行时间较短的情况

由于每个线程执行完之后会归还池中,在空闲时间内还可以执行其余任务,故起名为带缓存

完整代码:

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

public class ThreadPool {
    public static void main(String[] args) {
        //设置线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);
        service.execute(new MyThreads());
        service.execute(new MyThreads());
        service.execute(new MyThreads());
        service.execute(new MyThreads());
        //关闭链接
        service.shutdown();
    }
}

class MyThreads implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
posted @ 2021-11-07 14:23  HeiDaotu  阅读(29)  评论(0编辑  收藏  举报