第8章 多线程

打印 上一主题 下一主题

主题 528|帖子 528|积分 1584

8.1 线程简介

1 、多任务

  • 现实生活中多件事一起作。
  • 在程序中是指在一个系统中可以同时进行多个进程,即有多个单独运行的任务,每一个任务对应一个进程。
  • 每一个进程都有一段专用的内存区域,即使是多次启动同一段程序产生不同的进程也是如此。
2、多线程

  • Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
  • 多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。
  • 多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
  • 主线程和子线程交替执行
3、程序、进程、线程

  • 程序

    • 程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。

  • 进程

    • 一个应用程序(1个进程是一个软件)
    • 是执行程序的一次执行过程,是一个流动的概念。是系统资源分配的单位。
    • 一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。

  • 线程

    • 一个进程中的执行场景/执行单元。
    • 线程是CPU调度和执行的单位。
    • 一个线程不能独立的存在,它必须是进程的一部分。

  • 注意:

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

8.2 线程创建


  • 三种创建方式:

    • Thread class 继承Thread类
    • Runnable接口 实现Runnable接口
    • Callable接口 实现Callable接口

8.2.1 Thread类


  • 创建一个新的执行线程的方法,将一个声明为Thread的子类。
  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程。
  • 注意:

    • 线程开启不一定立即执行,由CPU调度执行
    • 启动线程:子类对象.start()
    • 不建议使用,为了避免OOP单继承的局限性

  • 实例
    1. package Demo032;<br>​<br>/**<br> * @Author: H-YONG-8<br> * @DATA: 2023/4/20<br> * JavaSE<br> * 创建多线程  继承Thread类<br> */<br>public class TestThread1 extends Thread{<br>    @Override<br>    public void run() {<br>        for (int i = 0; i < 20; i++) {<br>            System.out.println("我在看代码-----"+i);<br>        }<br>    }<br>​<br>    public static void main(String[] args) {<br>        //main 主线程<br>​<br>        //创建一个线程对象<br>        TestThread1 testThread1 = new TestThread1();<br>​<br>        //调用start()方法开启线程<br>        testThread1.start();<br>        for (int i = 0; i < 20; i++) {<br>            System.out.println("我在学习多线程---"+i);<br>        }<br>    }<br>}<br>​<br>//多条执行路径,主线程和子线程并行交替执行<br>​<br>执行结果:<br>我在学习多线程---0<br>我在看代码-----0<br>我在学习多线程---1<br>我在看代码-----1<br>我在学习多线程---2<br>我在看代码-----2<br>我在看代码-----3<br>我在学习多线程---3<br>我在看代码-----4<br>我在看代码-----5<br>我在学习多线程---4<br>我在学习多线程---5<br>我在看代码-----6<br>我在学习多线程---6<br>我在看代码-----7<br>我在学习多线程---7<br>我在看代码-----8<br>我在学习多线程---8<br>我在看代码-----9<br>我在学习多线程---9<br>我在看代码-----10<br>我在学习多线程---10<br>我在学习多线程---11<br>我在学习多线程---12<br>我在学习多线程---13<br>我在学习多线程---14<br>我在学习多线程---15<br>我在学习多线程---16<br>我在学习多线程---17<br>我在学习多线程---18<br>我在学习多线程---19<br>我在看代码-----11<br>我在看代码-----12<br>我在看代码-----13<br>我在看代码-----14<br>我在看代码-----15<br>我在看代码-----16<br>我在看代码-----17<br>我在看代码-----18<br>我在看代码-----19
    复制代码
8.2.2 Runnable


  • 创建一个线程是声明实现类Runnable接口
  • 定义MyRunable类实现Runnable接口
  • 实现run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
  • 注意:

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

  • 实例:
    1. package Demo032;<br>​<br>/**<br> * @Author: H-YONG-8<br> * @DATA: 2023/4/20<br> * JavaSE<br> * 实现Runnable接口线程<br> */<br>public class TestThread2 implements Runnable{<br>    <br>    //重写方法<br>    @Override<br>    public void run() {<br>        for (int i = 0; i < 20; i++) {<br>            System.out.println("我在看代码-----"+i);<br>        }<br>    }<br>​<br>    public static void main(String[] args) {<br>        //main 主线程<br>​<br>        //创建一个线程对象<br>        TestThread2 testThread2 = new TestThread2();<br>        //代理,通过线程对象来开启线程<br>        Thread thread = new Thread(testThread2);<br>        thread.start();<br>        for (int i = 0; i < 20; i++) {<br>            System.out.println("我在学习多线程---"+i);<br>        }<br>    }<br>}<br>​<br>​<br>​<br>执行结果:<br>我在学习多线程---0<br>我在学习多线程---1<br>我在看代码-----0<br>我在学习多线程---2<br>我在看代码-----1<br>我在学习多线程---3<br>我在看代码-----2<br>我在学习多线程---4<br>我在学习多线程---5<br>我在学习多线程---6<br>我在看代码-----3<br>我在学习多线程---7<br>我在学习多线程---8<br>我在学习多线程---9<br>我在学习多线程---10<br>我在学习多线程---11<br>我在学习多线程---12<br>我在学习多线程---13<br>我在学习多线程---14<br>我在学习多线程---15<br>我在学习多线程---16<br>我在学习多线程---17<br>我在学习多线程---18<br>我在看代码-----4<br>我在学习多线程---19<br>我在看代码-----5<br>我在看代码-----6<br>我在看代码-----7<br>我在看代码-----8<br>我在看代码-----9<br>我在看代码-----10<br>我在看代码-----11<br>我在看代码-----12<br>我在看代码-----13<br>我在看代码-----14<br>我在看代码-----15<br>我在看代码-----16<br>我在看代码-----17<br>我在看代码-----18<br>我在看代码-----19
    复制代码
  • 案例:
    1. package Demo032;<br>​<br>/**<br> * @Author: H-YONG-8<br> * @DATA: 2023/4/20<br> * JavaSE<br> *多个线程同时操作同一个对象<br> * 买火车票的例子<br> */<br>public class TestThread4 implements Runnable{<br>​<br>    private int a = 10;<br>​<br>    @Override<br>    public void run() {<br>        while (true){<br>            if (a<=0){<br>                break;<br>            }<br>            System.out.println(Thread.currentThread().getName()+"拿到了"+a--+"票");<br>        }<br>​<br>    }<br>​<br>    public static void main(String[] args) {<br>        TestThread4 b = new TestThread4();<br>        new Thread(b,"小明").start();<br>        new Thread(b,"老师").start();<br>        new Thread(b,"黄牛").start();<br>        new Thread(b,"警察").start();<br>    }<br>}<br>​<br>执行结果:<br>小明拿到了9票<br>老师拿到了8票<br>警察拿到了7票<br>黄牛拿到了10票<br>警察拿到了4票<br>警察拿到了2票<br>警察拿到了1票<br>老师拿到了5票<br>小明拿到了6票<br>黄牛拿到了3票<br>​
    复制代码
8.2.3 实现Callable接口


  • 实现Callable接口,需要返回值类型
  • 重写call方法,需要抛出异常
  • 创建目标对象
  • 创建执行服务:ExecutorService ser =Executors.newFixedThreadPool(1);
  • 提交执行:Futre < Boolean>result1 =ser.submit(t1);
  • 获取结果:boolean r1 = result1.get()
  • 关闭服务:ser.shutdownNow();
  • 实例
    1. package Demo032;<br>​<br>/**<br> * @Author: H-YONG-8<br> * @DATA: 2023/4/20<br> * JavaSE<br> * 龟兔赛跑<br> * 首先来个赛道距离,然后要离距离越来越近<br> * 判断比赛是否结束<br> * 打印出胜利者<br> * 龟兔赛跑开始<br> * 模拟兔子睡觉<br> * 乌龟赢得比赛<br> */<br>public class TestThread3 implements Runnable{<br>​<br>    //胜利者<br>    private static String winner;<br>​<br>    @Override<br>    public void run() {<br>​<br>        //跑步开始<br>        for (int i = 0; i <= 100; i++) {<br>​<br>            //模拟兔子睡觉<br>            if(Thread.currentThread().getName().equals("兔子") && i%10 == 0){<br>                try {<br>                    Thread.sleep(1);<br>                } catch (InterruptedException e) {<br>                    e.printStackTrace();<br>                }<br>​<br>            }<br>​<br>            //判断比赛是否结束<br>            boolean flag = gameOver(i);<br>            //如果比赛结束了,就停止程序<br>            if(flag){<br>                break;<br>            }<br>            System.out.println(Thread.currentThread().getName()+"---->跑了"+i+"步");<br>        }<br>​<br>    }<br>​<br>    //判断是否完成比赛<br>    private boolean gameOver(int steps){<br>        //判断是否有胜利者<br>        if(winner!=null){//已经存在胜利者<br>            return true;<br>        }{<br>            if (steps >= 100){<br>                winner = Thread.currentThread().getName();<br>                System.out.println("winner ="+winner);<br>                return true;<br>            }<br>        }<br>        return false;<br>    }<br>​<br>    public static void main(String[] args) {<br>​<br>        //设置赛道<br>        TestThread3 race = new TestThread3();<br>​<br>        new Thread(race,"兔子").start();<br>        new Thread(race,"乌龟").start();<br>​<br>    }<br>}<br>​<br>​<br>运行结果:<br>乌龟---->跑了0步<br>乌龟---->跑了1步<br>乌龟---->跑了2步<br>乌龟---->跑了3步<br>乌龟---->跑了4步<br>乌龟---->跑了5步<br>乌龟---->跑了6步<br>乌龟---->跑了7步<br>乌龟---->跑了8步<br>乌龟---->跑了9步<br>乌龟---->跑了10步<br>乌龟---->跑了11步<br>乌龟---->跑了12步<br>乌龟---->跑了13步<br>乌龟---->跑了14步<br>乌龟---->跑了15步<br>乌龟---->跑了16步<br>乌龟---->跑了17步<br>乌龟---->跑了18步<br>乌龟---->跑了19步<br>乌龟---->跑了20步<br>乌龟---->跑了21步<br>乌龟---->跑了22步<br>乌龟---->跑了23步<br>乌龟---->跑了24步<br>乌龟---->跑了25步<br>乌龟---->跑了26步<br>乌龟---->跑了27步<br>乌龟---->跑了28步<br>乌龟---->跑了29步<br>乌龟---->跑了30步<br>乌龟---->跑了31步<br>乌龟---->跑了32步<br>乌龟---->跑了33步<br>乌龟---->跑了34步<br>乌龟---->跑了35步<br>乌龟---->跑了36步<br>乌龟---->跑了37步<br>乌龟---->跑了38步<br>乌龟---->跑了39步<br>乌龟---->跑了40步<br>乌龟---->跑了41步<br>乌龟---->跑了42步<br>乌龟---->跑了43步<br>乌龟---->跑了44步<br>乌龟---->跑了45步<br>乌龟---->跑了46步<br>乌龟---->跑了47步<br>乌龟---->跑了48步<br>乌龟---->跑了49步<br>乌龟---->跑了50步<br>乌龟---->跑了51步<br>乌龟---->跑了52步<br>乌龟---->跑了53步<br>乌龟---->跑了54步<br>乌龟---->跑了55步<br>乌龟---->跑了56步<br>乌龟---->跑了57步<br>兔子---->跑了0步<br>乌龟---->跑了58步<br>兔子---->跑了1步<br>兔子---->跑了2步<br>兔子---->跑了3步<br>兔子---->跑了4步<br>兔子---->跑了5步<br>兔子---->跑了6步<br>兔子---->跑了7步<br>兔子---->跑了8步<br>兔子---->跑了9步<br>乌龟---->跑了59步<br>乌龟---->跑了60步<br>乌龟---->跑了61步<br>乌龟---->跑了62步<br>乌龟---->跑了63步<br>乌龟---->跑了64步<br>乌龟---->跑了65步<br>乌龟---->跑了66步<br>乌龟---->跑了67步<br>乌龟---->跑了68步<br>乌龟---->跑了69步<br>乌龟---->跑了70步<br>乌龟---->跑了71步<br>乌龟---->跑了72步<br>乌龟---->跑了73步<br>乌龟---->跑了74步<br>乌龟---->跑了75步<br>乌龟---->跑了76步<br>乌龟---->跑了77步<br>乌龟---->跑了78步<br>乌龟---->跑了79步<br>乌龟---->跑了80步<br>乌龟---->跑了81步<br>乌龟---->跑了82步<br>乌龟---->跑了83步<br>乌龟---->跑了84步<br>乌龟---->跑了85步<br>乌龟---->跑了86步<br>乌龟---->跑了87步<br>乌龟---->跑了88步<br>乌龟---->跑了89步<br>乌龟---->跑了90步<br>乌龟---->跑了91步<br>乌龟---->跑了92步<br>乌龟---->跑了93步<br>乌龟---->跑了94步<br>乌龟---->跑了95步<br>乌龟---->跑了96步<br>乌龟---->跑了97步<br>乌龟---->跑了98步<br>兔子---->跑了10步<br>兔子---->跑了11步<br>兔子---->跑了12步<br>兔子---->跑了13步<br>兔子---->跑了14步<br>兔子---->跑了15步<br>兔子---->跑了16步<br>兔子---->跑了17步<br>兔子---->跑了18步<br>兔子---->跑了19步<br>乌龟---->跑了99步<br>winner =乌龟
    复制代码
8.3 lamda表达式和静态代理模式

8.3.1 lamda表达式


  • 希腊字母表中排序第十一位的字母,英文名为Lambda
  • 避免匿名内部类定义过多。
  • 代理模式其实就是通过一个类去代替另一个类去做一些操作。
  • 其实质属于函数式编程的概念
    1.  public class CallableTest {<br>      public static void main(String[] args) throws ExecutionException, InterruptedException,TimeoutException{<br>          //创建一个线程池<br>          ExecutorService executor = Executors.newCachedThreadPool();<br>          Future<String> future = executor.submit(()-> {<br>                  TimeUnit.SECONDS.sleep(5);<br>                  return "CallableTest";<br>          });<br>          System.out.println(future.get());<br>          executor.shutdown();<br>      }<br>  }<br>​
    复制代码
  • 为什么要用lambda表达式

    • 避免匿名内部内定义过多
    • 代码更加简洁
    • 去掉没有意义的代码,只留下核心代码

  • Functional Interface(函数式接口)是lamda表达式的关键。

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

  • 实例:
    1. (params) ->expression[表达式]<br>(params) ->statement[语句]<br>(params) ->{statements}
    复制代码
  • 总结:

    • lambda表达式只能有一行代码的情况下才能简化成一行,如果有多行,就要用代码块包裹
    • 前提是函数式接口,只有一个方法。
    • 多个参数类型也可以去掉参数类型,要去掉都要去掉。

8.3.2 静态代理模式


  • 为其他对象提供一个代理以控制对这个对象的访问。
  • 主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
  • 代理模式的元素是:共同接口、代理对象、目标对象。
  • 代理模式的行为:由代理对象执行目标对象的方法、由代理对象扩展目标对象的方法。
  • 代理模式的宏观特性:对客户端只暴露出接口,不暴露它以下的架构。
  • 好处多多:中间隔离了一层,更加符合开闭原则
  • 流程图

  • 实例:
    1. package Demo032;<br>​<br>/**<br> * @Author: H-YONG-8<br> * @DATA: 2023/4/21<br> * JavaSE<br> * 推导lambda表达式<br> */<br>public class TestThread5 {<br>​<br>    //3静态内部类,实现类<br>    static class Like2 implements Ilike{<br>        @Override<br>        public void lambda(){<br>            System.out.println("i like lambda2");<br>        }<br>    }<br>​<br>    public static void main(String[] args) {<br>        Ilike like = new Like();<br>        like.lambda();<br>​<br>        like=new Like2();<br>        like.lambda();<br>​<br>​<br>        //4局部内部类<br>        class Like3 implements Ilike{<br>            @Override<br>            public void lambda(){<br>                System.out.println("i like lambda3");<br>            }<br>        }<br>        like=new Like3();<br>        like.lambda();<br>​<br>        //5匿名内部类<br>        like = new  Ilike() {<br>            @Override<br>            public void lambda() {<br>                System.out.println("i like lambda4");<br>            }<br>        };<br>        like.lambda();<br>​<br>        //6用lambda简化<br>        like = ()->{<br>            System.out.println("i like lambda5");<br>        };<br>        like.lambda();<br>​<br>    }<br>}<br>​<br>//1定义一个函数式接口<br>interface Ilike{<br>    void lambda();<br>}<br>//2实现类<br>class Like implements Ilike{<br>    @Override<br>    public void lambda(){<br>        System.out.println("i like lambda");<br>    }<br>}<br>​
    复制代码
  • 总结:

    • 真实对象和代理对象都要事先同一个接口
    • 代理对象要代理真实角色,
    • 好处:

      • 代理对象可以做真实对象做不了的事情
      • 真实对象专注于自己的事情


8.4 线程状态


  • 线程五大状态



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


  • 不推荐使用JDK提供的stop()、destory()方法。
  • 推荐让线程自己停下来
  • 建议使用一个标志未进行终止变量当flag=false,则终止线程运行。
  • 测试停止线程
    1. package Demo032;<br>​<br>/**<br> * @Author: H-YONG-8<br> * @DATA: 2023/4/21<br> * JavaSE<br> */<br>public class TeatThread6 {<br>​<br>    public static void main(String[] args) {<br>        WeddingCompany weddingCompany = new WeddingCompany(new You());<br>        weddingCompany.HappyMarry();<br>    }<br>}<br>​<br>interface Marry{<br>    void HappyMarry();<br>}<br>​<br>//真实角色<br>class You implements Marry{<br>    @Override<br>    public void HappyMarry() {<br>        System.out.println("小明要结婚了");<br>    }<br>}<br>​<br>//代理角色,帮助结婚<br>class WeddingCompany implements Marry{<br>​<br>    //代理真实角色<br>    private Marry target;<br>    //构造函数<br>    public WeddingCompany(Marry target) {<br>        this.target = target;<br>    }<br>​<br>    @Override<br>    public void HappyMarry() {<br>​<br>        before();<br>        this.target.HappyMarry();//这就是真实对象<br>        after();<br>    }<br>​<br>    private void after() {<br>        System.out.println("结婚之后");<br>    }<br>​<br>    private void before() {<br>        System.out.println("结婚前");<br>    }<br>}<br>​
    复制代码
8.4.2 线程休眠

<ol  start=""><li >sleep(时间)指定当前线程阻塞的毫秒数;
<li >sleep存在异常InterruptedExcepiton;
<li >sleep时间到达后线程进入就绪状态;
<li >sleep可以模拟网络延迟,倒计时等
<li >每一个对象都有一个锁,sleep不会释放锁
<li >倒计时实例
  1. package Demo032;<br>​<br>/**<br> * @Author: H-YONG-8<br> * @DATA: 2023/4/21<br> * JavaSE<br> * 测试停止线程<br> * 注意死循环<br> */<br>public class TestStop implements Runnable{<br>​<br>    //1.设置一个标识位<br>    private boolean flag = true;<br>​<br>    //重写方法<br>    @Override<br>    public void run() {<br>        int i = 0;<br>        while (flag){<br>            System.out.println("线程正在运行"+i++);<br>        }<br>    }<br>​<br>    //2设置一个公开的方法,停止线程,转换标志位<br>    public void stop(){<br>        this.flag = false;<br>    }<br>​<br>    public static void main(String[] args) {<br>​<br>        TestStop testStop = new TestStop();<br>        new Thread(testStop).start();<br>​<br>        for (int i = 0; i < 1000; i++) {<br>            System.out.println("main"+i);<br>            if(i == 900){<br>                //调用stop方法,切换标志位,让线程停止<br>                testStop.stop();<br>                System.out.println("线程该停止了");<br>            }<br>        }<br>    }<br>}
复制代码
</ul>8.4.6 线程优先级


  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪一个线程来执行。
  • 线程的优先级用数字表示,范围从1~10.

    • Thread.MIN_PRIORITY = 1;
    • Thread.MAX_PRIORITY = 10;
    • Thread.NORM_PRIORITY =5;

  • 使用以下方式改变或者获取优先级

    • getPriority().setPriority(int xxx)

  • 实例
    1. package Demo032;<br>​<br>import java.text.SimpleDateFormat;<br>import java.util.Date;<br>​<br>/**<br> * @Author: H-YONG-8<br> * @DATA: 2023/4/21<br> * JavaSE<br> */<br>public class TestSleep2 {<br>    public static void main(String[] args) throws InterruptedException {<br>        tenDwon();<br>​<br>    //模拟倒计时<br>    public static void tenDwon() throws InterruptedException {<br>        int num = 10;<br>​<br>        while (true){<br>            Thread.sleep(1000);<br>            System.out.println(num--);<br>            if (num<=0){<br>                break;<br>            }<br>        }<br>    }<br>}<br>​
    复制代码
8.4.7 守护线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 实例
    1. package Demo032;<br>​<br>import java.text.SimpleDateFormat;<br>import java.util.Date;<br>​<br>/**<br> * @Author: H-YONG-8<br> * @DATA: 2023/4/21<br> * JavaSE<br> */<br>public class TestSleep2 {<br>    public static void main(String[] args){ <br>        //获取系统当前时间<br>        Date startTime = new Date(System.currentTimeMillis());<br>        while (true) {<br>            try {<br>                Thread.sleep(1000);<br>                //更新系统时间<br>                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));<br>                startTime = new Date(System.currentTimeMillis());<br>            } catch (InterruptedException e) {<br>                e.printStackTrace();<br>            }<br>        }<br>​<br>    }<br>}<br>​
    复制代码
8.5 线程同步

8.5.1 并发


  • 并发:同一个对象被多个线程同时操作。
  • 所谓并发编程是指在一台处理器上 “同时” 处理多个任务。并发是在同一实体上的多个事件。多个事件在同一时间间隔发生。
  • 并发编程,从程序设计的角度来说,是希望通过某些机制让计算机可以在一个时间段内,执行多个任务。从计算机 CPU 硬件层面来说,是一个或多个物理 CPU 在多个程序之间多路复用,提高对计算机资源的利用率。从调度算法角度来说,当任务数量多于 CPU 的核数时,并发编程能够通过操作系统的任务调度算法,实现多个任务一起执行。
  • 并发编程有三大特性:

    • 原子性;
    • 可见性;
    • 有序性。

8.5.2 线程同步

<ol  start=""><li >线程有自己的私有数据,比如栈和寄存器,同时与其它线程共享相同的虚拟内存和全局变量等资源。 在一般情况下,创建一个线程是不能提高程序的执行效率的,所以要创建多个线程。但是当多个线程同时读写同一份共享资源的时候,会引起冲突,例如在多个线程同时对同一个内存地址进行写入,由于CPU时间调度上的问题,写入数据会被多次的覆盖,所以就要使线程同步。这时候就需要引入线程同步机制使各个线程排队一个一个的对共享资源进行操作,而不是同时进行。
<li >简单的说就是,在多线程编程里面,一些数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
<li >处理多线程问题时,多线程访问同一个对象,并且某些线程还想修改这个对象。这个时候我们就需要线程同步。
<li >线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成对列,等待前面线程使用完毕,下一个线程再使用。
<li >由于同一进程的多个线程共享一块存储空间,在方便的同时,也带来了访问冲突,为了保证数据在方法中被访问时的正确性,再访问时加入锁机制,当一个线程获得对象的排他锁,多占资源,其他线程必须等待,使用后释放锁即可。

  • 会损失性能
  • 优先级倒置
<li >线程不安全案例
  1. package Demo032;<br>​<br>/**<br> * @Author: H-YONG-8<br> * @DATA: 2023/4/21<br> * JavaSE<br> * 礼让线程<br> */<br>public class TestYield {<br>​<br>    public static void main(String[] args) {<br>        MyYield myYield = new MyYield();<br>        new Thread(myYield,"a").start();<br>        new Thread(myYield,"b").start();<br>​<br>    }<br>}<br>​<br>class MyYield implements Runnable{<br>    @Override<br>    public void run() {<br>        System.out.println(Thread.currentThread().getName()+"线程开始执行");<br>        Thread.yield();//礼让<br>        System.out.println(Thread.currentThread().getName()+"线程停止执行");<br>    }<br>}
复制代码
 
</ul> 
 
8.6 线程通信问题


 




<ul  data-mark="+"><li >实例
[code]/**
* 测试:生产者消费者模型-->利用缓冲区解决:管程法
*/
public class Demo33_ThreadPC {
   public static void main(String[] args) {
       SynContainer synContainer = new SynContainer();
       new Producer(synContainer).start();
       new Consumer(synContainer).start();
   }
}

//生产者
class Producer extends Thread {
   //容缓冲区
   SynContainer container;

   public Producer(SynContainer container) {
       this.container = container;
   }

   //生产
   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           container.push(new Product(i));
           System.out.println("生产了" + i + "件产品");
       }
   }
}

//消费者
class Consumer extends Thread {
   //容缓冲区
   SynContainer container;

   public Consumer(SynContainer container) {
       this.container = container;
   }

   //消费
   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           System.out.println("消费了-->" + container.pop().id + "件产品");
       }
   }
}

//产品
class Product {
   int id;//产品编号

   public Product(int id) {
       this.id = id;
   }
}

//缓冲区
class SynContainer {
   //需要一个容器大小
   Product[] products = new Product[10];
   //容器计数器
   int count = 0;

   //生产者放入产品
   public synchronized void push(Product product) {
       //如果容器满了,需要等待消费者消费
       /*如果是if的话,假如消费者1消费了最后一个,这是index变成0此时释放锁被消费者2拿到而不是生产者拿到,这时消费者的wait是在if里所以它就直接去消费index-1下标越界,如果是while就会再去判断一下index得值是不是变成0了*/
       while (count == products.length) {
           //通知消费者消费,等待生产
           try {
               this.wait();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
       //如果没有满,需要丢入产品
       products[count] = product;
       count++;
       //通知消费者消费
       this.notifyAll();
   }

   //消费者消费产品
   public synchronized Product pop() {
       //判断是否能消费
       while (count

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

水军大提督

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表