Java基础-多线程基础概念 (多线程的好处;创建线程的三种方式;多线程常用方法:sleep(long),wait(),notifyAll())

Java基础-多线程基础概念

前言:多线程系列拖到现在,也该复习下
这个东西怎么说呢,也是非常常见。而且面试基本上是必问的(好吧,这是我复习的主要目的)
一共预计会有6篇,这是第一篇

推荐书籍:通过博客来理解多线程总会缺失点什么,毕竟每个人写的博客水平有不同。而且有些信息被加工后可能并不适合你
《Java核心技术 卷Ⅰ》 这本书不止是线程,其实每章内容都不错。而且对Java水平有比较大的提升
《Java编程思想》 这本书对Java水平提升帮助很大,但是老实说,感觉多线程那一篇内容很全,但是文章很乱
《Java并发编程的艺术》 多线程相关鼎力推荐

还有种好的方式就是用IDEA,通过ctrl+B快捷键直接追溯源码。不需要一字一句的去理解源码(太费时间)。看输出删除,返回值。或者其他想知道的东西。而且能看到继承,实现关系。这种方式收益也挺大
多线程相关类或接口 :Thread Runnable Callable FutureTask

大纲脑图
多线程脑图

1. 线程简介

本段简单介绍什么是线程,进程。线程的状态,状态转换,以及线程优先级

1.1 线程与进程

进程 是资源分配的最小单元,一个进程可以有多个线程
线程 是资源调度的最小单元,同一个进程下,各个线程的内存质押共享,所以线程间的通信代价小。线程也称为轻量进程

1.2 为什么使用多线程

  • 对于多CPU,多核的来说,多线程能提高程序响应速度。相当几个人做同时开工,肯定比一个人快。(但是这个也不一定,因为线程调度,分配资源都需要开销。就像一个项目组5-10个人效率,可能比20-100个人效率更高,因为开会,信息共享都需要大量时间)
  • 对于单CPU单核机器(这种情况可能更常见)来说,因为有阻塞的存在,多线程效率能够更好。主要是磁盘太慢,如果是单线程,可能磁盘在007,CPU却大多数时间在喝茶。如果运行在内存中,单线程可能会更好,没有上下文切换的额外开销(例如 Redis)
  • 有些场景,多线程实现起来简单。例如游戏,不同的角色同时在做不同的事情,用单线程就很难处理这种情况

单线程相当于顺序结构,一次做一件事,这件事情做完了(即使这件事中途可能要等待好久)才能做另外一件事情。
多线程相当于在一个时间段内,做几件事情

有些时候多线程好:一般是有些事情等待时间长,可以中途做其他事情
有些时候单线程好:例如我想8个小时写3篇博客,肯定是单线程好,如果中途切换着写,可能思路就没了

1.3 线程状态

Java的线程状态和操作系统的不一样
线程状态
Java线程转换,其实我觉得理解其每个状态的意思就自然知道装换规则了。不用可以去记忆,没必要

  • New 新建状态
  • Runnable 可运行,代表线程可以被运行,可能正在运行,也可能等待时间片的过程中
  • Waiting 等待 因为调用了wait而暂停。注意与Blocked区分
  • Time_Waiting 计时等待,调用了sleep和wait后暂停,等待固定的时间后,进入可运行状态
  • Blocked 阻塞 因为缺少某些资源被迫不能运行
  • Terminated 终止状态 线程完成,或者说某些原因线程挂了(功成身退与中途陨落)

1.4 线程优先级

Java线程可以通过 getPriority()与setPriority() 获取和设置线程优先级
Java线程有10个优先级。默认为5。优先级是有操作系统觉得的,其实在Java程序设置了优先级,也没什么卵用,因为操作系统不认(3月份HB的绿码,BJ也不认啊)
windows有7个优先级

1.5 守护线程

守护线程,在《Java编程思想》里叫后台线程,为其他线程服务的线程,例如垃圾回收线程
可以通过 setDaemon()将线程设置为守护线程

2. Java线程相关操作

2.1 Java创建线程的三种方式

方式1: 继承Thread类,重写run()方法来创建线程
   // 创建线程方式一:继承Thread类,重写Run方法
    class ThreadChild extends Thread{
        @Override
        public void run() {
            super.run();
            int length = 100;
            Thread.currentThread().setName("方式一:继承Thread方式实现线程");
            for (int i = 0; i < length; i++){
                System.out.println("这是以方式壹实现的线程,循环体i的值为" + i);
            }
        }
    }
方式2: 实现Runnable接口,重写run()方法
// 创建线程方式二:实现Runnable接口,重写Run方法
    class ThreadRunnable implements Runnable{
        @Override
        public void run() {
            int length = 300;
            Thread.currentThread().setName("方式二:实现Runnable接口方式实现线程");
            for (int i = 200; i < length; i++){
                System.out.println("这是以方式二实现的线程,循环体i的值为" + i);
            }
        }
    }
方式3: 实现Callable接口,重写call()方法
// 创建线程方式三:实现Callable接口,重写Call方法
    class ThreadCallable implements Callable{

        @Override
        public Object call() throws Exception {
            int length = 900;
            Thread.currentThread().setName("方式3:实现Callable接口方式实现线程");
            for (int i = 800; i < length; i++){
                System.out.println("这是以方式三实现的线程,循环体i的值为" + i);
            }
            // 我没有什么需要返回的,有点尴尬
            return null;
        }
    }
三种方式对比

方式一-继承Thread类的方式尽量少用,一般来说继承太过于重量级(继承子类用于所有父类公有的域,方法),实现接口的方式比继承好。
一般用方式二-实现Runnable接口的方式。如果有返回值就用方式三实现Callable接口

Runable和Callable接口都只有一个方法run()。实现Callable接口的方式有返回值。而且需要和FutureTask 配合使用

需要注意的是,启动线程用的start(),不是调用run方法 如果调用run就是调用普通方法

全部类代码

package com.java.basic.multithreading;

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

/**
 * @author dengtiantian
 */
public class ThreadTest {

    // 创建线程方式一:继承Thread类,重写Run方法
    class ThreadChild extends Thread{
        @Override
        public void run() {
            super.run();
            int length = 100;
            Thread.currentThread().setName("方式一:继承Thread方式实现线程");
            for (int i = 0; i < length; i++){
                System.out.println("这是以方式壹实现的线程,循环体i的值为" + i);
            }
        }
    }

    // 创建线程方式二:实现Runnable接口,重写Run方法
    class ThreadRunnable implements Runnable{
        @Override
        public void run() {
            int length = 300;
            Thread.currentThread().setName("方式二:实现Runnable接口方式实现线程");
            for (int i = 200; i < length; i++){
                System.out.println("这是以方式二实现的线程,循环体i的值为" + i);
            }
        }
    }

    // 创建线程方式三:实现Callable接口,重写Call方法
    class ThreadCallable implements Callable{

        @Override
        public Object call() throws Exception {
            int length = 900;
            Thread.currentThread().setName("方式3:实现Callable接口方式实现线程");
            for (int i = 800; i < length; i++){
                System.out.println("这是以方式三实现的线程,循环体i的值为" + i);
            }
            // 我没有什么需要返回的,有点尴尬
            return null;
        }
    }

    public static void main(String[] args) {
        ThreadTest threadTest = new ThreadTest();
        threadTest.testThread();
    }

    // 测试
    public void testThread(){
        ThreadChild threadChild = new ThreadChild();
        ThreadRunnable threadRunnable = new ThreadRunnable();
        ThreadCallable threadCallable = new ThreadCallable();
        //方式一开始运行
        threadChild.start();
        //方式二开始运行
        (new Thread(threadRunnable)).start();
        //方式三运行
        FutureTask<Object> task = new FutureTask<>(threadCallable);
        (new Thread(task)).start();
    }

}

2.2 线程相关常用方法

去看源码或者API最自在,我这个也是从里面复制的

2.2.1 Object线程相关方法
  • public final void wait() throws InterruptedException 暂停线程 会释放锁
  • public final native void notify();随机唤醒一个线程
  • public final native void notifyAll() 唤醒所有线程

这三个方法都是Object的,我记得有个很常见的问题是:wait和sleep的区别
这个是利用每个对象本身自带的锁实现的。都是本地方法
notify()这个方法其实用的少,因为是随机唤醒一个线程,他自己也不知道唤醒的谁

2.2.2Thread相关常用方法
  • getXXX系列方法
  • setXXX系列方法
  • public void interrupt() 中断 试图停止线程
  • public static boolean interrupted()测试线程是否被中断
  • public synchronized void start() 启动线程
  • public static native void sleep(long millis) throws InterruptedException 休眠,不会释放锁
  • public final void stop() 停止线程,已被放弃
  • public final void suspend()暂停线程,已被放弃
  • public final void resume() 唤醒线程,已被放弃
  • public static native void yield() 让出锁和时间片,如果他的优先级还是最高,那么他还会抢到锁

3. 线程间协作

其实这个标题当时其实是线程间的通信,后面改成同步。最后是这个
反正这节 目的是为了表达线程之间工作关系
重复:线程是共享内存,所以通信代价很低,但是这也有问题。
举个例子:父亲剥柚子,儿子吃柚子。这两个动作是可以同时进行的(在已有剥好的前提下。) 儿子吃柚子得在有柚子可以吃的前提下,就是父亲有剥好的柚子。就是得剥好才能吃(你可不要说不剥儿子就吃,我敬你是条汉子)。本节就是说明线程的协作关系。
以及还有父亲,儿子都吃瓜子。两种是竞争关系,得有瓜子才行

3.1 Synchronized和Volatile机制

Synchronized 是通过对某个共享的资源加锁,是这个资源一次只能被一个资源消耗者使用(利用对象锁,下章节详细写)
Volatile相当于,对某个资源的操作一旦发生,马上所有关注这个资源的用户都知晓(内存模型里讲最好)

3.2 等待通知机制

利用wait和notifyAll方法,对于有一定先后关系的操作,一旦 后者先执行了,马上wait,然后先者,执行后notifyAll唤醒后者消费

3.3 使用管道通信

我不记得Java管道知识点了,有点尴尬。后续补上

聊下Redis的管道和操作系统的管道缓解下尴尬
Redis的管道有点类似于JDBC里面的addBatch()和累积发送(数据攒到一定量在传输)。就是说Redis的管道为了提高效率,将多条命令攒到一起提交给服务器执行
Linux的管道相当于提供一个中间文件,线程或进程将数据放到管道,然后另外的线程或进程从管道获取数据。其实也是表明了一种先后的顺序,虚拟机里面happen-before那味儿。

3.4 ThreadLocal的使用

这个其实线程有些数据不想共享给别的线程,就相当于局部变量。知道这个我觉得就好了,其实没什么特殊的,具体用法用的时候查看具体的API或者看源码(ctrl+b)就好了。我不明白为什么很多有很多文章特意提这个

4. 小结

  1. 什么是线程,线程与进程的区别
  2. 多线程的好处。多线程一定快吗?(需要知道上下文切换是有消耗的)
  3. 创建线程的三种方式
  4. 线程相关的方法

总目录:Java进阶之路-目录

                天高地迥,觉宇宙之无穷。兴尽悲来,识盈虚之有数
                《滕王阁序》 王勃
                博主:五更依旧朝花落
                首次发布时间:2020年4月18日17:24:53
                末次更新时间:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值