前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Java线程

Java线程

作者头像
用户11097514
发布2024-05-30 21:28:02
发布2024-05-30 21:28:02
10000
代码可运行
举报
文章被收录于专栏:技术分享技术分享
运行总次数:0
代码可运行

进程线程

程序

  • 就是为了完成特定的任务,用某种语言编写的一组指令的集合。简单的说就是我们写的代码

进程

  • 进程是指运行中的程序,比如QQ,就启动了一个进程,操作系统就会为该进程分配内存空间当我们使用其他程序的时候,就又启动了一个进程,操作系统就会为其他进程分配新的内存空间
  • 进程是程序的一次执行过程,或者是正在运行的一个程序。
  • 是一个动态的过程,有他自身的产生、存在和消亡的过程

下面就是几个进程

线程

  • 线程是由进程创建的,是进程的一个实体
  • 一个进程可以拥有多个线程,如下图【同一个应用迅雷 下载多个内容,就产生多个线程】
多线程:

​ 同一时刻,可以执行多个进程,比如上面的迅雷同时下载多个文件

单线程:

同一时刻,只允许执行一个线程

并发 :

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

看上去他们同时进行,但是在任意时间段内,他们都是相互交错执行的

比如 : ‘人的大脑一边说话,一边写字 ’

并行 :

同一时刻, 多个任务同时执行。多核cpu可以实现并行

线程使用的两种方式

  • 当一个类继承Thread类, 那么该类就可以当成一个线程

线程常用的方法

两种方式的区别

  • 从java的设计来看, 通过继承Thread或者实现Runnable接口来创建线程本质上是没有区别的,他们都是通过start方法来调用start0 ,然后实现了多线程
  • 从jdk帮助文档来看, Thread类本身就实现了Runnable接口
  • 实现Runnable接口方式更加适合多线程共享一个资源的情况,并且避免了单继承的限制

一、 继承Thread创建线程

  • 案例一: 开启一个线程,该线程每隔1秒,在控制台输出一段话
代码语言:javascript
代码运行次数:0
复制
public class Thread01 {
    public static void main(String[] args) {
        //1. 创建一个cat对象,可以当作一个线程来使用
        Cat cat = new Cat();
        cat.start(); //启动线程 - > 最终会执行cat的run方法

    }
}
class Cat extends Thread{

    int times = 0;
    //todo 通过重写run方法,来实现自己的业务逻辑
    @Override
    public void run() { // 实现了Runnable接口的run方法

        while(true){
            System.out.println("执行一次" + "次数" +(++times));
            //让其休眠一秒
            try {
                Thread.sleep(1000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

结果就是每隔一秒输出一次

二、 实现Runnable

  • java是单继承的,在某些情况下一个类可能已经继承了某个父类,这是再用Thread类方法来创建线程显然是不可能了
  • java设计者们就想出了另一个创建线程的方法,就是通过实现Runnable接口来创建线程
实现案例:

写一个程序,该程序可以每隔1秒,在控制台输出一段话

代码语言:javascript
代码运行次数:0
复制
public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        //无法调用start方法 ,切记不能调用run方法,因为他只是一个普通的方法,没办法开启线程
        Thread thread = new Thread(dog);
        //这时 ,我们可以创建一个Thread对象, 然后调用start方法 ,然后就可以启动一个线程,调用了run方法
        thread.start();
    }

}
class Dog implements Runnable{//通过Runnable接口实现线程

    int times = 0;
    @Override
    public void run() {
        System.out.println("执行一次" + "次数" +(++times)+ "线程名:" + Thread.currentThread().getName());
        //让其休眠一秒
        try {
            Thread.sleep(3000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

这里使用的是一个设计模式【代理模式】

多线程机制

当我们执行上述程序是,它的流程是这样的

主线程的执行

代码语言:javascript
代码运行次数:0
复制
public static void main(String[] args) throws Exception{
    int count = 0;
    //1. 创建一个cat对象,可以当作一个线程来使用
    Cat cat = new Cat();
    cat.start();
    //当main方法执行一个线程 之后 ,主线程不会阻塞 ,会继续执行
   while (true){
       System.out.println("主线程 执行次数: " + (++count) + "线程名:" + Thread.currentThread().getName());
       Thread.sleep(1000);
   }
}

当main方法执行一个线程 之后 ,主线程不会阻塞 ,会继续执行

最后用检测工具发现,虽然主线程的执行完一定次数之后,结束了但是它的子线程Thread-0 没有执行完 ,那么程序就不会结束

为什么这里要调cat.start()方法?

答 :因为run方法就是一个普通的方法,并无法启动一个真正的线程就会产生阻塞。

  • 源码分析start方法
代码语言:javascript
代码运行次数:0
复制
 public synchronized void start() {
     if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
 }


//本地方法, 是由jvm调用,底层是由c/c++实现,无法调用
private native void start0();

换言之, 真正实现多线程的效果是start0();方法

多个子线程案例

代码语言:javascript
代码运行次数:0
复制
public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Catt catt = new Catt();
        //无法调用start方法 ,切记不能调用run方法,因为他只是一个普通的方法,没办法开启线程
        Thread thread1 = new Thread(dog);
        Thread thread2 = new Thread(catt);
        //这时 ,我们可以创建一个Thread对象, 然后调用start方法 ,然后就可以启动一个线程,调用了run方法
        thread1.start();
        thread2.start();
    }

}
class Dog implements Runnable{//通过Runnable接口实现线程

    int times = 0;
    @Override
    public void run() {
       while (true){
           System.out.println("执行一次" + "次数" +(++times)+ "  线程1名:" + Thread.currentThread().getName());
           //让其休眠一秒
           try {
               Thread.sleep(1000);

           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
    }
}
class Catt implements Runnable{//通过Runnable接口实现线程

    int times = 0;
    @Override
    public void run() {
       while (true){
           System.out.println("执行一次" + "次数" +(++times)+ "  线程2 名:" + Thread.currentThread().getName());
           //让其休眠一秒
           try {
               Thread.sleep(1000);

           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
    }
}

运行结果 ,他们交叉执行

多线程售票问题

代码语言:javascript
代码运行次数:0
复制
package ticket;

public class SellTicket {

    public static void main(String[] args) {
        SellTicket01 ticket01 = new SellTicket01();
        SellTicket01 ticket02 = new SellTicket01();
        SellTicket01 ticket03 = new SellTicket01();

        ticket01.start();
        ticket02.start();
        ticket03.start();

    }


}
class  SellTicket01 extends Thread{
    static int ticketNum =  100;

    @Override
    public void run() {
        while (true){
            if (ticketNum <= 0 ){
                System.out.println("售票结束");
                break;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 :" + Thread.currentThread().getName() + "售出一张票" +  "  剩余票数为: "+  (--ticketNum));
        }
    }
}

运行结果 :

出现超卖问题

原因:

也就是在票数为 1 的时候, 他们三者同时进行售卖 ,导致三个线程同时进行,然后就会超卖两张

解决方案:

  • 通知线程退出
  • 线程中断

通知线程退出

代码语言:javascript
代码运行次数:0
复制
public class Thread01 {
    public static void main(String[] args) throws Exception{
        int count = 0;
        //1. 创建一个cat对象,可以当作一个线程来使用
        Cat cat = new Cat();
        cat.start();
        //休息10 秒 让其退出
        Thread.sleep(10000);
        cat.loop = false;
        //当main方法执行一个线程 之后 ,主线程不会阻塞 ,会继续执行
    }
}
class Cat extends Thread{

    int times = 0;

    boolean loop = true;
    //todo 通过重写run方法,来实现自己的业务逻辑
    @Override
    public void run() { // 实现了Runnable接口的run方法

        while(loop){
            System.out.println("执行一次" + "次数" +(++times)+ "线程名:" + Thread.currentThread().getName());
            //让其休眠一秒
            try {
                Thread.sleep(1000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在main方法中控制变量,让另一个线程退出

运行结果:

线程中断

就是让正在休眠的线程停止休眠, 继续工作的意思

代码语言:javascript
代码运行次数:0
复制
package Thread;

public class Thread01 {
    public static void main(String[] args) throws Exception{
        int count = 0;
        //1. 创建一个cat对象,可以当作一个线程来使用
        Cat cat = new Cat();
        cat.start();
        //休息10 秒 让其退出
        Thread.sleep(1000);
//        cat.loop = false;
        System.out.println("线程停止休眠 ,继续执行" );
        cat.interrupt(); //中断子线程的休眠
        //Thread.sleep(5000);


        //当main方法执行一个线程 之后 ,主线程不会阻塞 ,会继续执行
    }
}
class Cat extends Thread{

    int times = 0;

    boolean loop = true;
    //todo 通过重写run方法,来实现自己的业务逻辑
    @Override
    public void run() { // 实现了Runnable接口的run方法

        while(loop){
            System.out.println("执行一次" + "次数" +(++times)+ "线程名:" + Thread.currentThread().getName());
            //让其休眠一秒
            try {
                System.out.println("休眠中--");
                Thread.sleep(10000);

            } catch (InterruptedException e) {

                System.out.println(Thread.currentThread().getName() + "被interrupt了");
            }
        }
    }
}

线程插队

  • yield :将线程让出来,让其他线程执行
  • join : 线程的插队。 插队线程一旦插队成功 ,则肯定先执行完插入的线程所有的任务

用户、守护 线程

  • 用户线程: 也叫工作线程, 当线程的任务执行完成或者通知方式结束
  • 守护线程: 一般是为工作线程服务的,当所有的用户线程结束, 守护线程自动结束
  • 常见的守护线程 : 垃圾回收机制

如何将一个线程设置为守护线程?

代码语言:javascript
代码运行次数:0
复制
public class Thread02 {
    public static void main(String[] args) throws Exception{
        Dog dog = new Dog();
        Catt catt = new Catt();
        Thread thread1 = new Thread(dog);
        //Runnable方式实现 设置为守护线程 ,然后在启动
        thread1.setDaemon(true);    
        thread1.start();
        //Thread 实现 设置为守护线程 ,然后在启动
        catt.setDaemon(true); 
        catt.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("test... ");
            Thread.sleep(1000);
        }
    }

}
class Dog implements Runnable{//通过Runnable接口实现线程

    int times = 0;
    @Override
    public void run() {
       while (true){
           System.out.println("执行一次" + "次数" +(++times)+ "  线程1名:" + Thread.currentThread().getName());
           //让其休眠一秒
           try {
               Thread.sleep(1000);

           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
    }
}
class Catt extends Thread{//通过Runnable接口实现线程

    int times = 0;
    @Override
    public void run() {
       while (true){
           System.out.println("执行一次" + "次数" +(++times)+ "  线程2 名:" + Thread.currentThread().getName());
           //让其休眠一秒
           try {
               Thread.sleep(1000);

           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
    }
}

通过//Runnable方式实现 设置为守护线程 ,然后在启动 thread1.setDaemon(true); 的方式实现设置守护线程

注意顺序 ,先设置 再执行

线程的生命周期

线程的7大状态

jdk官方文档中可以看出有六个

但是从我们的线程生命周期转换图可以看出 我们将Runnable(可运行状态)细化为Ready(就绪) 和Running(真正运行)两个状态

线程生命周期转换图

案例

代码语言:javascript
代码运行次数:0
复制

public class Thread02 {
    public static void main(String[] args) throws Exception{
        Catt catt = new Catt();
        //Thread 实现 设置为守护线程 ,然后在启动
        System.out.println(catt.getName() + " 状态  " + catt.getState());
        catt.start();
        while(Thread.State.TERMINATED != catt.getState()){
            System.out.println(catt.getName() + " 状态  " + catt.getState());
            Thread.sleep(500);
        }
        System.out.println(catt.getName() + " 状态  " + catt.getState());
    }

}

class Catt extends Thread{//通过Runnable接口实现线程
    int times = 0;
    @Override
    public void run() {
       while (times != 5){
           System.out.println("执行一次" + "次数" +(++times)+ "  线程2 名:" + Thread.currentThread().getName());
           //让其休眠一秒
           try {
               Thread.sleep(1000);

           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
    }
}

运行结果:

线程同步机制—-Synchronized

  • 在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性
  • 也可以这样理解:线程同步,即当有一个线程在对内存进行操作试试,其他的线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作

Synchronized

同步具体方法

  • 同步代码块
代码语言:javascript
代码运行次数:0
复制
//得到对象的锁,才能操作同步代码
synchronized(对象){
    //需要被同步的代码
}
  • 同步方法
代码语言:javascript
代码运行次数:0
复制
public synchronized void m(String name){
    //需要被同步的代码
}

具体理解 ,不管有多少线程来,在某一时刻,只能有一个线程访问

分析同步原理

假设 t1 抢到锁之后就开始执行代码 ,当执行完之后就会把锁放回去 ,然后t1、t2、t3 三个继续抢这个锁

互斥锁

  • java语言中引入了互斥锁的概念 ,来保证共享数据操作的完整性
  • 每个对象都对应一个可以称为“互斥锁”的标记, 这个标记用来保证在任意时刻,只能有一个线程访问
  • 同步的局限性: 导致程序的执行效率降低
  • 同步方法(静态的) 的锁为当前类本身
  • 同步方法(非静态的) 的锁可以是this ,也可以是其他对象 (必须是同一对象,可以是不同引用)

**注意事项: **

线程死锁

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

上述情况导致o1、o2都无法释放,导致死锁

必须避免这种两个锁互相锁

释放锁

  • 当前线程的同步方法、同步代码块执行结束
  • 当前线程在同步代码块、同步方法中遇到break、return
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或者Exception,导致程序异常结束
  • 当前线程在同步代码块、同步方法中执行了wait()方法,当前线程暂停,并且释放锁

下面的操作不会释放锁

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-04-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 进程线程
    • 程序
    • 进程
    • 线程
  • 线程使用的两种方式
    • 线程常用的方法
    • 两种方式的区别
    • 一、 继承Thread创建线程
    • 二、 实现Runnable
    • 多线程机制
      • 为什么这里要调cat.start()方法?
  • 多个子线程案例
  • 多线程售票问题
  • 通知线程退出
  • 线程中断
  • 线程插队
  • 用户、守护 线程
    • 如何将一个线程设置为守护线程?
  • 线程的生命周期
    • 线程的7大状态
      • 案例
  • 线程同步机制—-Synchronized
    • Synchronized
    • 分析同步原理
  • 互斥锁
  • 线程死锁
  • 释放锁
    • 下面的操作不会释放锁
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档