多线程-创建

 

1.进程

        进程指的是运行中的程序,一个运行中的app就是一个进程。

2.线程

        线程由进程创建,是进程的一个实体。

        一个进程可以拥有多个线程。例如迅雷,下载多个文件。

        这里所说的线程指程序执行过程中的一个线程实体。JVM 允许一个应用并发执行多个线程。 Hotspot JVM 中的 Java 线程与原生操作系统线程有直接的映射关系。当线程本地存储、缓 冲区分配、同步对象、栈、程序计数器等准备好以后,就会创建一个操作系统原生线程。 Java 线程结束,原生线程随之被回收。操作系统负责调度所有线程,并把它们分配到任何可 用的 CPU 上。当原生线程初始化完毕,就会调用 Java 线程的 run() 方法。当线程结束时,会释放原生线程和 Java 线程的所有资源。

Hotspot是JVM的实现方式  JVM算是Hotspot的规范。

Hotspot JVM 后台运行的系统线程主要有下面几个:

虚拟机线程 (VM thread) 这个线程等待 JVM 到达安全点操作出现。这些操作必须要在独立的线程里执行,因为当 堆修改无法进行时,线程都需要 JVM 位于安全点。这些操作的类型有:stop-theworld 垃圾回收、线程栈 dump、线程暂停、线程偏向锁(biased locking)解除。

周期性任务线程 这线程负责定时器事件(也就是中断),用来调度周期性操作的执行。

GC 线程 这些线程支持 JVM 中不同的垃圾回收活动。

编译器线程 这些线程在运行时将字节码动态编译成本地平台相关的机器码。 信号分发线程 这个线程接收发送到 JVM 的信号并调用适当的 JVM 方法处理。

单线程        

多线程        一个程序可以开启多个线程

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

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

1.创建线程(继承Thread)

单线程(继承Thread)demo:
创建线程与线程的一些注意事项:

对于进程的运行情况,可以在Terminal中,使用jconsole监视进程的情况。

package Thread_;

/**
 * @program:多线程和IO
 * @descripton:
 * @author:ZhengCheng
 * @create:2021/9/14-20:40
 **/
public class Thread01 {
    public static void main(String[] args) {
//            new a().run(10);
        a d= new a();//提问?为什么调用的是.start,会用run方法
        d.start();
        //当main线程启动子线程Thread0时,主线程是会继续执行的
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i+Thread.currentThread().getName());
        }
    }

}
//当一个类继承了Thread类,就可以当做一个线程使用。
//需要重写run , run是Thread继承的Runnable接口的方法
class a extends Thread{

    @Override
    public void run(){
        for (int i = 0; i < 10; i++) {
            System.out.println("miao   "+i+this);
            //miao   9Thread[Thread-0,5,main]
            //休眠
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

    }

//    public void run(int times){
//        for (int i = 0; i < times; i++) {
//            System.out.println("miao"+i);
//            //休眠
//            try {
//                Thread.sleep(100);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        }
//    }
}

在上述的demo中,可以看到,使用start可以直接调用override之后的run方法,具体原因????

其次,如果我们需要的是run方法,那为什么不在main方法中直接使用new nameThread().run呢?

注意:如果直接使用new nameThread().run,那么我们可以看成,此时是main线程调用了run方法,如果此时输出该线程的值,得到的结果是main。而并没有开启子线程。只有使用start才会开启子线程。

源码解析:

public synchronized void start(){
    start0();    
}
//start0() 是本地方法,由jvm调用,底层由c或者c++实现
//真正实现多线程的是start0 而非run

//native方法是本地方法
//当start调用了start0时,其实该线程并不会立马执行,只是将线程变成了可运行状态。
//具体什么时候执行,取决于CPU,又CPU同一调度。
private native void start0(){
    
}

       关于native关键字:Java当中native方法的详解_先苦后甜似淡非定的博客-优快云博客_native方法 

目前的大致理解:native 关键字是负责告知jvm,该方法由c或者cpp实现,需要从操作系统中找到我们所需要的方法。


所以native关键字的函数都是操作系统实现的,
java只能调用。


java是跨平台的语言,既然是跨了平台,所付出的代价就是牺牲一些对底层的控制,而java要实现对底层的控制,就需要一些其他语言的帮助,这个就是native的作用了
 


Java不是完美的,Java的不足除了体现在运行速度上要比传统的C++慢许多之外,Java无法直接访问到操作系统底层(如系统硬件等),为此Java使用native方法来扩展Java程序的功能。
  可以将native方法比作Java程序同C程序的接口,其实现步骤:
  1、在Java中声明native()方法,然后编译;
  2、用javah产生一个.h文件;
  3、写一个.cpp文件实现native导出方法,其中需要包含第二步产生的.h文件(注意其中又包含了JDK带的jni.h文件);
  4、将第三步的.cpp文件编译成动态链接库文件;
  5、在Java中用System.loadLibrary()方法加载第四步产生的动态链接库文件,这个native()方法就可以在Java中被访问了。


JAVA本地方法适用的情况 
1.为了使用底层的主机平台的某个特性,而这个特性不能通过JAVA API访问

2.为了访问一个老的系统或者使用一个已有的库,而这个系统或这个库不是用JAVA编写的

3.为了加快程序的性能,而将一段时间敏感的代码作为本地方法实现。   -- 转载

 

线程的实现(实现Runnable接口)

当一个类继承了别的类时,无法再继承Thread,那么其实现Runnable接口,同样可以开启线程。

package Thread_;

/**
 * @program:多线程和IO
 * @descripton:
 * @author:ZhengCheng
 * @create:2021/9/14-22:30
 **/
public class Thread02 {
    public static void main(String[] args) {
        B a = new B();
        new Thread(a).start();

    }
}
class B extends Thread01 implements Runnable{
    @Override
    public void run() {
        System.out.println("Runnable实现");
    }
}

这里的底层使用的设计模式[代理模式]????

分析实现:(模拟代理模式)

package Thread_;

/**
 * @program:多线程和IO
 * @descripton:
 * @author:ZhengCheng
 * @create:2021/9/14-22:30
 **/
public class Thread02 {
    public static void main(String[] args) {
        
        new ThreadProxy (new Cat).start();
    }
}

class Animal{
}

class Cat extends Animal implements Runnable{
    @Override
    public void run(){
        System.out.println("通过代理模式实现成功");
    }
}

//线程代理类
class ThreadProxy implements Runnable{

    private Runnable target = null ;//属性,类型是Runnable
    @Override
    public void run() {
        if (target != null){
            target.run();//这个run是Runnable中的run
            /*
             @FunctionalInterface
                public interface Runnable {
                public abstract void run();
             }
             在Thread中下列描述,说明run的是target
                  What will be run.
                 private Runnable target;
             */
        }
    }

    public ThreadProxy(Runnable target) {
        this.target = target;
    }
    //我们调用的是start
    public void start() {
        start0();
    }
    //模拟底层,最终让我们实现run
    private void start0() {
        run();
    }
}


设计模式---代理模式 - Dan_Go - 博客园

具体的设计模式在学习更多之后可以查看上文。

好处:实现Runnable接口方式更加适合多个线程共享一个资源。因为可以传进同一个资源对象,从而避免单继承的限制。

其实在上述两种创建线程中,本质上都是创建Thread对象,只不过一种是直接重写Run方法,另一种是通过实例化Runnable对象,将对象传递给Thread的target,由target.run最终调用到对象的Run方法。

小问题:

1.一共有几类创建线程的方式?

2.哪种方法更好?(3点)多多思考

1.代码架构解耦的角度

2.新建线程和关闭线程的损耗

3.java不支持双继承

3.如果同时使用两种方法?会出现什么样的情况?

 

其他可以创建线程的方式(不算真正的创建,核心都是继承Thread或者Runnable)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值