程序,进程,线程

img点击并拖拽以移动

  • 程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
  • 进程则是程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
  • 通常一个进程中可以包含若干个线程,一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。

注: 很多多线程都是模拟出来的,真正的多线程是指多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快(CPU根据一定的线程调度算法来切换线程),所以就有同时执行的错局。

同一个进程下的所有线程都只能在CPU同一个核下运行,同一进程下的多个线程在同一个核下轮流使用处理器,因为处理速度快,看起来是并行,实际上同一进程下的多线程是串行。
多核可以同时运行多个进程,

核心概念

  • 线程就是独立的执行路径;
  • 在程序执行时,即使没有自己创建线程,后天也会有多个线程,如:主线程(main方法),GC线程(垃圾回收)
  • main()称之为主线程,为系统的入口,用于执行整个程序。
  • 在一个进程中,如果避开了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
  • 线程对同一资源操作时,会存在资源抢夺问题,需要加入并发控制;
  • 线程会带来额外的开销,如CPU调度时间,并发控制开销。
  • 每个线程在自己的工作内存交换,内存控制不当会造成数据不一致。

线程的创建

线程的创建有三种方式:

  • Thread class:继承Thread类(重点
  • Runnable接口:实现Runnable接口(重点
  • Callable接口:实现Callable接口(了解)

Thread

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程

测试:

//创建线程方式1:继承Thread类,重写run() 方法;
public class TestThread  extends Thread{
    @Override
    public void run() {
       //run() 方法线程体
        for (int i = 0; i < 15; i++) {
            System.out.println("我在看代码---"+i);
        }
    }

    public static void main(String[] args) {
      /* main()方法 主线程*/

        //创建一个线程对象:
        TestThread test= new TestThread();
        //调用start()方法开启线程:
        test.start();

        for (int i = 0; i < 15; i++) {
            System.out.println("我在学习多线程---"+i);
        }
    }
}

执行结果:
我在学习多线程---0
我在学习多线程---1
我在看代码---0
我在学习多线程---2
我在看代码---1
我在学习多线程---3
我在看代码---2
我在学习多线程---4
我在看代码---3
我在学习多线程---5
我在看代码---4
我在学习多线程---6
我在看代码---5
我在学习多线程---7
我在看代码---6
我在学习多线程---8
我在看代码---7
我在学习多线程---9
我在看代码---8
我在学习多线程---10
我在看代码---9
我在看代码---10
我在学习多线程---11
我在学习多线程---12
我在学习多线程---13
我在学习多线程---14
我在看代码---11
我在看代码---12
我在看代码---13
我在看代码---14

Process finished with exit code 0

如果调用run方法:

//创建一个线程对象:
      TestThread test= new TestThread();
      //调用start()方法开启线程:
      test.run();
执行结果:
我在看代码---0
我在看代码---1
我在看代码---2
我在看代码---3
我在看代码---4
我在看代码---5
我在看代码---6
我在看代码---7
我在看代码---8
我在看代码---9
我在看代码---10
我在看代码---11
我在看代码---12
我在看代码---13
我在看代码---14
我在学习多线程---0
我在学习多线程---1
我在学习多线程---2
我在学习多线程---3
我在学习多线程---4
我在学习多线程---5
我在学习多线程---6
我在学习多线程---7
我在学习多线程---8
我在学习多线程---9
我在学习多线程---10
我在学习多线程---11
我在学习多线程---12
我在学习多线程---13
我在学习多线程---14

从上述测试可以得出结论:

img

如上图所示:如果自定义线程对象在主线程main()方法中调用的是start()方法,那么此时便有多条执行路径,自定义线程会和主线程(main)会交替执行,而且执行的顺序不定,是由CPU调度来决定。如果自定义线程对象在主线程main()方法中调用的是run()方法,那么,此刻就只有主线程一条执行路径,两个线程的执行就有了先后顺序,如上执行结果,先执行完了自定义线程,再执行主线程。

Runnable

  • 自定义线程类实现Runnable接口
  • 实现run( )方法,编写线程执行体
  • 创建线程对象,调用start() 方法
//创建线程方式2:实现Runnable接口,重写run
//执行线程需要丢入Runnable接口实现类,调用start方法
public class TestThread3 implements Runnable{
    @Override
    public void run() {
        //run() 方法线程体
        for (int i = 0; i < 15; i++) {
            System.out.println("我在看代码---"+i);
        }
    }

    /* main()方法 主线程*/
    public static void main(String[] args) {
        //创建一个线程对象:Runnable接口实现类对象
        TestThread3 test3= new TestThread3();
        //创建线程对象,通过线程对象来开启我们的线程(代理,因为Thread类的底层也是实现了Runnable接口)
        Thread thread=new Thread(test3);
        thread.start();
        for (int i = 0; i < 15; i++) {
            System.out.println("我在学习多线程---"+i);
        }
    }
}

执行结果:
    
我在看代码---0
我在学习多线程---0
我在学习多线程---1
我在学习多线程---2
我在学习多线程---3
我在学习多线程---4
我在学习多线程---5
我在学习多线程---6
我在学习多线程---7
我在学习多线程---8
我在学习多线程---9
我在学习多线程---10
我在学习多线程---11
我在学习多线程---12
我在学习多线程---13
我在学习多线程---14
我在看代码---1
我在看代码---2
我在看代码---3
我在看代码---4
我在看代码---5
我在看代码---6
我在看代码---7
我在看代码---8
我在看代码---9
我在看代码---10
我在看代码---11
我在看代码---12
我在看代码---13
我在看代码---14

Process finished with exit code 0

小结:

继承Thread类:

  • 启动线程:子类对象.start()
  • 不建议使用:避免OOP单继承局限性

实现Runnable接口:

  • 启动线程:传入目标对象+Thread对象.start()
  • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

Thread类的底层也是实现了Runnable接口

Callable(了解)

  • 实现Callable接口,需要返回值类型
  • 重写call方法,需要抛出异常
  • 创建目标对象
  • 创建执行服务:ExecutorService ser=Executors.newFixedThreadPool(1);
  • 提交执行:Future result1=ser.submit(t1);
  • 获取结果:boolean r1=result1.get();
  • 关闭服务:ser.shutdownNow();

多线程测试

采用多线程来下载网图:

1.下载jar包:commons-io-2.8.0.jar

https://sovzn.lanzous.com/iX6A7mncr2b 密码:bd3z

2.将jar包导入项目 ( idea中可以在项目中新建文件夹lib,将jar包复制到lib中,然后右击lib,Add as Library即可)

3.编写测试类

import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//练习Thread,实现多线程同步下载图片
public class TestThread2 extends Thread{
    private String url; //网络图片地址
    private String name; //保存的文件名

    public TestThread2(String url,String name){
        this.url=url;
        this.name=name;
    }
    @Override
    public void run() {
        WebDownloader webDownloader=new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载了文件名为"+name);
    }

    public static void main(String[] args) {
        TestThread2 t1=new TestThread2("https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2771978851,2906984932&fm=26&gp=0.jpg","1.jpg");
        TestThread2 t2=new TestThread2("https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1603365312,3218205429&fm=26&gp=0.jpg","2.jpg");
        TestThread2 t3=new TestThread2("https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2853553659,1775735885&fm=26&gp=0.jpg","3.jpg");
        t1.start();
        t2.start();
        t3.start();
    }
}
//下载器
class WebDownloader{
    //自定义一个下载方法
    public void downloader(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));   //这个工具类由我们刚才导入的包提供
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常");
        }
    }
}
//下载的图片会保存在项目的根目录下

执行结果:
下载了文件名为1.jpg
下载了文件名为3.jpg
下载了文件名为2.jpg

Process finished with exit code 0

从执行的结果可以看出:自定义子线程的执行顺序并非按照程序的书写顺序,如果再执行一次,可能会产生新的执行顺序,这样证明了结论线程的运行由CPU调度器安排调度,先后顺序是不能人为干预的。

初始并发问题

//多个线程操作同一个对象
public class TestThread4 implements Runnable{
    //票数
    private int ticketNums=10;

    @Override
    public void run() {
        while(true){
            if(ticketNums<=0){ break;}
            try {
                //模拟延时
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"--拿到了第"+ticketNums--+"票");
        }
    }

    public static void main(String[] args) {
        TestThread4 test4=new TestThread4();
      //创建多个线程
        Thread thread1= new Thread(test4,"线程1");
        Thread thread2= new Thread(test4,"线程2");
        Thread thread3= new Thread(test4,"线程3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

执行结果:
    
线程1--拿到了第10票
线程3--拿到了第8票
线程2--拿到了第9票
线程1--拿到了第7票
线程2--拿到了第7票
线程3--拿到了第6票
线程2--拿到了第5票
线程1--拿到了第5票
线程3--拿到了第5票
线程1--拿到了第4票
线程3--拿到了第3票
线程2--拿到了第3票
线程3--拿到了第2票
线程1--拿到了第0票
线程2--拿到了第1Process finished with exit code 0

从上结果可以看出:多个线程操作同一资源的情况下,线程不安全,数据紊乱。

静态代理

实例测试:

//静态代理测试:取快递
public class StaticProxy {
    public static void main(String[] args) {
        You you =new You();
        PostMan postMan=new PostMan(you);
        postMan.success();
    }
}

interface GetExpress{
    //提取快递成功
    void success();
}
//真实对象:你去取快递
class You implements GetExpress{
    @Override
    public void success() {
        System.out.println("提取快递成功");
    }
}
//代理对象:邮递员帮你去取快递
class PostMan implements GetExpress{
    private GetExpress target;//代理目标
    public PostMan(GetExpress target) {
        this.target = target;
    }

    @Override
    public void success() {
        before();
        this.target.success();
        after();
    }

    private void before(){
        System.out.println("取快递前:付定金");
    }
    private void after(){
        System.out.println("取快递后:付尾款");
    }
}

执行结果:
    
取快递前:付定金
提取快递成功
取快递后:付尾款

Process finished with exit code 0

总结:

  • 真实对象和代理对象都要实现同一个接口
  • 代理对象要代理真实对象

好处:代理对象可以做很多真实对象做不了的业务(如上面的before和after方法),真实对象专注做自己的业务

Lambda表达式

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。

函数式接口定义:

任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。

如:

public interface Runnable{
    public abstract void run();
}

对于函数式接口,我们可以通过lambda表达式来创建该接口的对象

实例1:

  package com.lamda;
/*
* 推导lambda表达式
* */
public class TestLamda {

    public static void main(String[] args) {
        //正常执行
        ILike like = new Like();
        like.lambda();

        //用lambda简化,将下列语句传入接口ILike,相当于实现了接口中的lambda()方法
        like=()->{
            System.out.println("i like lambda2");
        };
        like.lambda();
    }

}
//1.定义一个函数式接口
interface ILike{
    void lambda();
}
// 2.实现类
class Like implements ILike{

    @Override
    public void lambda() {
        System.out.println("I like Lambda");
    }
}

结果:
I like Lambda
i like lambda2

Process finished with exit code 0

以下是lambda表达式的重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

Lambda 表达式实例

Lambda 表达式的简单例子:

// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

实例2:

public class Java8Tester {
   public static void main(String args[]){
      Java8Tester tester = new Java8Tester();
        
      // 类型声明
      MathOperation addition = (int a, int b) -> a + b;
      System.out.println("test1---"+addition. operation(3 ,1 ));  
      // 不用类型声明
      MathOperation subtraction = (a, b) -> a - b;
      System.out.println("test2---"+subtraction. operation(3 ,1 ));  
      // 大括号中的返回语句
      MathOperation multiplication = (int a, int b) -> { return a * b; };
      System.out.println("test3---"+multiplication. operation(3 ,1 ));  
      // 没有大括号及返回语句
      MathOperation division = (int a, int b) -> a / b;
        
      System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
      System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
      System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
      System.out.println("10 / 5 = " + tester.operate(10, 5, division));
        
      // 不用括号
      GreetingService greetService1 = message ->
      System.out.println("Hello " + message);
        
      // 用括号
      GreetingService greetService2 = (message) ->
      System.out.println("Hello " + message);
        
      greetService1.sayMessage("Runoob");
      greetService2.sayMessage("Google");
   }
    //函数式接口
   interface MathOperation {
      int operation(int a, int b);
   }
   //函数式接口
   interface GreetingService {
      void sayMessage(String message);
   }

   private int operate(int a, int b, MathOperation mathOperation){
      return mathOperation.operation(a, b);
   }
}

输出结果:

test1---4
test1---2
test1---6
10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Runoob
Hello Google

使用 Lambda 表达式需要注意以下两点:

  • Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。
  • Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。

线程状态

点击并拖拽以移动 img点击并拖拽以移动

过程描述:

点击并拖拽以移动 img点击并拖拽以移动

线程方法:

方法 说明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
void join() 等待该线程终止
static void yield() 暂停当前正在执行的线程对象,并执行其他线程
void interrupt() 中断线程,别用这个方式
boolean isAlive() 测试线程是否处于活动状态

停止线程

  • 不推荐使用JDK提供的stop(),destroy()方法【已废弃】。
  • 推荐线程自己停止下来,建议使用一个标志位进行终止变量,当flag=false,则终止线程运行。

测试:

/*
* 测试停止线程
* 1.建议线程正常停止----》利用次数,不建议死循环
* 2.建议使用标志位----》设置一个标志位
* 3.不要使用stop或者destroy等过时的或JDK不建议使用的方法
* */
public class TestStopThread implements Runnable{

    //1.设置一个标志位
    private boolean flag=true;
    @Override
    public void run() {
    int i=0;
    while(flag){
        System.out.println("线程正在执行------"+i++);
    }
    }
    //2.设置一个公开的方法停止线程,转换标志位
    public void stopThread(){
        this.flag=false;
    }
    public static void main(String[] args) {
     //启动线程
        TestStopThread test=new TestStopThread();
        new Thread(test).start();
        for (int i = 0; i <= 20; i++) {
            System.out.println("main线程正在运行"+i);
            if (i==12){
                test.stopThread();//调用stopThread方法切换标志位,让线程停止
                System.out.println("线程停止运行--------------------");
            }
        }
    }
}

执行结果:
main线程正在运行0
线程正在执行------0
main线程正在运行1
线程正在执行------1
main线程正在运行2
线程正在执行------2
线程正在执行------3
线程正在执行------4
线程正在执行------5
main线程正在运行3
main线程正在运行4
线程正在执行------6
main线程正在运行5
main线程正在运行6
main线程正在运行7
main线程正在运行8
main线程正在运行9
main线程正在运行10
main线程正在运行11
main线程正在运行12
线程正在执行------7
线程停止运行--------------------
main线程正在运行13
main线程正在运行14
main线程正在运行15
main线程正在运行16
main线程正在运行17
main线程正在运行18
main线程正在运行19
main线程正在运行20

Process finished with exit code 0

从上执行结果可以看出,当i=12时,子线程停止运行,主线程main一直执行完

线程休眠:sleep

  • sleep(时间)指定当前线程阻塞的毫秒数
  • sleep存在异常InterruptedException
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁

测试:

模拟网络延时:

//模拟网络延时
public class TestSleep implements Runnable {
    //票数
    private int ticketNums=10;

    @Override
    public void run() {
        while(true){
            if(ticketNums<=0){ break;}
           try {
                //模拟延时
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"--拿到了第"+ ticketNums-- +"票");
        }
    }

    public static void main(String[] args) {
        TestSleep test4=new TestSleep();
        new Thread(test4,"线程1").start();
        new Thread(test4,"线程2").start();
        new Thread(test4,"线程3").start();
    }
}

结果:
    
线程3--拿到了第10票
线程2--拿到了第10票
线程1--拿到了第9票
线程2--拿到了第7票
线程1--拿到了第8票
线程3--拿到了第8票
线程3--拿到了第6票
线程2--拿到了第6票
线程1--拿到了第6票
线程2--拿到了第5票
线程1--拿到了第5票
线程3--拿到了第4票
线程3--拿到了第3票
线程2--拿到了第2票
线程1--拿到了第1Process finished with exit code 0

模拟倒计时:

//模拟倒计时
public class TestSleep2 {
    public  static void tendown(){
        int num=10;
        while(true){
            try {
                Thread.sleep(1000);
                System.out.println(num--);
                if (num<=0){
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        tendown();
    }
}

结果:每一秒打印一个数字,打印十个
10
9
8
7
6
5
4
3
2
1

Process finished with exit code 0

线程礼让:yield

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态,等待CPU再次调度
  • CPU重新调度,礼让不一定成功,CPU可能再次优先调度该线程。

测试:

//测试礼让线程
public class TestYield {
    public static void main(String[] args) {
        Myyield myyield=new Myyield();
        new Thread(myyield,"线程1").start();
        new Thread(myyield,"线程2").start();
    }
}

class Myyield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":线程开始执行");
        Thread.yield();//线程礼让
        System.out.println(Thread.currentThread().getName()+":线程停止执行");
    }
}
执行结果:
线程2:线程开始执行
线程1:线程开始执行
线程1:线程停止执行
线程2:线程停止执行

Process finished with exit code 0

从上执行结果可以看出:初次调度,CPU先执行的线程2,线程2礼让成功,开始执行线程1,线程1礼让失败,再次执行线程1,线程1执行完后,再执行完线程2

线程强制执行:join

  • Join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞
  • 可以想象成插队

测试:

//测试join
public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("VIP子线程-----"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //启动线程
        TestJoin test = new TestJoin();
        Thread thread=new Thread(test);
        thread.start();
        
        //主线程
        for (int i = 0; i <= 12; i++) {
            if (i==8){
            thread.join();//插队
            }
            System.out.println("main线程--"+i);
        }
    }
}

结果:
    
main线程--0
main线程--1
main线程--2
main线程--3
main线程--4
main线程--5
main线程--6
main线程--7
VIP子线程-----0
VIP子线程-----1
VIP子线程-----2
VIP子线程-----3
VIP子线程-----4
VIP子线程-----5
VIP子线程-----6
VIP子线程-----7
VIP子线程-----8
VIP子线程-----9
main线程--8
main线程--9
main线程--10
main线程--11
main线程--12

Process finished with exit code 0

由上运行结果可以看出,当 i= 8时,子线程被强制执行,等到子线程执行完,主线程main才继续执行。

线程状态

Thread.State

  • NEW: 尚未启动的线程处于此状态
  • RUNNABLE:正在java虚拟机中执行的线程处于此状态
  • BLOCKED:被阻塞等待监视器锁定的线程处于此状态
  • WAITING:正在等待另一个线程执行特定动作的线程处于此状态
  • TIMED_WAITING: 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
  • TERMINATED:已退出的线程处于此状态
//测试观察线程的状态
public class TestState {
    public static void main(String[] args) {
        Thread thread=new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("--------");
            }
        });
        //观察状态
        Thread.State state = thread.getState();
        System.out.println(state);

        //观察启动后状态
        thread.start();
        state= thread.getState();
        System.out.println(state);

        /*while (state!=Thread.State.TERMINATED){
            //只要线程不终止,就一直输出状态
            state= thread.getState();
            System.out.println(state);
        }*/
    }
}

结果:
NEW
RUNNABLE
--------
--------
--------
--------
--------

Process finished with exit code 0