Java线程详解

目录

线程相关的概念

线程的使用

继承Thread类创建线程

实现Runnable接口使用线程

线程常用的方法

线程的生命周期

 互斥锁


线程相关的概念

进程:"程表示编写的程序",那么进程就表示运行起来的程序,当程序运行起来的时候操作系统会为进程分配内存空间,开始占用设备的内存,比如我们打开QQ就是启动了一个进程。进程是一个动态的过程——产生、存在、消亡。

线程:线程由进程创建,线程是进程的一个实体。一个进程可以有多个线程,比如说我们打开迅雷——创建了一个进程,我们在迅雷中同时下载多个文件,就表示在执行多个线程。

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

多线程:同一时刻,执行多个线程。

并发:同一时刻,同一个CPU交替执行多个任务,造成一种“貌似同时”的错觉,注意并发并不是多个任务同时执行,只是CPU在交替执行多个任务的时候交替速度极快。简单来说就是单核CPU实现多任务就是并发。

并行:同一时刻,多个任务同时执行(只有多核CPU才能实现),注意多核CPU也能实现并行。

线程的使用

目前基础部分有如下几种方法

线程的使用调用java.lang包下面的Thread.start来开启一个线程。所有的线程类要么继承Thread并重写run方法。要么实现Runnable接口,并重写run方法

//Runnable源码
public interface Runnable {

    public abstract void run();
}

//在Thread类中的run方法
@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

继承Thread类创建线程

数猫猫案例:在控制台数十条猫猫,每数一条间隔一秒

public class TestCat {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.start();
    }
}
public class Cat extends Thread{
    int times = 0;
    @Override
    public void run() {
        while (true){
            System.out.println((++times)+"条猫猫");
            try {//直接调用sleep会报一个中断异常的错误
                Thread.sleep(1000);//表示休眠线程的意思(单位是毫秒,1000毫秒 = 1秒)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(times == 10){
                break;
            }
        }
    }

//执行结果

我们来分析上述线程的创建机制:当代码运行时——启动了一个进程;调用了main方法——开启一个了主线程;在main中创建了Cat线程对象,当调用strat方法时开启了子线程——在主线程中开启了子线程。分析完毕(注意在执行子线程的时候并不会阻塞主线程后面的代码,比如作如下修改)

public class TestCat {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.start();
        for (int i = 0; i < 10; i++) {
            System.out.println((i+1)+"条狗狗");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Cat extends Thread{
    int times = 0;
    @Override
    public void run() {
        while (true){
            System.out.println((++times)+"条猫猫");
            try {//直接调用sleep会报一个中断异常的错误
                Thread.sleep(1000);//表示休眠线程的意思(单位是毫秒,1000毫秒 = 1秒)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(times == 10){
                break;
            }
        }
    }

//执行结果

那么从上面来看我们可以看到在主线程中执行子线程后并不会阻塞主线程后面的代码,他会和子线程交替执行(并发)。

疑问

1.为什么子线程中不能像主线程那样直接调用Thread类中的Sleep方法来使用,而需要继承Thread类?

如果你把Cat类中的继承Thread删掉,编译器并不会报错,但是在执行的时候会报错,因为主线程中在调用strat方法他就找不到run方法了。

2.为什么不直接用run方法,而是通过strat方法绕一圈来找到run方法?

如果你直接调用run方法,并不会报错也能执行,但是此时就只有一个主线程了,他不属于多线程了,不会交替再执行,他只是单纯的调用了方法,在Cat类中使用的线程还是主线程。数完全部的猫后才会数狗(会阻塞在run这里)。

这里面插入一句话:真正实现多线程的不是run方法,而是通过start方法调用start0方法,这个start0方法才是实现多线程的真正方法,他是一个本地方法,底层由c或者c++来实现的由JVM直接调用,我们无法调用。start0通过不同的机制找到run方法完成调用。

 所以记住这句话:真正实现多线程的是start0方法。

Java中main方法启动的是一个线程也是一个进程,一个java程序启动后它就是一个进程,进程相当于一个空盒,它只提供资源装载的空间,具体的调度并不是由进程来完成的,而是由线程来完成的。一个java程序从main开始之后,进程启动,为整个程序提供各种资源,而此时将启动一个线程,这个线程就是主线程,它将调度资源,进行具体的操作。Thread、Runnable的开启的线程是主线程下的子线程,是父子关系,此时该java程序即为多线程的,这些线程共同进行资源的调度和执行。

实现Runnable接口使用线程

    public static void main(String[] args) {
        Dog dog = new Dog();
        Thread thread = new Thread(dog);
        thread.start();

    }
}
class Dog implements Runnable{

    @Override
    public void run() {
        int count = 0;
        while (true){
            System.out.println("小狗汪汪叫"+(++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 9){
                break;
            }
        }
    }
}

 //在Thread类中有一个属性Runnable target = null;还有一个构造器Thread(Runnable target)
因为我这个Dog实现了Runnable,所以我可以传进去。然后在Thread(Runnable target)这个构造器中调用了start0方法,在start0会调用Thread中的run方法,在Thread中的run方法中这样一个判断
if(target != null){target.run();}然后你要明白此时你这个target就是你传进来的Dog,所以这个判断target是不为空的,所以会执行target.run();虽然在Thread中也有run方法,但是在调用run方法的时候会动态绑定到dog中的run方法。(这是一个设计模式叫静态设计模式)。

当然他的线程执行机制还是和前面一样的只不过更绕了一圈。

线程常用的方法

 礼让不一定会成功,但是插队肯定会成功。礼让和插队的使用方式都是在一个线程num1中去使用num2.yield();或者num2.join();的方式。礼让成功与否是根据cup的内核来决定的,如果内核繁忙则礼让成功率高。

工作线程和守护线程

  1. 工作线程(用户线程):该线程要么执行完毕结束线程,要么以通知的方式结束线程。
  2. 守护线程:一般是用来服务工作线程的,当所有的工作线程结束后,守护线程自动结束(常见的守护线程——垃圾回收机制),守护线程的设置方,线程name.setDaemon(true);工作线程是不需要设置啥的就是普通的线程

线程的生命周期

 互斥锁

非静态同步方法解读:
1.在非静态的方法中用synchronized 修饰后即为非静态同步方法。
  比如这样:public synchronized static void m1() {} 。
  此时互斥锁是加在当前对象本身,也就是this,                  
2. 如果你觉得同步方法效率太低,那么可以在方法中添加同步代码块中,比如
    public static void m1() {
        synchronized(this){代码块}
    }
这样做的话互斥锁仍然是加在当前对象——this对象。
3.互斥锁可以加在当前对象this,也可以加在其他对象但是必须是同一个对象,
  这句话很绕,互斥锁是一个对象,你先把互斥锁当成一个厕所的门,门后面只有一个坑位。那只有一个坑位的情况下你就得保证你的门必须为同一扇门,否则的话就造成了一个坑位多扇门,那这样的话大家都能够根据不同的门跑进那个坑位了,所以只要保证只有一扇门就可以了,不需要管他是什么样的门(随便一个对象都可以只要是同一个对象)
比如:
Window window = new Window();
new Thread(window).start();
new Thread(window).start();
class Window implements Runnable{
    Object object = new Object();
   @Override
  1. public synchronized void run() {}
  2. public void run() {synchronized(this){}}
  3.public void run() {synchronized(objcet){}}
  //1和2都是把互斥锁加在window对象中,而3是把互斥锁加在Object中。
    那你怎么理解“也可以加在其他对象但是必须是同一个对象”?因为前面的两个线程都是
    对window对象进行操作,那么我把互斥锁加在this肯定就是同一个window。
    我把互斥锁可以加在object
    是因为我都是在同一个我这个object是在window下面的,所以我的两个线程操作的
    也是同一个obcet对象,没毛病啊!!!我不管加在哪个对象都只是为了拿到一扇厕所门,
    千万别以为我把互斥锁加在哪个对象他就是把哪个对象同步。
    synchronized(this){}和synchronized(objcet){}效果是一样的,因为我的线程操作的
    是同一个对象window,那么我拿到的都是同一个的门,都是用来同步代码块的作用


}

 //这两个方法都是在SellTicket03.class这个类下面的

死锁

何种情况下会释放锁

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

new麻油叶先生

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

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

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

打赏作者

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

抵扣说明:

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

余额充值