多线程(线程概念、代码示例)

进程和线程

  1. 说起进程,就必须提一下程序,程序是指令和数据的有序集合,是一个静态的概念
  2. 进程是执行程序的一次执行过程,第一个动态的概念,是系统资源分配的单位
  3. 在一个进程中包含若干个线程,线程是独立的执行路径,一个进程中至少含有一个线程,像我们已知的主线程JC线程(垃圾回收),即使我们没有自己创建线程,后台也会自动存在上面提到的两个和其他一些线程;
  4. 我们运行程序其实是运行的主线程,因为主方法是程序的入口,线程是CPU调度和执行的单位
  5. 在一个进程中如果开辟了多个线程,线程的运行有调度器安排,调度器与操作系统密切相关,人为不能干预
  6. 多个线程对同一份资源进行操作,会存在资源抢夺的问题,需要加入并发控制
  7. 线程会带来额外的开销,如CPU调度时间,并发控制开销
  8. 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

注意:大部分多线程是模拟的,真正的多线程是指有多个CPU,即多核,但是模拟出来的多线程,就是在一个CPU的前提下,在一个时间点执行一段代码,但是因为切换的很快,所以会有同步执行的错觉

创建线程

线程创建方式一 :
第一步:继承Thread
第二步:重写run()方法
第三步:创建继承了Thread类的对象 , 调用start()方法启动。

代码实现:

package com.kuang.demo01;
 
import java.awt.*;

public class TestThread1 extends Thread{     //继承Thread类

    //自定义run方法的线程
    @Override
    public void run() {
        //线程执行体
        for (int i = 0; i < 200; i++) {
            System.out.println("有人在写笔记-->"+i);
        }
    }

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

        //创建线程对象
        TestThread1 testThread1 = new TestThread1();
        testThread1.start();      //start() 方法用来 启动线程;

        for (int i = 0; i < 3000; i++) {
            System.out.println("有人在在睡觉sleep-->"+i);
        }

    }

}

上述代码运行结果太长,只取出其中部分运行结果:

有人在在睡觉sleep-->83
有人在在睡觉sleep-->84
有人在在睡觉sleep-->85
有人在在睡觉sleep-->86
有人在写笔记-->0
有人在在睡觉sleep-->87
有人在在睡觉sleep-->88
有人在在睡觉sleep-->89
有人在在睡觉sleep-->90

可以看到在运行注方法时,并没有按照顺序进行,因为:

TestThread1 testThread1 = new TestThread1();
testThread1.start(); 

上面的代码让TestThread1 类的对象可以通过start()方法来开启多线程,系统会先跳过testThread1.start(); 语句,因为线程启动还需要一段时间,转而执行下面的循环,在执行循环一段时间以后,线程差不多开启成功后开始执行,在执行一段时间后就又回到主线程,就这样不断来回切换,产生同步执行的错觉

注意:多线程只是对重写的run()方法中的逻辑而言,多条线程同步执行重写后的run()方法中的内容

创建线程方式二:
第一步:实现runnable接口
第二步:重写run方法
第三步:需要创建一个线程Thread对象 ,然后把runbale接口实现类的对象丢到构造参数里 ,调用start方式启动

package com.kuang.demo01;

public class TestThread3 implements Runnable {      //实现接口


    @Override          //重写方法
    public void run() {
        //线程执行体
        for (int i = 0; i < 200; i++) {
            System.out.println("有人在写笔记-->"+i);
        }
    }

    public static void main(String[] args) {
        //重点就是将Runbale接口实现类的对象丢入Thread构造器
        TestThread3 testThread3 = new TestThread3();
        new Thread(testThread3).start();

        for (int i = 0; i < 3000; i++) {
            System.out.println("有人在在睡觉sleep-->"+i);
        }
    }
}

代码运行结果为:

有人在写笔记-->77
有人在写笔记-->78
有人在在睡觉sleep-->1236
有人在写笔记-->79
有人在写笔记-->80
有人在写笔记-->81
有人在写笔记-->82

原理和创建方式一一样:在这里不再解释,这里要注意,一定要使用线程对象的start方法,如果使用run方法的话会先执行完线程执行体中的所有代码之后再执行主线程中的代码,在这里不再做演示

那么两种方法相比有什么优缺点?
第一种最好理解
第二种比较灵活,可以创建一个Runnable接口实现类的对象之后放入不同的Thread构造器构造器中来对任务执行多线程,在后面的龟兔赛跑的案例中可以用到

多线程应用

代码示例1:
我们可以使用多线程来在网上下载图片
代码示例:

package org.westos.java1;

import org.apache.commons.io.FileUtils;     //这里导入了第三方包

import java.io.File;
import java.io.IOException;
import java.net.URL;

//多线程实现图片下载
public class MyTest5 extends Thread{        //使用第一种创建方式

    private String url;        //定义目标图片URL
    private String filename;     //定义目标文件

    public MyTest5(String url, String filename){        //定义有参构造来确定要下载的图片的url和目标文件
        this.url = url;
        this.filename = filename;
    }


    @Override
    public void run() {
        //下载图片
        webdownload load = new webdownload();    //下载器,自定义
        load.downlodar(url, filename);
        System.out.println(filename + "下载完成");
    }

    public static void main(String[] args) {
        MyTest5 myTest1 = new MyTest5("https://img.alicdn.com/tfs/TB1uUvlXL1H3KVjSZFBXXbSMXXa-990-110.png", "file1");
        MyTest5 myTest11 = new MyTest5("https://gw.alicdn.com/tfs/TB1KZFLaBKw3KVjSZFOXXarDVXa-240-240.png", "file2");
        MyTest5 myTest12 = new MyTest5("https://img.alicdn.com/tfs/TB13XYhaW5s3KVjSZFNXXcD3FXa-990-400.jpg_1080x1800Q90s50.jpg", "file3");

        myTest1.start();
        System.out.println("执行了myTest1");
        myTest11.start();
        System.out.println("执行了myTest11");
        myTest12.start();
        System.out.println("执行了myTes12");
    }
}

class webdownload{      //自定义下载器
    public void downlodar(String url, String name){
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));       //使用第三方类的方法
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("下载出错了");
        }
    }

}

运行结果为:

执行了myTest1
执行了myTest11
执行了myTes12
file2下载完成
file1下载完成
file3下载完成

可以看到图片下载完成的顺序不一样,因为是多线程,三条线程一起执行,所以顺序可能不会按照代码中的顺序完成,至于代码:

执行了myTest1
执行了myTest11
执行了myTes12

为什么按顺序执行,这是因为主方法也是一个独立的线程,会按照线程中的顺序执行

代码示例2:
龟兔赛跑:

  1. 首先创建个赛道,然后要离终点越来越近

  2. 判断比赛是否结束

  3. 打印出胜利者

  4. 龟兔赛跑开始

  5. 故事中是乌龟赢的,兔子需要睡觉,所以我们来模拟兔子睡觉

  6. 终于,乌龟赢得比赛

    package com.kuang.demo01;
    
    
    //龟兔赛跑
    public class Race implements Runnable{        //使用第二种创建方式
    
     //winner:只有一个胜利者
     private static String winner;
    
     @Override
     public void run() {
         //赛道
         for (int step = 1; step <= 101; step++) {  
             if (Thread.currentThread().getName().equals("兔子") && step % 50==0){
                 try {
                     Thread.sleep(100);          //兔子休眠
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
             //判断比赛是否结束
             boolean flag = gameOver(step);
             if (flag){
                 break;
             }
    
             System.out.println(Thread.currentThread().getName()+"跑了"+ step +"步");
    
         }
     }
    
     //判断比赛是否结束
     private boolean gameOver(int step){
         if (winner!=null){ //如果存在胜利者
             return true;
         }
         if (step>=100){  //如果跑到了比赛结束
             winner = Thread.currentThread().getName();
             System.out.println("比赛结束");
             System.out.println("胜利者----->"+winner);
             return true;
         }
         return false;
     }
    
     public static void main(String[] args) {
         Race race = new Race();           //创建一个跑道
    
         new Thread(race,"兔子").start();         //两个线程
         new Thread(race,"乌龟").start();
     }
    

    }

运行结果为:

乌龟跑了36步
兔子跑了49步
乌龟跑了37步
乌龟跑了38步
乌龟跑了39步
乌龟跑了40步
乌龟跑了41步
乌龟跑了42步
乌龟跑了43步
乌龟跑了44步
乌龟跑了45步
......
比赛结束
胜利者----->乌龟

可以看到在兔子跑了50步后不再跑,在兔子线程休眠的时候,乌龟线程执行完毕,在winner是乌龟后打印结果,程序结束。这就是龟兔赛跑的实现

代码示例3:
模拟抢票:

package com.kuang.demo01;


//初识并发问题:多个线程同时操作一个对象(runnable接口实现类)
//线程不安全
public class TestThread4 implements Runnable {

    //票
    private int ticketNums = 10;

    @Override
    public void run() {
        test();
    }


    public void test(){
        //执行体
        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 t = new TestThread4();

        //第二个参数,创建线程名字
        new Thread(t,"小明").start();
        new Thread(t,"老师").start();
        new Thread(t,"黄牛党").start();
    }

}

运行结果为:

黄牛党-->抢到了第10张票
老师-->抢到了第8张票
小明-->抢到了第9张票
黄牛党-->抢到了第7张票
小明-->抢到了第7张票
老师-->抢到了第6张票
黄牛党-->抢到了第4张票
小明-->抢到了第5张票
老师-->抢到了第3张票
小明-->抢到了第2张票
黄牛党-->抢到了第1张票
老师-->抢到了第2张票

可以在上述运行结果中发现,黄牛小明都抢到了第7张票,这是个错误,也就是线程不安全,多个线程对同一份资源进行操作需要使用并发控制,使用同步可以改正,同步在以后会讲到

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页