Java线程基本介绍

近期在学习Java线程,也是从基础开始再重新回顾一遍,顺便做个笔记整理下思路,个人也是看网上视频加上个人理解,若有错误还请广大网友指正,共同进步。

1 线程的相关概念

1.1 进程和线程

1 进程:进程就是运行中的程序,启动一个线程后,操作系统就是为该进程分配一个内存空间。如图:

2 线程:线程是由进程创建的,是进程的一个实体。一个进程可以拥有多个线程。可以理解为一个进程包含一个或者多个线程。

1.2 并发与并行

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

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

帮助记忆理解:

并发的“发”,可理解为发生,同一时刻有多件事情要发生,你一个人(单核CPU)要全部将其解决。

并行的“行”,可以理解为共同行走,你和你的好朋友一起散步,两个人是同时往前走的。

2 线程运行的过程(以Java为列)

线程的实现有两种方式,一种是继承Thread线程类的方式,如下:

//继承Thread线程类
class Cat extend Thread{/*******/}

public static void main(String[] args) {
   Cat cat=new Cat();
   cat.start();  //启动一个子线程
}

另一种是类B实现Runnable的接口,如下:

//实现Runnable接口
class Dog implements Runnable{/********/}

public static void main(String[] args) {
   Dog dog = new Dog();
   Thread thread = new Thread(dog);//创建了Thread对象,把 dog对象(实现Runnable),放入Thread
   dog.start();  //启动一个子线程
}

下面将根据这两种方法具体介绍。

2.1 继承Thread线程实现线程

当程序执行时,之后开启main方法这个主线程,则进行一个进程的执行,若有子线程,子线程A.start()开启一个子线程,当主线程和子线程全部执行完毕,进程结束。注意:只有所有的线程结束,进程才会结束。这样说可能很难理解,上代码:

public class Thread01 {

    public static void main(String[] args) throws InterruptedException{
        Cat cat=new Cat(); //创建Cat对象,可以当作线程使用
        cat.start(); //启动子线程
        //说明:当main线程启动一个子线程 Thread-0,主线程不会阻塞,会继续执行,这时 主线程和子线程会交替执行
        System.out.println("主线程继续执行"+Thread.currentThread().getName());
        for (int i = 0; i <10 ; i++) {
            System.out.println("主线程 i="+i);
            Thread.sleep(1000);
        }
    }
}

/*
1 当一个类继承了Thread类,该类就可以当作线程类使用
2 我们会重写run方法,写上自己的业务代码
3 Thread类 内部也是实现了 Runnable 接口的run方法
*/
class Cat extends Thread{

    int times=0;
    @Override
    public void run() {
        while (true){
            //该线程每隔1秒种,在控制台输出 “喵喵,我是小喵喵”
            System.out.println("喵喵,我是小喵喵 "+(++times)+" 线程名="+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(times==80){
                break; //当times到80,退出while,这时线程也就退出。。。
            }
        }
    }
}

上述的代码中,其中main()方法,即为一个主线程。cat.start(),即启动了一个子线程,在主线程和子线程同时运行时,无论时主线程先结束还是子线程先结束,进程都不会停止。全部线程结束时,整个进程才会停止。上述代码可以直接复制运行,可以实际的观察一下。

2.2 实现Runnable接口实现线程

class Dog implements Runnable { //通过实现Runnable接口,实现线程
    int count = 0;
    @Override
    public void run() { //普通方法
        while (true) {
            System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);  //休眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10) {
                break;
            }
        }
    }
}

public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Thread thread = new Thread(dog); //创建了Thread对象,把 dog对象(实现Runnable),放入Thread
        thread.start(); //启动一个子线程
    }
}

代码之后,看一个流程图:

1 Thread thread =newThread(dog); 在Thread类的具体内部实现中会发现也是实现了Runnable的接口:

2 将dog对象放进Thread中,之后通过thread.start()的方式启动线程。

2.3 继承Thread VS 实现Runnable的区别

1 从Java设计来看,通过继承Thread或者实现Runnable接口创建线程本质上没有区别,从jdk帮助文档上看Threrad类本身就是实现了Runnable接口。

2 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable。

3 如何终止线程

3.1 任务执行结束

当任务执行结束,线程会终止。

3.2 线程通知方式

还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式。上代码:

class T extends Thread{
    int count=0;
    //设置一个控制变量
    private boolean loop=true;
    @Override
    public void run() {
        while (loop){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T 运行中... "+(++count));
        }
    }
    //通过set的方法来设置线程是否执行
    public void setLoop(boolean loop){
        this.loop=loop;
    }
}

public class ThreadExit {
    public static void main(String[] args) throws InterruptedException {
        T t1 = new T();
        t1.start();

        //如果希望main线程去t1 线程终止,必须可以修改loop
        //让t1 退出run方法,从而终止 t1线程-> 通知方式
        Thread.sleep(10000); //休眠10秒,在退出
        t1.setLoop(false);
    }
}

3.3 线程中断

1 线程中断是一种协作机制,线程可以通过这种机制来通知另一个线程,告诉它在合适的或者可能得情况下停止当前工作。

2 每个线程都有一个boolean类型的中断变量,并且在Thread类中有三个方法,分别是。

public class Thread{
    public void interrupt(){...}
    public boolean isInterrupted(){...}
    public static boolean interrupted(){...}
    ...
}

1 interrupt()方法 可以被其他线程进行调用,通过将中断变量设置为true来中断线程。

2 isInterrupted() 方法返回线程的中断变量状态,但不会改变其状态值。

3 interrupted() 方法会先清除中断状态,即将中断变量设为false,并返回之前的中断状态

3.3.1 如何理解中断机制

中断操作(即调用线程的interrupte()方法)并不是会停止一个正在运行的线程,它只是把线程里边的中断变量设置为true,向这个线程发出一个中断请求,而线程怎么反应,则是线程自己的事情。它可以完全不理会中断,照常执行。

3.3.2 如何恢复中断

并不是把线程从BLOCKED或WAITING等状态中解救出来(本来中断也不是这么个意思),而是把线程的中断状态变量重新设置为True。

4 线程常用的方法

4.1 常见方法

1 setName //设置线程名称,使之与参数name相同
2 getName //返回线程的名称
3 start //是该线程开始执行;java虚拟机底层调用该线程的start0()方法
4 run //调用该线程的run方法
5 setPriority //更改线程的优先级
6 getPriority //获取线程的优先级
7 sleep //在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
8 interrupt //中断线程,不是停止,一般用于正在休眠的线程
线程优先级:MAX_PRIORITY MIN_PRIORITY NORM_PRIORITY

4.2 线程礼让

1 yield线程礼让,让出cpu,让其他线程执行,但是礼让的时间不确定,所以也不一定礼让成功。

4.2 线程插队

join 线程的插队,插队的线程一旦成功,则肯定先执行插入的线程所有任务。

class T2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(1000); //休眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程(大哥)吃了 "+i+" 个包子");
        }
    }
}

public static void main(String[] args) throws InterruptedException {
        T2 t2=new T2();
        t2.start();
        for(int i=0;i<20;i++){
            Thread.sleep(1000);
            System.out.println("主线程(小弟)吃了 "+i+" 个包子");
            if (i==5){
                System.out.println("主线程(小弟)让(大哥)先吃");
                // join 线程插队 一定会成功
                // t2.join();
                // 礼让 不一定会成功
                Thread.yield();
                System.out.println("子线程(老大)吃完了,我们接着吃");
            }
        }
}

4.3 用户线程和守护线程

1 用户线程:也叫工作线程,当线程的任务执行完或以通知的方式结束。

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

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

5 线程的生命周期

在Java中,线程的生命周期有新建状态,就绪状态,运行状态,阻塞状态和终止状态等五个状态,如下图所示:

5.1 新建状态

当一个线程处于创建状态时,系统不会为他分配资源。

Thread myThread=new Thread()

5.2 就绪状态

Java通过start()方法启动处于新建状态的线程对象,使其进入就绪状态。处于就绪状态的线程已经具备了运行的条件,将进入线程队列等待系统为其分配CPU,一旦获得了CPU,线程进入运行状态,并调用自己的run方法。

myThread.start();

5.3 运行状态

处于就绪状态的线程被调度并获得CPU的处理后进入到运行状态,每一个Thread类及其子类的对象都有一个run()方法,当线程被调度的时候,它将自动调用本对象的run()方法。

5.4 阻塞状态

处于运行状态的线程,在某些情况下会暂时终止运行,进入阻塞状态。阻塞状态下的线程不能进入就绪的队列,只有当引起阻塞的原因消除时,线程便转入就绪状态,重新到就绪队列中排队等待,当获取CPU的资源时,从原来终止位置开始继续执行。

1 调用sleep()方法 使线程进入休眠状态

2 调用suspend()方法使线程进入挂起的状态(建议不再使用)

3 调用wait()方法,进入等待状态

4 等待输入输出操作

5.5 终止状态

终止状态是线程生命周期的最后一个阶段。线程终止的原因主要有两个:

1 线程完成全部的工作,工作结束运行。

2 线程被强制终止运行。如通过 stop()或destroy()方法终止一个线程。

6 线程同步

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

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

互斥锁

1 Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。

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

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

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

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

6 同步方法(静态的)的锁为当前类本身。

线程的死锁

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

释放锁

9.1 会释放锁的情况

1 当前线程的同步方法、同步代码块执行结束。例如上完厕所,完事出来。

2 当前线程在同步代码块、同步方法中遇到break、return。例如没有正常的完事,经理叫他修改bug,不得已出来。

3 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。例如没有正常的完事,突然地震了,急忙跑出来。

4 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停并释放锁。例如一直蹲着,突然没了感觉,一直拉不出来,出来等会再进去。

9.2 不会释放锁的情况

1 当前线程在同步代码块、同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁。上厕所,太困了,在坑位上迷了一会。

2 当前线程在同步代码块、同步方法时,其他线程调用了该线程的suspend()方法将线程挂起,线程不会释放锁。应尽量避免使用suspend

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值