多线程基础复习(java)

线程进程的概念

程序的概念: 是为完成特定任务,用某种语言编写的一组指令的集合,简单来说就是我们写的代码

进程的概念:

  1. 进程是指运行中的程序,比如我们使用 QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间
  2. 进程正在运行的一个程序。是动态过程,有它自身的产生,存在和消亡的过程

线程

  1. 线程由进程创建的,是进程的一个实体
  2. 一个进程可以拥有多个线程

比如你开了个进程,你有下载任务1,那就是一个线程。还有下载任务2,就是线程2

在这里插入图片描述

最大那个就是进程下面7个是它的线程

单线程:同一个时刻,只允许一个线程

多线程:同一个时刻,可以执行多个线程,比如:一个 qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件

并发:同一个时刻,多个任务交替执行,造成一种 “貌似同时” 的错觉,简单的说,单核 CPU 实现的多任务就是并发

在这里插入图片描述

并行:同一时刻,多个任务同时进行。多核 CPU 可以实现并行,也可能并发和并行同时进行

在这里插入图片描述



线程状态转换图

这里直接拿了B站韩顺平老师的图
请添加图片描述

  1. New(新建)

新建一个线程对象

  1. Ready(就绪状态)

线程调用 start 方法,就变为就绪状态

  1. Running(运行状态)

线程被调度,从就绪状态变为运行状态

  1. Timed_Waiting(第一种等待状态)

有期限等待,如果线程在指定的时间范围内没有被其他线程唤醒(例如,通过notify()或notifyAll()方法),那么它会超时并自动停止等待,继续执行后续的代码。

  1. Waiting(第二种等待状态)

无期限等待,只有当其他线程调用了同一个对象的notify()或notifyAll()方法来唤醒它时,该线程才会从等待状态恢复,继续执行后续的代码。

  1. Blocked(阻塞状态)

阻塞状态是指线程因为某些原因无法继续执行,需要等待其他条件满足才能继续执行。例如,当一个线程调用了某个对象的同步方法或同步代码块时,如果锁被拿了,那就先阻塞,直到锁被释放,才转就绪状态

  1. Terminated(退出状态)

下面说一下什么时候锁会释放:

  • 同步方法或代码块执行完毕
  • 同步方法或代码块中遇到 break, return
  • 同步方法或代码中出现了未处理的 Error 或 $Exception,导致异常结束
  • 同步方法或代码块中执行了 wait() 方法,线程暂停了,会释放锁

注意:sleep(), yield() 不会 释放锁



线程的使用

注意执行 main 方法也是开一个线程

直接继承Thread 方式

public class Main {
    public static void main(String[] args) {
    
        //创建对象
        Test test = new Test();
        
        //开启线程
        test.start();
    }
}

class Test extends Thread {
    @Override
    public void run() {
        System.out.println("Crazy!!!");
    }
}

实现 Runnable接口 方式

public class Main {
    public static void main(String[] args){

        //创造对象
        Test test = new Test();

        //创建线程代理(底层是静态代理模式)
        Thread thread1 = new Thread(test);

        //启动线程
        thread1.start();

    }
}

class Test implements Runnable {
    @Override
    public void run() {
        System.out.println("Crazy!!!");
    }
}

实现 Callable 接口 方式

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //创造对象
        Test test = new Test();

        //用于管理多线程运行的结果
        FutureTask<Integer> ft = new FutureTask<>(test);

        //启动线程
        Thread thread = new Thread(ft);

        //线程启动
        thread.start();

        //利用 ft 获取返回值
        Integer num = ft.get();
        System.out.println(num);

    }
}

class Test implements Callable<Integer>  {
    @Override
    public Integer call() {
        System.out.println("Crazy!!!");
        return 10;
    }
}

总结:

  1. 由于 java 是单继承的,所以 继承了其他类就不能继承 Thread 类了
  2. Runnable 接口更加适合多线程共享一个资源的情况,而且避免了单继承的限制
  3. Callable 这种就是可以有返回值

线程常用方法

  • String getName():返回此线程的名称

  • void setName(String name):设置线程名字

  • static Thread currentThread():获取当前线程的对象

  • static void sleep(long time):指定线程休眠时间,单位为毫秒

  • setPriority(int newPriority):设置线程优先级

  • final int getPriority():获取线程优先级

  • final void setDaemon(boolean on):设置为守护线程

  • public static void yield():暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程

  • public static void join():等待其他线程终止,先进入等待,直到另一个线程运行结束,才由阻塞转为就绪状态

  • interrupt:中断线程,但没真正中断,一般用于中断正在休眠的线程

  • notify():唤醒等待的单个线程

  • notifyAll():唤醒等待的所有线程


setPriority

取值范围为 1~10,


yield

让当前运行的线程回到就绪状态,这时候所有优先级一样的线程重新争夺运行权概率还是一样的,所以往往有时候不能让步


interrupt

发送中断信号,让线程在等待状态的时候可以抛出异常结束线程

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        test.start();

        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("h" + i);
        }

		//这里直接让下面睡眠20000毫秒抛出异常,直接中断
        test.interrupt();


    }
}

class Test extends Thread {
    @Override
    public void run() {
        while(true) {

            for (int i = 1; i < 5; i++) {
                System.out.println("hello");
            }

            try {
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                System.out.println("等待状态被interrupt");
            }
        }
    }
}

join

插队,先执行插入的线程所有任务。

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        test.start();

        for (int i = 0; i <= 20; i++) {
            Thread.sleep(1000);
            System.out.println("主线程:" + i);
            if (i == 5) {
                System.out.println("主线程 礼让 子线程");
				/*
				这里主线程礼让子线程,子线程执行完毕,才轮到主线程执行
				*/
                test.join(); 
                System.out.println("主线程继续执行");
            }
        }

        test.interrupt();


    }
}

class Test extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("子线程: " + i);
        }
    }
}

setDaemon

  1. 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束,就是所有非守护线程
  2. 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束,常见的守护线程 就是 垃圾回收机制,特点是当所有
public class Main {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();


        //如果我们希望当main线程结束,子线程可以自动结束
        //只需设置成守护线程
        t.setDaemon(true);
        t.start();

        for (int i = 1; i <= 5; i++) { //main线程
            System.out.println("HEHE");
            Thread.sleep(1000);
        }
    }
}

class T extends Thread {
    @Override
    public void run() {
        for (; ;) {
            try {
                Thread.sleep(500); //休眠50毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Crazy!!!");
        }
    }
}


线程的同步机制

因为有时候,不同步可能会出问题,比如三个线程同时卖票,他们可能同时都卖出一张票,但是,票总数量才减少一张,实际减少三张这种问题

当由一个拿到锁,其他线程就会被阻塞,直到锁释放,他们才会重新回到就绪状态

同步代码块实现:

锁对象一定要保证唯一:一般可以用 对象名.class 字节码,有些例子会用 static Object obj = new Object()

public class Main {
    public static void main(String[] args) {

        SellTicket sellTicket03 = new SellTicket();
        new Thread(sellTicket03).start();//第1个线程-窗口
        new Thread(sellTicket03).start();//第2个线程-窗口
        new Thread(sellTicket03).start();//第3个线程-窗口

    }
}


//实现接口方式, 使用synchronized实现线程同步
class SellTicket implements Runnable {

    private int ticketNum = 100;//让多个线程共享 ticketNum

    @Override
    public void run() {
        while (true) {
            synchronized (SellTicket.class) {
                if (ticketNum <= 0) {
                    System.out.println("售票结束");
                    break;
                }

                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                        + " 剩余票数=" + (--ticketNum));//1 - 0 - -1  - -2
            }
        }
    }
}

同步方法实现

锁对象不能自己指定

  • 非静态:this
  • 静态:当前类的字节码文件对象
public class Main {
    public static void main(String[] args) {

        SellTicket sellTicket03 = new SellTicket();
        new Thread(sellTicket03).start();//第1个线程-窗口
        new Thread(sellTicket03).start();//第2个线程-窗口
        new Thread(sellTicket03).start();//第3个线程-窗口

    }
}


//实现接口方式, 使用synchronized实现线程同步
class SellTicket implements Runnable {

    private int ticketNum = 100;//让多个线程共享 ticketNum
    private boolean loop = true;//控制run方法变量


    //1.同步方法
    //2.这时锁在 this 对象
    public synchronized void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行sell方法
            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                loop = false;
                return;
            }

            //休眠50毫秒, 模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                    + " 剩余票数=" + (--ticketNum));//1 - 0 - -1  - -2
        }

    @Override
    public void run() {
        while (loop) {
            sell();//sell方法是一共同步方法
        }
    }
}

lock 锁实现

  1. 创造一个锁
  2. 上锁
  3. 解锁
    可以手动指定上锁位置,释放锁的位置
public class Main {
    public static void main(String[] args) {

        SellTicket sellTicket03 = new SellTicket();
        new Thread(sellTicket03).start();//第1个线程-窗口
        new Thread(sellTicket03).start();//第2个线程-窗口
        new Thread(sellTicket03).start();//第3个线程-窗口

    }
}


//实现接口方式, 使用synchronized实现线程同步
class SellTicket implements Runnable {

    Lock lock = new ReentrantLock();//造锁
    private int ticketNum = 100;//让多个线程共享 ticketNum

    @Override
    public void run() {
        while (true) {
            lock.lock(); //上锁
                if (ticketNum <= 0) {
                    System.out.println("售票结束");
                    break;
                }

                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                        + " 剩余票数=" + (--ticketNum));//1 - 0 - -1  - -2


            lock.unlock(); //解锁
        }
    }
}

注意这里 break 和 return 不会 解锁,所以要注意 unlock



线程池

不用线程池的弊端

用到线程就创建,用完就消失


线程池原理

  1. 创建一个池
  2. 提交任务时,线程池会创建新的线程对象,任务执行完毕,线程归还线程池,下次再次提交任务时,不需要再创建新的线程,直接复用已有的线程
  3. 但是如果提交任务时,池中没有空闲线程,也无法创建新的线程,任务就会排队等待

系统的线程池

创造线程池

  1. public static ExecutorService new CachedThreadPool():创建一个没有上限的线程池
  2. public static ExecutorService newFixedThreadPool(int nThreads):创建有上限的线程池
public class Main {
    public static void main(String[] args) throws InterruptedException {

        //1. 获取线程池对象
        ExecutorService pool1 = Executors.newCachedThreadPool();

        //2. 提交任务:执行完任务会还回去
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());


        //3. 销毁线程池
        //pool1.shutdown();
    }
}

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        //for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" /*+ i*/);
        //}
    }
}

自定义线程池

构造方法的参数

  1. 核心线程数:不能小于0
  2. 线程池中最大线程数量(包括临时线程):最大数量 >= 核心线程数
  3. 空闲时间(值):不能小于0
  4. 空闲时间(单位):用 TimeUnit 指定
  5. 阻塞队列:不能为 null
  6. 创建线程的方式:一般用工具类的方法Executors.privilegedThreadFactory(),//创建线程工厂
  7. 执行的任务过多时的解决方案 不能为null

核心线程都在占用,且队列已经满了,就会创建临时线程,临时线程的数量就是 核心线程数量 - 核心线程数

public class Main {
    public static void main(String[] args) throws InterruptedException {

        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3, //核心线程数量
                6, //最大线程数量
                60,//空闲线程最大存活时间
                TimeUnit.SECONDS,//时间单位
                new ArrayBlockingQueue<>(3)/LinkBlockingQueue<>()//任务队列,Array指定长度,Link的无限增长
                Executors.privilegedThreadFactory(),//创建线程工厂
                new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略/静态内部类
        );
    }
}

阻塞队列

实现类
ArrayBlockingQueue:

底层时数组,有界,要指定长度
LinkedBockingQueue
底层时链表,最大值为 int 的最大值


线程池多大合适

  • CPU 密集型运算:最大并行数 + 1
  • I/O密集型运算:

最大并型数 * 期望的 CPU 利用率 * 总时间(CPU时间 + 等待时间)/ CPU 计算时间

用 thread dump 工具可以查 cput 计算时间和等待时间

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值