Java笔记(18)-完整的线程基础介绍,看完就基本了解了什么是线程

1. 基础介绍

线程有四种创建方式,但是我只记录两种,还有的创建方式是通过实现Callable接口,并且重写其中的call方法(有一点要注意,就是只有线程执行完毕才能收到返回值);

  • 程序:是为了完成某种任务而使用某一个编程语言编写的一组指令的集合;(可以理解成代码)
  • 进程:是指程序的执行过程,或是正在运行的程序,属于一个动态的过程;
  • 线程:由进程进一步细化,是一个程序内部的一条执行途径(比如在Java中一个main函数其实就是一个线程,它是从main开始执行);
  • 线程分为守护线程和用户线程;守护线程(如垃圾回收机制的线程)用来服务用户线程;
  • 多线程:指的就是一个进程同时有多个线程并行执行;
  • 并行:多个CPU执行多个任务;
  • 并发:一个CPU执行多个任务;(详细的看一看操作系统,通过时间片完成)

注意:一个简单的Java.exe应用程序,运行的话,其中至少有三个线程;一个main(主线程),一个gc()垃圾回收机制(垃圾回收线程,它的垃圾回收是自动的),还有一个异常处理线程;

2.线程的创建和使用

  1. 第一种创建线程的方法
//自己创建一个类继承Thread类;然后就重写其中的run方法;run方法中写你要执行的动作;
class testThread extends Thread {
    //    重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("线程:"+Thread.currentThread().getName()+":" + i);
        }

    }
}

public class ThreadCreate {
    public static void main(String[] args) {
//        创建一个对象调用start方法,不过这个方法是Thread中的;
        testThread t = new testThread();
        t.start();
        for (int j = 0; j < 1000; j++) {
            System.out.println("主线程:"+Thread.currentThread().getName()+":" + j);
        }
    }
}

它的结果就证明了线程之间的运行是并发的,就是通过时间片来管理进行;不是一种顺序执行;
线程运行
Thread中的常用方法:

方法名方法介绍
start()启动当前线程,调用当前线程的run方法
run()run方法一般都会被重写,将需要执行的操作写入此方法
currentThread()该方法返回当前执行代码的线程;属于静态方法,无需对象调用;
getName()获得当前线程的名字
setName()设置当前线程的名字
yield()释放当前cpu中执行的线程,(简单来说就是用来给同级线程做轮换,当然有时不一定成功,因为可能还是会选中当时释放的线程)
join()该方法主要是用于在A线程中调用B线程,这是A线程就被阻塞了,只有当B线程运行完毕了,A线程才能继续;
sleep(time)这个方法可以使某个线程进入“睡眠状态”,就是让一个线程进行阻塞time毫秒(1000毫秒等于一秒),
isAlive()该方法返回一个boolean值,判断线程是否还存活
  • yeild()方法运行演示:(每次运行到i%2==1的时候就会释放子线程,但是有时候cpu的运行权又会被子线程重新抢回去 😃)
    在这里插入图片描述
  • join()方法运行演示:(在主线程中调用子线程,直到子线程运行完,才会重新调用主线程)
  • join方法
  1. 第二种创建线程的方法
    就是指写一个类实现Runnable接口,然后重写其中的run方法;需要执行的操作写在run方法中;
public class RunableText implements Runnable {
    //重写Runnable中的方法,
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
/*
           由于这个方法已经不是Thread类中的方法,而是Runnable
            接口中的方法,在Runnable中没有getName等方法,所以只能直接通过Thread调用;
            主要还是currentThread()是静态的返回一个Thread对象;
*/
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }


    public static void main(String[] args) {
//        创建一个实现Runnable接口的对象
        RunableText runableText = new RunableText();
//        创建一个Thread对象,然后就是放入到Thread的构造方法中生成一个线程;
        Thread thread = new Thread(runableText);
//        由于通过Thread类已经生成了一个线程;,于是就可以调用Thread线程中的方法;
        thread.setName("子线程");
        thread.start();//开启线程
        Thread.currentThread().setName("主线程");
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

上述程序运行结果:

2. 线程的调度

注意: 在Java中线程的优先级并非绝对的,尝试之后发现并非优先级高就可以先全部执行,只能说优先级越高,那么执行的概率越大;

  1. 线程的各种优先级,如下图:(不同的优先级就会有不同的执行顺序;)
    第一个是最低的优先级,第二个是默认优先级,第三个是最高优先级;
    在Java中的线程优先级

  2. 定义优先级的方法

    方法名方法介绍
    getPriority()返回当前线程的优先级值,就像上图一样
    setPriority(int)设置当前线程的优先级值

    在这里插入图片描述
    上述代码执行的结果:优先级高不一定就先运行

  3. 线程通信,常用的方法(在Object类中)

    方法名方法介绍
    wait()让一个线程陷入等待状态,还能释放它当前所持的锁(暂时不清楚什么是锁)
    notify()唤醒正在等待的单个线程
    notifyAll()唤醒所有线程

3. 线程的生命周期

线程的生命周期,是主要记录在线程的类Thread.State,

按照上面的图片稍作解释;
第一个new状态,就是指线程刚刚创立,还没有调用start方法的时候;
第二个 RUNNABLE,则是指线程目前处于可运行状态,在等待需要的资源,就像是操作系统中的就绪;
第三个 BLOCKED,这个不是很理解,等继续学习之后再看看吧,但是在网上看来一下,大概就是类似于阻塞;
第四个 WAITING,这个大概就是理解为使用了wait()方法进入等待状态之后;
第五个TIMED_WAITING,是指使用wait(long)方法,进入等待状态后;
TERMINATED;最后一个就是线程已经终结;生命到了尽头;

4. 线程的同步

有时候,比如卖票的时候,如果只是使用上面的基础线程肯定是不能达到要求的,会出现重票,甚至是负票的现象;
如下面的问题程序:

public class RunTicket implements Runnable {
    private static int ticket = 100;

    @Override
    public void run() {
        while (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + ":" + ticket);
            ticket--;
        }
    }

    public static void main(String[] args) {
        RunTicket run = new RunTicket();
        Thread thread1 = new Thread(run);
        Thread thread2 = new Thread(run);
        Thread thread3 = new Thread(run);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

运行结果:
在这里插入图片描述
那么为什么出票会出现重复?
首先可以知道线程的运行顺序是不规则的,你不能知道cpu并发线程中的哪些线程会运行,哪些线程会停止,当第一个线程对象的ticket(ticket=100)没有运行ticket--时,第二个线程就已经取到了ticket的值(这时ticket=100),这时就会发生所谓的重票现象,因为他们共用一个对象run,所以也就共用一个ticket;

解决办法:
  1. 同步代码块,使用synchronized对需要同步的代码进行修饰;在这里我们可以直接同步那些使用了ticket的代码,意思就是只有一个对线程对象可以使用,这就是使用同步监视器(或称锁)的方法进行同步;
public class RunTicket implements Runnable {
    private int ticket = 100;
    //创建一个公用的对象,当锁
    Kmdog kmdog = new Kmdog();

    @Override
    public void run() {
        while (true) {
        //当然这里就是使用锁的地方,锁也不一定非要这样写,有时候可以用this,考虑使用当前类来做锁;
            synchronized (kmdog) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + ":" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        RunTicket run = new RunTicket();

        Thread thread1 = new Thread(run);
        Thread thread2 = new Thread(run);
        Thread thread3 = new Thread(run);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class Kmdog {
}

最重要的是要使用公共的锁;
运行结果:

2. 通过同步方法;也可以达到上述的要求;详细请百度,不做赘述;

杂记,现学即现记(以后把所有东学西记的东西全部总结起来)

百度的时候无意中看到,在子类的构造方法中可以通过super调用父类的普通方法,尝试了一下还真的可以;又get到了一个新东西;

  1. 父类中的方法要是没有throws异常,那么子类就无法直接throws异常;
  2. 为什么static不能修饰局部变量,因为它修饰的是全局变量;
  3. 线程组:在java.lang.ThreadGroup类是用来定义一个线程组的;一个管理线程的类;
  4. 每个线程都拥有自己独立的栈和程序计数器;多个线程共享一个进程中的方法区;
  5. 接口与接口之间必须要通过extends继承,而不能通过implements实现;只有类才能实现接口;
    我发现其实所有继承了Runnable接口的B接口,都可以通过实现B,来创建线程;比如(RunnableFuture);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

神秘的天

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

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

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

打赏作者

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

抵扣说明:

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

余额充值