Java多线程

目录

线程相关概念

什么是线程

线程的基本使用

继承 Thread 和实现Runnable 的区别

线程常用方法

线程的优先级概念

线程的生命周期

线程的同步

java互斥锁

Lock锁

线程的死锁

诊断死锁

释放锁

线程池


线程相关概念

进程:

  1. 进程是指运行中的程序,比如QQ 就是启动了一个进程,操作系统就会为该进程分配内存空间,当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间

  2. 进程是程序的一次执行过程,或是 正在运行的一个程序 是动态进程:有它自身的产生,存在和消亡的过程

什么是线程

  1. 线程是由进程创建的 是进程的一个实体(执行路径)

  2. 一个进程可以拥有多个线程

相关概念:

  1. 单线程 :同一个时刻 只允许执行一个线程

  2. 多线程:同一个时刻可以执行多个线程 比如 一个QQ程序 可以同时开启多个聊天程序 一个迅雷 可以同时下载多个文件

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

  4. 并行:同一个时刻 多个任务同时执行, 多核CPU可以实现并行


线程的基本使用

创建线程的三种方式

  • 继承Thread类 重写run方法

  • 实现Runnable接口 重写run方法

  • 实现Callable接口 重写call方法

在idea下面的Terminal中输入jconsole 进入java监视和管理控制台

start() 启动线程

案例1 继承Thread类

    
//演示通过继承Thread 类创建线程
    public static void main(String[] args) {
        //创建Cat对象 可以当做线程使用
        Cat cat = new Cat();
        cat.start();//启动线程---> 会执行Cat 的run方法
        //说明当main线程启动一个子线程Thread-0 主线程不会阻塞 会继续执行
          //这时说明 主线程和子线程是交替执行
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程"+i);
            //让主线程休眠1秒
            Thread.sleep(1000);
        }
    }
}
//当一个类继承了Thread 类 该类就可以当做线程使用
// 我们会重写run方法 写上自己的业务逻辑
//run  Thead 类实现了 Runnable 接口的 run方法
/*
   @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
 */
/*源码解读
    1)public synchronized void start(){
            start0();
}
    2) Start0 是本地方法 是jvm调用 底层是c/c++实现
        真正实现多线程的效果 是start0() 而不是run方法
        private native void start0();
*/
class Cat extends Thread{
    @Override
    public void run() {// 重写run方法 写上自己的业务逻辑
        int a = 1;
        while (true) {
            //该线程 每隔一秒 在控制台输出 我是小猫
            System.out.println("我是小猫咪" + a++);
            // 让该线程休眠1秒
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            if(a == 10){//当  a = 10  退出循环  线程也就退出
                break;
            }
        }
    }
}

案例2 实现Runnable 接口

  1. java 是单继承的 在某些情况下一个类可能已将继承了某个父类 这时在用继承Thread类方法来创建线程显然是不可能了

  2. java设计者提供了另外一种方式创建线程,就是通过实现Runnable接口来创建线程

  
 //通过实现接口Runnable 来创建线程
    public static void main(String[] args) {
        Dog dog = new Dog();
       // dog.start();这里不能调用start
        //我们来创建 Thread对象 把dog(实现Runnable) 对象放进去
        Thread thread = new Thread(dog);
        thread.start();
    }
}
class Dog implements Runnable{
        int a = 1;
    @Override
    public void run() {
        while (true){
            System.out.println("小狗在叫"+(a++)+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(a > 10){
                break;
            }
        }
    }
}

案例3 多线程案例

//在main线程中启动2个子线程
    public static void main(String[] args) {
        t1 t1 = new t1();
        t2 t2 = new t2();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);
        thread1.start();//启动第一个线程
        thread2.start();//启动第二个线程
    }
}
class t1 implements Runnable{
int num = 0;
    @Override
    public void run() {
        //每隔一秒钟 输出hello
       while (true) {
           System.out.println("hello world" + (++num));
           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           if (num ==10){
               break;
           }
       }
​
    }
}
class t2 implements Runnable{
    int num = 0;
    @Override
    public void run() {
        //每隔一秒钟 输出hello
      while (true) {
          System.out.println("hi" + (++num));
          try {
              Thread.sleep(1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          if (num==5){
              break;
          }
      }
    }
}

案例4 实现Callable接口 重写call方法

  public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> task = new FutureTask<>(new T());
        Thread thread = new Thread(task);
        thread.start();
        //get() 获取任务的结果
        System.out.println(task.get());
    }
}
class T implements Callable <String>{
​
    @Override
    public String call(){
        for (int i = 0; i < 20; i++) {
  System.out.println(Thread.currentThread().getName());
            System.out.println("T类方法调用中");
        }
        return "执行完毕";
    }
 

继承 Thread 和实现Runnable 的区别

  1. 从java的设计来看 两者本质上没有区别 从jdk帮助文档我们可以看到 Thread类本身就实现了Runnable接口

  2. 实现runnable接口方式更加适合多个线程共享一个资源的情况 并且避免了单继承限制

线程常用方法

    start() //启动线程
​
    Thread.sleep(100)//线程休眠 (100)表示休眠100毫秒 线程的静态方法
​
    Thread.currentThread().getName() //获取线程名
​
    在idea下面的Terminal中输入jconsole 进入java监视和管理控制台
​
    SetName():// 设置线程名称 使其参数 name 相同
​
    getName()// 返回该线程的名称
​
    run // 调用线程对象 run方法
​
    setPriority(number) // 更改线程的优先级
​
    getPriority() // 回去线程的优先级
​
    interrupt()  // 中段线程 但并没有真正意义上的结束线程 一般用于终止休眠的线程
​
    yield() :// 线程礼让 暂停自身的线程 让给优先级相同或更高的线程[存在失败几率]
​
    join()://线程插队 插队的线程一旦插队成功 先执行完插队的线程所有任务
​
    isDaemon() // 是否是守护线程 是 就返回true 不是返回false
​
    setDaemon(true)//设置线程为守护线程(主线程不可设置)【应在启动线程前设置】【守护线程是依赖非守护线程的存在/死亡 而存在/慢慢消亡】
​
     getState(): // 获取线程的状态
​

线程的优先级概念

   线程的优先级等级为 :1------10

   创建的线程默认优先级 都是 5

线程的三个常量 :

        MAX_Priority 默认优先级 10

        MIN_Priority 默认优先级 1

        NOME_Priority 默认优先级 5

线程终止基本说明:

        当线程完成任务后 会自动退出

        还可以通过使用变量来控制run方法退出的方式停止线程 ,【通知方式】

线程的礼让 和 插队:

yield: 线程礼让 让出cpu 让其他线程执行,但礼让的时间不确定 所有不一定成功

join:线程插队 插队的线程一旦插队成功 先执行完插队的线程所有任务

Thread.yield() :// 线程礼让 暂停自身的线程 让给优先级相同或更高的线程[存在失败几率]

Thread.join()://线程插队 插队的线程一旦插队成功 先执行完插队的线程所有任务
​

用户线程和守护线程

1.用户线程 也叫工作线程 当线程的任务Zhi性完或通知方式结束

2.守护线程 一般是为了工作线程服务的,当所有的用户线程结束后 守护线程自动结束

常见的守护线程:垃圾回收机制

isDaemon() // 是否是守护线程 是 就返回true 不是返回false
​
setDaemon(true)//设置线程为守护线程(主线程不可设置)【应在启动线程前设置】【守护线程是依赖非守护线程的存在/死亡 而存在/慢慢消亡】
​
  Thread t =   new THread();
    t.setDaemon(true)//设置 线程t 为守护线程

线程的生命周期

在JDK 中用Thread.State 枚举表示了线程的7种状态

  1. NEW

    尚未启动的线程处于此状态

  2. RUNNABLE

    在java虚拟机中执行的线程处于此状态

  3. BLOCKED

    被阻塞等待监视器锁定的线程处于此状态

  4. WAITING

    正在等待另一个线程执行特定动作的线程处于此状态

  5. TIMED_WAITING

    正在等待另一个线程执行动作达到指定时间的线程处于此状态

  6. TERMINATED

    已经退出的线程处于此状态

  7. RUNNABLE 细分的话 又分为2个状态 【执行状态】---【就绪状态】

 getState(): // 获取线程的状态

        Thread thread = new Thread(new A());
        System.out.println(thread.getState());//获取子线程当前状态
        thread.start();
        while (Thread.State.TERMINATED != thread.getState()) {//只要主线程 不等于子线程就一直循环                            // 输出子线程当前的状态
            System.out.println(thread.getName() + "   " + thread.getState());
            Thread.sleep(500);//主线程休息0.5秒
        }            //循环退出后 子线程的状态
        System.out.println(thread.getName()+"  状态 "+thread.getState());
​
    }
}
class A implements  Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("真好"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程转换图

 

 

线程的同步

Synchronized 线程同步

线程同步机制

  1. 在多线程编程 一些敏感数据不允许被多个线程同时访问,此时就是用同步访问技术 保证数据在任何时刻,最多有一 个线程访问,以保证数据的完整性

  2. 也可以这样理解 线程同步 即当有一个线程在对内存进行操作时,其他线程都不能对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作

同步具体方法

//1)
synchronized(对象){//得到对象的锁后 才能操作同步代码
        //需要被同步的代码
}
​
//2)
synchronized //还何以放在方法的声明中,表示整个方法--为同步方法
    public synchronized void m(String name){
    //需要被同步的代码
}

案例

 // 有三个窗口在卖票  代表三个线程    
Synchronized01 syn = new Synchronized01();
        new Thread(syn).start();//第一个
        new Thread(syn).start();//第二个
        new Thread(syn).start();//第三个
    }
}
// 实现接口方式     使用Synchronized 实现线程同步
class  Synchronized01 implements  Runnable {
    private static int num = 10;//让多个线程共享
    private boolean toop = true; //控制run的方法变量
​
public synchronized void Sell(){//使用同步方法 在同一时刻 只能有一个线程来执行run方法
    if (num <=0){
        System.out.println("票卖完了");
        Thread.interrupted();
              toop = false;
                return;
    }
    try {
        Thread.sleep(500);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("窗口"+Thread.currentThread().getName()+"卖出一张 还剩下"+(--num)+"张");
}
    @Override
    public  void run() {
        while (toop) {
           Sell();//同步方法
        }
    }
}

java互斥锁

基本介绍

  1. java语言中 引用了对象互斥锁的概念 来保证共享数据的完整性

  2. 每个对象都对应了一个可称为“互斥锁”的标记 这个标记用来保证在任一时刻,只能有一个线程访问该对象

  3. 关键字synchronized 来与对象的互斥锁联系 当某个对象用synchronized 修饰时 表明该对象在任一时刻只能有一个线程访问

  4. 同步的局限性: 导致程序的可执行效率要降低

  5. 同步方法(非静态的) 的锁可以是this 也可以是其他对象(要求是同一对象)

  6. 同步方(静态的)的锁为当前对象本身

class A{
    // 同步方法(非静态) 锁是加在 this对象上
    public synchronized void m1(){}
​
    //也可以加在方法内 同步代码块
    public  void m1(){
        synchronized(this){
            System.out.println("hello");
        }
    }
}
​
​
class A{ 
//同步方法 (静态的) 锁是加在类上 A.class 上  
public synchronized static void m1(){}
​
    //也可以加在方法内
     public  void m1(){
        synchronized(A.class){
            System.out.println("hello");
        }
    }
}

注意事项和细节

  1. 同步方法如果没有使用static修饰 默认锁对象为this

  2. 如果方法使用static 修饰 默认锁对象为类.class

  3. 实现的落地步骤

    需要先分析上锁的代码

    选择同步代码块 或 同步方法

    要求多个线程的锁对象为同一个

Lock锁

JDK 5.0 出现的新特性

Lock lock = new ReentrantLock();// 创建锁对象
​
//显示加锁
lock.lock();
​
//显示释放锁 
lock.unlock();
// 释放锁是必须要做的 所以要加在finally块中

线程的死锁

多个线程都占用了对方的锁资源 但不肯想让 导致了死锁 在编程是一定要避免的死锁的发生

诊断死锁

  1. 在黑窗口 cmd 中输入 tasklist 知道线程对应的pid号 (java/javaw)

    输入jstack 的pid值 查找哪里发生了死锁

  2. 在idea 下面 terminal 中输入 jstack 诊断

释放锁

下面操作会释放锁

  1. 当前线程的同步方法 同步代码块执行结束

    案例 :上厕所大号 完事后出来

  2. 当前线程在同步代码块 同步方法中遇到break return

    案例 没有正常的完事 经理 叫他出来修改bug 不得不出来

  3. 当前线程在同步代码块 同步方法中出现了未处理的Error 或 Exception 导致异常结束

    案例 没有正常的完事 发现忘带纸 不得不出来

  4. 当前线程在同步代码块 同步方法中执行了线程对象的wait方法 当前线程暂停 并释放锁

    案例 没有正常完事 觉得需要酝酿下 所以出来等会再进去

下面操作不会释放锁

  1. 线程执行同步代码块 或同步方法时 程序调用Thread.sleep(),Thread.yield()方法暂停当前线程的执行 不会释放锁

    案例 上厕所 太困了 咋坑位上眯了会

  2. 线程执行同步代码块时 其他线程调用了该线程的suspend()方法 将线程挂起 该线程不会释放锁

    提示 应尽量避免使用suspend 和 resume 来控制线程 方法 不推荐使用

线程池

线程池是一个容器

Executors 创建线程池

//创建一个默认线程池
static Executors.newCachedTheadPool();
​
//创建指定数量线程的线程池
 static Executors.newFixedThreadPool(int nThreads)

public static void main(String[] args) {
    // 创建默认线程池
        ExecutorService pool = Executors.newCachedThreadPool();
    // 向池中提交任务
        pool.submit(new my());
        pool.submit(new my());
​
        //关闭线程
//已提交的任务执行完后 不接受新任务
        pool.shutdown();
​
//试图停止正在执行的任务  不执行已提交并
// 处于等待状态的任务 将未执行的任务返回到list集合中
        List<Runnable> runnables = pool.shutdownNow();
​
    }
}
class my implements  Runnable{
    @Override
    public void run() {
        for (int i = 0; i <11; i++) {
            System.out.println(Thread.currentThread().getName()+"--------"+i);
        }
    }
}


关于Java多线程就分享到这了  如果有遗漏的 还请大家评论指出 

如果本篇文章帮到了你 也希望大家可以动动小手点赞加关注 支持下

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

皮皮虾_凡夫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值