多线程

多线程

线程是依赖于进程存在的,

进程:

进程就是正在运行的程序,是系统进行资源分配和调用的单位,每一个进程都有它自己的内存空间和系统资源。

多线程

单进程计算机只能做一件事,常见的操作系统都是多进程操作系统,对于单核计算机来讲,游戏进程和音乐进程不是同时运行的。因为CPU在某个时间点上只能做一件事,计算机是在游戏进程和音乐进程之间做着频繁的切换,且切换速度很快。对进程的作用不是提高执行速度而是提高CPU的使用率。

概述:

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

意义:

多线程的作用不是提高执行速率而是为了提高应用程序的使用率。

其实我们的程序在运行的时候都是在抢占CPU的时间片(执行权),如果是多线程的程序,那么在抢到CPU的执行权的概率比单线程的要大,那么也就是说,CPU在多线程程序中执行的时间要比单线程多,所以就提高了程序的使用率.但是即使是多线程程序,那么他们中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机性.
并行和并发的区别

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

并发:

指应用能够交替执行不同的任务, 其实并发有点类似于多线程的原理, 多线程并非是如果你开两个线程同时执行多个任务。

并行:

指应用能够同时执行不同的任务, 例:吃饭的时候可以边吃饭边打电话, 这两件事情可以同时执行

Java程序的运行原理

java命令会启动java虚拟机,启动jvm,等于启动了一个应用程序,也就是启动了一个进程,该进程会启动一个主线程,然后主线程去调用某个类的main方法,所以main方法运行在主线程中。

JVM的启动至少启动了垃圾回收线程和主线程,所以是多线程的。

多线程程序的实现

实现方式1

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

具体实现方式1
  1. 继承Thread类
  2. 该子类应重写 Thread 类的 run 方法。
  3. 接下来可以分配并启动该子类的实例。
package com.itheima.demo.demo2;

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+i+"线程被执行了");
        }
    }
}
package com.itheima.demo.demo2;

public class Test7 {//主类里面的输出结果先执行,再执行run里面的代码
    public static void main(String[] args) {
        MyThread th = new MyThread();
        th.start();
        th.setName("cherry");
        MyThread th1 = new MyThread();
        th1.start();
        th1.setName("lisa");
        System.out.println("这里的代码执行了");

    }
}

注意事项:
  1. 在启动线程的时候,不能 直接调用run方法,如果调用的话并没有什么线程创建出来只是普通的new对象调用方法而已,这些代码还是运行在主线程。调用start来正确开启线程。
  2. 多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
为什么要重写run方法

run 方法中要封装一些要执行的耗时的代码,开启线程后调用的是是run方法中的代码,所以要重写run方法。

获取和设置多线程对象的名称
Thread类的基本获取和设置方法
	public final String getName()//获取线程名称
	public final void setName(String name)//设置线程名称
	其实通过构造方法也可以给线程起名字
	public static Thread currentThread()//获取当前执行的线程
线程的调度模式
  1. 分时调度模型 :所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
  2. 抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。 Java使用的是抢占式调度模型。优先级越高,抢占到的线程就越多
设置和获取线程优先级

​ public final int getPriority() //获取线程的优先级,线程的默认优先级是5
​ public final void setPriority(int newPriority)//设置线程的优先级

线程的优先级的大小仅仅表示这个线程被CPU执行的概率增大了.但是我们都知道多线程具有随机性,所以有的时候一两次的运行说明不了问题
线程控制
休眠线程
线程休眠是让当前线程休眠。
public static void sleep(long millis) 线程休眠
在主线程中,就是主线程休眠
在子线程中就是让子线程休眠
Thread.sleeep()
这个在子类中无法抛出异常,只能抓

线程休眠使线程处于阻塞状态

加入线程
public final void join()
意思就是: 等待该线程执行完毕了以后,其他线程才能再次执行
注意事项: 在线程启动之后,在调用方法
也就是MyThered中再次加入一个join的方法
  public void join(String s, String wq) {
        System.out.println("join执行了");
    }
礼让线程

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

其他线程执行完毕之后在执行当前的线程。这个礼让是要暂停当前正在执行的线程,这个暂停的时间是相当短的,如果在这个线程暂停完毕以后,其他的线程还没有
抢占到CPU的执行权,那么这个时候这个线程应该再次和其他线程抢占CPU的执行权.

守护线程

public final void setDaemon(boolean on):
将该线程标记为守护线程。当正在运行的线程都是守护线程时,Java 虚拟机退出,该方法必须在启动线程前调用。

用户线程和守护线程的区别

​ 用户线程和守护线程都是线程,区别是Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束。很好理解,守护线程是用来辅助用户线程的,如公司的保安和员工,各司其职,当员工都离开后,保安自然下班了。

用户线程和守护线程的适用场景

​ 由两者的区别及dead时间点可知,守护线程不适合用于输入输出或计算等操作,因为用户线程执行完毕,程序就dead了,适用于辅助用户线程的场景,如JVM的垃圾回收,内存管理都是守护线程,还有就是在做数据库应用的时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监听连接个数、超时时间、状态等。

创建守护线程

​ 调用线程对象的方法setDaemon(true),设置线程为守护线程。
​ 1)thread.setDaemon(true)必须在thread.start()之前设置。
​ 2)在Daemon线程中产生的新线程也是Daemon的。
​ 3)不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。
​ 因为Daemon Thread还没来得及进行操作,虚拟机可能已经退出了。

Java守护线程和Linux守护进程

​ 两者不是一个概念。Linux守护进程是后台服务进程,没有控制台。
​ 在Windows中,你可以运行javaw来达到释放控制台的目的,在Unix下你加&在命令的最后就行了。所以守护进程并非一定需要的。

中断线程
public final void stop():		停止线程的运行
public void interrupt():		中断线程(这个翻译不太好),查看API可得当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞interrupt:清除线程阻塞

采用多线程复制mp4

package com.itheima.demo.demo4;

public class Test {
    public static void main(String[] args) {
        CopyMp4Thread mp4Thread = new CopyMp4Thread();
        mp4Thread.start();
        System.out.println("复制完毕");
    }
}

package com.itheima.demo.demo4;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CopyMp4Thread extends Thread {
    @Override
    public void run() {
        try {
            Files.copy(Paths.get("Rec 2020-08-16 0002.mp4"),Paths.get("C:\\Users\\Administrator\\Desktop\\cherry.mp4"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

实现方式2实现Runnable接口

这种方式扩展性强,实现一个接口还可以继承其他的类

1.声明实现 Runnable 接口的类。
2.该类然后实现 run 方法。
3.然后可以分配该类的实例,
4.在创建 Thread 时作为一个参数来传递并启动。
Thread.currentThread().getName()   这样获取当前线程的名称
创建任务
  MyRunnable myRunnable = new MyRunnable();
 把任务传递进来
 Thread th = new Thread(myRunnable,"刘亦菲");
 Thread th2 = new Thread(myRunnable,"范冰冰");
 //刘亦菲===0
范冰冰===0
刘亦菲===1
范冰冰===1
刘亦菲===2
范冰冰===2
也就是各自执行各自的线程
void run ()
  使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。

实现方式3

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

实现步骤
	1.创建一个类实现Callable 接口
	2.创建一个FutureTask类将Callable接口的子类对象作为参数传进去
	3.创建Thread类,将FutureTask对象作为参数传进去
	4.开启线程
判断一个多线程应用程序是否有问题的标准:

是否是多线程环境

是否存在共享数据

是否存在多条语句同时操作共享数据

解决方法:

需要使用同步代码块:

格式:

synchronized(对象){//不能在括号了直接new 对象 new 了 就没效果

要被同步的代码 ;

}

这个同步代码块保证数据的安全性的一个主要因素就是这个对象
注意这个对象 要定义为静态成员变量 才能被所有线程共享

需要这个对象被所有的线程对象所共享

这个对象其实就是一把锁.

这个对象习惯叫做监视器

买电影票
出现同票和负票的原因

售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟

package com.itheima.demo.demo3;

public class CellRunnable implements Runnable {
   static int piao = 100;
    int i = 0;
    @Override
    public void run() {
       while (true){
           if(i%2==0){
               synchronized (CellRunnable.class){
                   //加锁,将有问题的代码放在同步代码块里面,
                   try {
                       Thread.sleep(20);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   if(piao>0){
                       System.out.println(Thread.currentThread().getName()+"正在售卖第"+(piao--)+"张票");
                   }
               }
           }
           else{
               try {
                   maipiao();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
           i++;
       }

    }

    public static void maipiao() throws InterruptedException {
        Thread.sleep(20);
        if(piao>0){
            System.out.println(Thread.currentThread().getName()+"正在售卖第"+(piao--)+"张票");
        }
    }
}
package com.itheima.demo.demo3;

public class Test {
    public static void main(String[] args) {
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable);
        Thread th2 = new Thread(cellRunnable);
        Thread th3 = new Thread(cellRunnable);
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        th1.start();
        th2.start();
        th3.start();
        System.out.println("各个窗口的售票情况");
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值