Java基础——多线程(1)

1.进程概述及多进程的意义

线程和进程?
要想说线程,首先必须得聊聊进程,因为线程是依赖于进程存在的。

进程概述
	什么是进程呢?通过任务管理器我们就可以看到进程的存在。
	概念:进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。
				  每一个进程都有它自己的内存空间和系统资源。

多进程的意义
	单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),
	所以我们常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。
	对于单核计算机来讲,游戏进程和音乐进程是同时运行的吗?不是。
	因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,
	所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率。

2.线程概述及多线程的意义及并行和并发的区别

线程:在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。
是程序使用CPU的基本单位。所以,进程是拥有资源的基本单位, 线程是CPU调度的基本单位。

线程有什么意义呢?
	多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。
	
	那么怎么理解这个问题呢?
	我们程序在运行的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到
	CPU的执行权的概率应该比较单线程程序抢到的概率要大.那么也就是说,
	CPU在多线程程序中执行的时间要比单线程多,所以就提高了程序的使用率.
	但是即使是多线程程序,
	那么他们中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机性.

并行和并发。
前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。
后者是物理上同时发生,指在某一个时间点同时运行多个程序。

什么是并发 ?
       并发 : 指应用能够交替执行不同的任务, 其实并发有点类似于多线程的原理, 多线程并非是如果你开两个线程同时执行多个任务。
 执行, 就是在你几乎不可能察觉到的速度不断去切换这两个任务, 已达到"同时执行效果", 
 其实并不是的, 只是计算机的速度太快, 我们无法察觉到而已. 
 就类似于你, 吃一口饭喝一口水, 以正常速度来看, 完全能够看的出来, 
 当你把这个过程以n倍速度执行时..可以想象一下.
        
 什么是并行 ?
     并行 : 指应用能够同时执行不同的任务, 
     例:吃饭的时候可以边吃饭边打电话, 这两件事情可以同时执行

Java程序运行原理和JVM的启动是多线程的吗?

Java程序运行原理
	Java.exe 命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。
	该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
	所以 main方法运行在主线程中。
JVM的启动是多线程的吗:JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。

3.多线程程序实现的方式

由于线程是依赖进程而存在的,所以我们应该先创建一个进程(JVM)出来。
  		而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。
  		但是Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。
  		但是呢?Java可以去调用C/C++写好的程序来实现多线程程序。
  		由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,
  		然后提供一些类供我们使用。我们就可以实现多线程程序了。

多线程实现方式1

public class MyTest {
    public static void main(String[] args) {
       //Thread 线程 是程序中的执行线程。
        // Java 虚拟机允许应用程序并发地运行多个执行线程。'创建新执行线程有两种方法。
        // 一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例。
        //1. 定义一个类,继承 Thread类
        //2. 重写 Thread类中的run方法,run()方法中的代码,就是由线程来执行的代码
        //3. 创建线程的子类对象,启动线程

        MyThread th = new MyThread();
        //启动线程,不是调用run() ,你这样调用,根本就没有新的线程被创建出来,你这只是很简单的new对象调用方法
        //th.run();
        //start()
        //使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
        th.start(); //正确的启动线程是调用strart()方法
        //th.start(); //同一个线程不要重复开启,重复开启会抛异常
        //MyThread th2 = new MyThread();
       // th2.start();
    }
}

MyThread

public class MyThread extends Thread {
    @Override
    public void run() {
        //run()方法里面的代码,就是由线程来执行,一般run()方法里面定义的是耗时的操作
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}
run()和start()方法的区别
 我们启动线程使用不是run方法,而应该是start方法.使该线程开始执行;
 Java 虚拟机调用该线程的 run 方法。
     
为什么要重写run方法?
这个类是一个线程类,那么在这个类中我们可不可以写一些其他的方法呢?		
   我们可以在写其他的方法,那么其他方法中封装的代码都是需要被我们线程执行的吗? 不一定
   那么也就是run方法中封装应该是必须被线程执行的代码。
   
 run方法中的代码的书写原则: 一般是比较耗时的代码

4.获取和设置线程对象名称

Thread类的基本获取和设置方法
	public final String getName()//获取线程名称
	public final void setName(String name)//设置线程名称
	其实通过构造方法也可以给线程起名字
	思考:
		如何获取main方法所在的线程名称呢?
		public static Thread currentThread()//获取当前执行的线程
	
	 我们现在是想获取主线程的名称,那么我们可不可以先获取到主线程,
	 如果我们能获取到主线程,那么我们就可以调用getName方法获取对应的名称.
	 
	 如何获取主线程呢? 
	 public static Thread currentThread()返回对当前正在执行的线程对象的引用。 

案例演示

public class MyTest {
    public static void main(String[] args) {
        MyThread th = new MyThread();
        //设置线程的名称
        th.setName("fan冰冰");
        th.start();
        MyThread th2 = new MyThread();
        th2.setName("李冰冰");
        th2.start();
        //获取当前正在执行的线程对象
        Thread thread = Thread.currentThread();
        String name = thread.getName();
        System.out.println(name);
    }
}

5.线程调度及获取和设置线程优先级

线程的执行
	假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,
	线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?
线程有两种调度模型:
	分时调度模型   	所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
	抢占式调度模型   优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,
					优先级高的线程获取的 CPU 时间片相对多一些。 
					Java使用的是抢占式调度模型。
如何设置和获取线程优先级
	public final int getPriority()  //获取线程的优先级
	public final void setPriority(int newPriority)  //设置线程的优先级

注意事项:  
有的时候我们给线程设置了指定的优先级,但是该线程并不是按照优先级高的线程执行,那是为什么呢?
因为线程的优先级的大小仅仅表示这个线程被CPU执行的概率增大了.
但是我们都知道多线程具有随机性,
所以有的时候一两次的运行说明不了问题

 我们现在呢没有给线程设置优先级,那么java采用的是抢占式调度模型,那么这个线程应该存在一个默认的优先级.
 那么这个默认的优先级是多少呢,以及我们如何来获取线程的优先级.
 获取线程的优先级:
 public final int getPriority()返回线程的优先级。 
 线程的默认优先级是5
 给线程设置优先级:范围是 1---10
	public final void setPriority(int newPriority)

案例演示

public class MyTest {
    public static void main(String[] args) {
        MyThread th = new MyThread();
        //设置线程的名称
        th.setName("fan冰冰");
        //获取线程的优先级 优先级的范围是 1---10
        //int priority = th.getPriority();
        //System.out.println(priority);
        th.setPriority(Thread.MIN_PRIORITY);
        th.start();
        MyThread th2 = new MyThread();
        //int priority1 = th2.getPriority();
       // System.out.println(priority1);
        th2.setPriority(Thread.MAX_PRIORITY);
        th2.setName("李冰冰");
        th2.start();
    }
}

6.线程控制

线程休眠: public static void sleep(long millis)

public class MyTest {
    public static void main(String[] args) throws InterruptedException {
        //让当前线程休眠,单位是毫秒
        Thread.sleep(3000);
        System.out.println("主线程代码");
        MyThread th = new MyThread();
        //设置线程的名称
        th.setName("fan冰冰");
        th.start();
    }
}

加入线程: public final void join()

意思就是: 等待该线程执行完毕了以后,其他线程才能再次执行
注意事项: 在线程启动之后,在调用方法

public class MyTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread th1 = new MyThread("刘备");
        MyThread th2 = new MyThread("关羽");
        MyThread th3 = new MyThread("张飞");
        //三个线程按顺序来执行 join(); 注意事项: 在线程启动之后,在调用方法
        //join()可以把线程默认的并发执行,变为串行也就是按顺序执行。
        th1.start();
        th1.join();
        th2.start();
        th2.join();
        th3.start();
        th3.join();
    }
}

礼让线程: public static void yield(): 暂停当前正在执行的线程对象,并执行其他线程。

public class MyThread extends Thread {
    public MyThread() {}
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            Thread.yield(); //线程礼让
            System.out.println(Thread.currentThread().getName()+ "===" + i);
        }
    }
}

测试类

public class MyTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread th1 = new MyThread("刘备");
        MyThread th2 = new MyThread("关羽");
        th1.start();
        th2.start();
    }
}

守护线程: public final void setDaemon(boolean on):

把该线程标记为守护线程 , 该方法必须在启动线程前调用。

详细内容请看:https://blog.csdn.net/weixin_43496491/article/details/121756384

案例演示
MyTest

public class MyTest {
    public static void main(String[] args) {
        Thread.currentThread().setName("刘备");
        MyThread th2 = new MyThread("关羽");
        MyThread th3 = new MyThread("张飞");
        th2.setDaemon(true); //设置该线程为守护线程
        th2.start();
        th3.setDaemon(true);
        th3.start();
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName()+"=="+i);

        }
        System.out.println("主线程里面的代码执行完了");
    }
}

MyThread

public class MyThread extends Thread {
    public MyThread() {}
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName()+ "===" + i);
        }
    }
}

中断线程
public final void stop(): 停止线程的运行
public void interrupt(): 中断线程,当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞
强制停止线程

public class MyTest {
    public static void main(String[] args) throws InterruptedException {

        MyThread th1 = new MyThread("关羽");
        th1.start();
        Thread.sleep(5);
        th1.stop(); //强制停止线程
    }
}

清除线程的阻塞状态

public class MyTest {
    public static void main(String[] args) throws InterruptedException {

        MyThread th1 = new MyThread("关羽");
        th1.start();
        th1.interrupt(); //清除线程的阻塞状态
    }
}

7.多线程程序实现的方式2

实现Runnable接口  这种方式扩展性强 实现一个接口 还可以再去继承其他类
	如何获取线程名称
	如何给线程设置名称
	实现接口方式的好处
		可以避免由于Java单继承带来的局限性。

案例演示

public class MyTest {
    public static void main(String[] args) {
       // 创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。
      //  然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
        MyRunnable myRunnable = new MyRunnable();
        Thread th = new Thread(myRunnable,"王祖贤");
        th.start();

        Thread th2 = new Thread(myRunnable, "张曼玉");
        th2.start();
    }
}

//Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。
// Runnable 任务,由线程来执行
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        // Thread.currentThread() 获取当前执行的线程
        String name = Thread.currentThread().getName();
        System.out.println(name+"===让线程执行的代码");
    }
}

8.多线程程序实现的方式3

实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类

实现步骤
	1.创建一个类实现Callable 接口
	2.创建一个FutureTask类将Callable接口的子类对象作为参数传进去
	3.创建Thread类,将FutureTask对象作为参数传进去
	4.开启线程

案例演示

public class MyTest3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable(500);
        FutureTask<Integer> task = new FutureTask<>(myCallable);
        Thread th= new Thread(task);
        th.start();

        //获取异步任务执行完的结果
        Integer integer = task.get();
        System.out.println(integer);
        
        MyCallable myCallable2 = new MyCallable(100);
        FutureTask<Integer> task2 = new FutureTask<>(myCallable2);
        Thread th2= new Thread(task2);
        th2.start();
        Integer integer1 = task2.get();
        System.out.println(integer1);
    }
}
public class MyCallable implements Callable<Integer> {
    private int num;
    public MyCallable(int num) {
        this.num = num;
    }

    //call() 方法,线程来执行的方法
    @Override
    public Integer call() throws Exception {
        System.out.println("线程执行了");
        int sum=0;
        for (int i=1;i<=num;i++){
            sum+=i;
        }
        return sum;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
回答: 你提供的引用内容中包含了一个Java程序的代码片段,该代码片段展示了如何使用多线程进行文件下载。这段代码中,首先创建了一个`MultiThreadDownload`对象,传入了下载路径、保存路径和线程数。然后调用`download()`方法开始下载文件。\[1\] 在代码的第二个引用中,根据文件的长度计算了每个线程下载的起始位置和结束位置。通过循环遍历线程数,计算每个线程的起始位置和结束位置,并进行相应的下载操作。\[2\] 在代码的第三个引用中,首先根据访问的URL路径创建了一个`HttpURLConnection`对象,然后调用`getContentLengthLong()`方法获取文件的字节大小。接着使用`RandomAccessFile`对象调用`setLength()`方法设置本地文件的长度,这个文件是一个空数据文件,通过多线程进行对`RandomAccessFile`对象的本地文件随机位置写入数据。最后关闭文件和断开连接。\[3\] 综上所述,这段代码展示了如何使用多线程进行Java文件的下载操作,并且通过设置文件的长度和使用`RandomAccessFile`对象实现了多线程写入数据的功能。 #### 引用[.reference_title] - *1* *2* *3* [【Java】网络编程——多线程下载文件](https://blog.csdn.net/qq_42470947/article/details/105889839)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Geek Li

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

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

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

打赏作者

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

抵扣说明:

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

余额充值