JAVA多线程详解(一)

JAVA多线程详解(一)

一. 线程简介

在这里插入图片描述

1. Process(进程)与 Thread(线程)

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

注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉

2. 本章核心概念

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

二. 线程实现(重点)

1. 线程创建

Thread class -->继承Thread类(重点)
Runnable接口 -->实现Runnable接口(重点)
Callalbe接口 -->实现Callable接口(现阶段了解,工作后核心)

2. Thread类

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

public class TestThread1 extends Thread{
    //创建线程方式一:继承Thread类,重写run()方法,调用stard开启线程
    
    @Override
    public void run() {
        //run方法线程体
        for (int i = 20; i > 0; i--) {
            System.out.println("倒计时:"+i);
        }
    }

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

        //创建一个线程对象
        TestThread1 testThread1 = new TestThread1();
        //调用start方法,开启线程
        testThread1.start();


        for (int i = 0; i < 300; i++) {
            System.out.println("顺计时:"+i);
        }
    }
//总结:线程开启,不一定立即执行,由CPU调度执行
}
---------------结果---------------
代码段截取(结果看电脑性能,不唯一):
顺计时:0
顺计时:1
...
顺计时:171
倒计时:20
倒计时:19
...
倒计时:1
顺计时:172
...
顺计时:298
顺计时:299

3. 案例:下载图片

首先需要下载commons-io包,一个别人写好的工具类库
下载地址
然后新建一个软件包,将下载好的文件直接拖到新建软件包中
安装步骤1在这里插入图片描述
直接点击重构,然后右击刚刚创建的软件包,选择添加为库,结束安装
在这里插入图片描述

代码实现:

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://img1.gtimg.com/zj/pics/hv1/93/164/2262/147128463.jpg\n","p1.jpg");
       TestThread2 t2 = new TestThread2("http://pic.51yuansu.com/pic3/cover/02/17/82/59aeff9769745_610.jpg","p2.jpg");
       TestThread2 t3 = new TestThread2("https://img1.gtimg.com/zj/pics/hv1/125/164/2262/147128495.jpg","p3.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异常,downloader方法出现异常");
       }
   }
}

---------------结果---------------
下载的文件名为:p2.jpg
下载的文件名为:p3.jpg
下载的文件名为:p1.jpg

4. 实现Runnable接口

定义 MyRunnable类实现 Runnable接口
实现run()方法,编写线程执行体
创建线程对象,调用 start()方法启动线程
推荐使用 Runnable对象,因为Java单继承的局限性

实例:

//创建线程方式2:实现runnable接口,重写run方法
public class TestThread3 implements Runnable{
    public void run() {
        //run方法线程体
        for (int i = 20; i > 0; i--) {
            System.out.println("倒计时:"+i);
        }
    }

    public static void main(String[] args) {
        //创建runnable接口的实现类对象
        TestThread3 testThread3 = new TestThread3();
        //创建线程对象,通过线程对象来开启我们的线程,代理
//        Thread thread = new Thread(testThread3);
//        thread.start();  合并为以下代码:
        new Thread(testThread3).start();
        
        for (int i = 0; i < 300; i++) {
            System.out.println("顺计时:"+i);
        }
    }
}
---------------结果---------------
代码段截取:
倒计时:15
倒计时:14
顺计时:246
倒计时:13
顺计时:247
倒计时:12
顺计时:248
顺计时:249

5. 小结:

继承 Thread类

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

实现 Runnable接口

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

6. 初识并发问题

多线程操作同一个对象

案例1:

//多个线程同时操作一个对象,以买火车票为案例
public class TestThread4 implements Runnable{
    //票数
    private int ticketNums=5;

    @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 ticket = new TestThread4();
        new Thread(ticket,"小明").start();
        new Thread(ticket,"张三").start();
        new Thread(ticket,"李四").start();
    }
}
---------------结果---------------
小明->拿到了第:5张票
李四->拿到了第:4张票
张三->拿到了第:3张票
李四->拿到了第:2张票
张三->拿到了第:2张票
小明->拿到了第:1张票
发现问题,多个线程操作同一个资源的情况下,线程不安全,数据紊乱

案例2:
1.首先来个赛道距离,然后要离终点越来越近
2.判断比赛是否结束
3.打印出胜利者
4.龟兔赛跑开始
5.故事中是乌龟嬴的,兔子需要睡觉,所以我们来模拟兔子睡觉
6.终于,乌龟赢得比赛

//模拟龟兔赛跑
public class Race implements Runnable{
    private static String winner;   //胜利者
    @Override
    public void run() {
        int i=0;
        while (i<=50) {
            //兔子的速度
            if (Thread.currentThread().getName().equals("兔子")) {
                System.out.println(Thread.currentThread().getName() + "->跑了:" + i + "米");
                i+=3;
			//乌龟的速度
            } else if (Thread.currentThread().getName().equals("乌龟")) {
                System.out.println(Thread.currentThread().getName() + "->跑了:" + i + "米");
                i+=1;
            }

            //判断比赛是否结束
            boolean flag = gameOver(i);
            if (flag) {
                break;
            }
            //兔子休息
            if (Thread.currentThread().getName().equals("兔子") && i % 36 == 0) {
                try {
                    Thread.sleep(65);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //延时
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    //是否完成比赛
    //steps  步数
    private boolean gameOver(int steps){
        //判断是否有胜利者
        if(winner!=null){
            return true;
        }else{
            if(steps>=50){
                winner=Thread.currentThread().getName();
                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();
    }
}
乌龟->跑了:0米
兔子->跑了:0米
兔子->跑了:3米
乌龟->跑了:1米
乌龟->跑了:2...
兔子->跑了:42米
乌龟->跑了:48米
兔子->跑了:45米
乌龟->跑了:49米
胜利者为:乌龟

7. 实现 Callable接口(了解即可)

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

演示:利用callable改造下载图片案例

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

//线程创建方式3:实现callable接口
//1. 可以定义返回值
//2. 可以抛出异常

public class testCallable implements Callable<Boolean> {
    private String url;  //网络图片地址
    private String name;  //保存的文件名


    public testCallable(String url, String name){
        this.url=url;
        this.name=name;
    }

    //下载图片线程的执行体
    @Override
    public Boolean call() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载的文件名为:"+name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        testCallable t1 = new testCallable("https://img1.gtimg.com/zj/pics/hv1/93/164/2262/147128463.jpg\n","p1.jpg");
        testCallable t2 = new testCallable("http://pic.51yuansu.com/pic3/cover/02/17/82/59aeff9769745_610.jpg","p2.jpg");
        testCallable t3 = new testCallable("https://img1.gtimg.com/zj/pics/hv1/125/164/2262/147128495.jpg","p3.jpg");
        //创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
        Future<Boolean> r3 = ser.submit(t3);
        //获取结果
        Boolean rs1 = r1.get();
        Boolean rs2 = r2.get();
        Boolean rs3 = r3.get();
        //关闭服务
        ser.shutdownNow();

    }

}
//下载器
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异常,downloader方法出现异常");
        }
    }
}
---------------结果---------------
下载的文件名为:p2.jpg
下载的文件名为:p1.jpg
下载的文件名为:p3.jpg

8. Lamda表达式

  • λ希腊字母表中排序第十一位的字母,英语名称为Lambda

  • 避免匿名内部类定义过多

  • 其实质属于函数式编程的概念

      ( params)-> expression[表达式]
      	( params)-> statement[语句]
      		(params)-> {statements}
    

a->System. out. println(“i like lambda–>”+a);
new Thread (()-> System. out.println(“多线程学习…”)).start();

为什么要使用 lambda表达式

  • 避免匿名内部类定义过多
  • 可以让你的代码看起来很简洁
  • 去掉了一堆没有意义的代码,只留下核心的逻辑

也许你会说,我看了 Lambda表达式,不但不觉得简洁,反
而觉得更乱,看不懂了。那是因为我们还没有习惯,用的多
了,看习惯了,就好了

首先:

  • 理解 Functional Interface(函数式接口)是学习Java8 lambda表达式的关键所在。

函数式接口的定义:

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

    public interface Runnablepublic abstract void run();
  • 对于函数式接口,我们可以通过 lambda表达式来创建该接口的对象。

推导Lambda表达式
实例:

//推导Lambda表达式
public class TestLambda1 {
    public static void main(String[] args) {
        Like like = new Like();
        like.lambda();
    }
}
//定义一个函数式接口
interface ILike{
    void lambda();
}

class Like implements ILike{
    @Override
    public void lambda() {
        System.out.println("i like lambda");
    }
}
---------------结果---------------
i like lambda

当用内部类写时:

public class TestLambda1 {
    //静态内部类
    static class Like implements ILike{
        @Override
        public void lambda() {
            System.out.println("i like lambda");
        }
    }
    
    public static void main(String[] args) {
        Like like = new Like();
        like.lambda();
    }
}
//定义一个函数式接口
interface ILike{
    void lambda();
}
---------------结果---------------
i like lambda

用局部内部类写时:

public class TestLambda1 {
    public static void main(String[] args) {
        //局部内部类
        class Like implements ILike{
            @Override
            public void lambda() {
                System.out.println("i like lambda");
            }
        }

        Like like = new Like();
        like.lambda();
    }
}
//定义一个函数式接口
interface ILike{
    void lambda();
}

用匿名内部类写时:

public class TestLambda1 {
    public static void main(String[] args) {
        //没有类的名称,必须借助接口或父类
        ILike like = new ILike() {
            @Override
            public void lambda() {
                System.out.println("i like lambda");
            }
        };

        like.lambda();
    }
}
//定义一个函数式接口
interface ILike{
    void lambda();
}
---------------结果---------------
i like lambda

使用lambda简化

public class TestLambda1 {
    public static void main(String[] args) {
        ILike like = ()->{
            System.out.println("i like lambda");
        };
        like.lambda();
    }
}
//定义一个函数式接口
interface ILike{
    void lambda();
}
---------------结果---------------
i like lambda

lambda表达式简化:

public class TestLambda2 {
    public static void main(String[] args) {
        //lambda表达式简化
        Ilove love1 =(int a)->{
            System.out.println("i love you--"+a);
        };
        //简化1,去掉参数类型
        Ilove love2=(a)->{
            System.out.println("i love you--"+a);
        };
        //简化2,简化括号
        Ilove love3=a->{
            System.out.println("i love you--"+a);
        };
        //简化3,简化花括号
        Ilove love4=a-> System.out.println("i love you--"+a);
        love1.love(1);
        love2.love(2);
        love3.love(3);
        love4.love(4);
    //总结
        //lambda表达式只能有一行代码的情况下才能简化成一样,如果有多行,那么就用代码块包裹
        //前提是接口为函数式接口
        //多个参数,也可以去掉参数类型,要去掉都去掉,必须加上括号。如:Ilove love3 =(a,b)->{...}
    }
}
interface Ilove{
    void love(int a);
}
---------------结果---------------
i love you--1
i love you--2
i love you--3
i love you--4

9. 静态代理模式

演示:实现静态代理对比 Thread

/*
* 静态代理模式总结
* 真实对象和代理对象都要实现同一个接口
* 代理对象要代理真实角色
* 好处:
*   代理对象可以做很多真实对象做不了的东西
*   真实对象专注于自己的事情
*/
public class StaticProxy {
    public static void main(String[] args) {
        You you = new You();//你要结婚

        new Thread(()-> System.out.println("我爱你")).start();

        new WeddingCompany(new You()).HappyMerry();
    }
}
interface Merry{
    void HappyMerry();
}
//真实角色,你去结婚
class You implements Merry{
    @Override
    public void HappyMerry() {
        System.out.println("结婚了!");
    }
}
//代理角色,帮助你结婚
class WeddingCompany implements Merry{
    //代理真实目标角色
    private Merry target;

    public WeddingCompany(Merry target) {
        this.target = target;
    }

    @Override
    public void HappyMerry() {
        before();
        this.target.HappyMerry();//这就是真实对象
        after();
    }

    private void after() {
        System.out.println("结婚之后");
    }

    private void before() {
        System.out.println("结婚之前");
    }
}
---------------结果---------------
我爱你
结婚之前
结婚了!
结婚之后
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值