javaday13

目录

Day13---多线程+售票. 1

1       进程. 1

1.1      概念. 1

1.2      特点. 1

1.3      CPU分时调度. 1

2       线程. 1

2.1      概念. 1

2.2      进程和线程的关系. 1

3       多线程的特性. 1

3.1      随机性. 1

3.2      线程状态. 1

4       多线程创建1:继承Thread1

4.1      概述. 1

4.2      常用方法. 1

4.3      测试. 1

5       多线程创建2:实现Runnable接口. 1

5.1      概述. 1

5.2      常用方法. 1

5.3      测试. 1

5.4      比较. 1

6       售票案例. 1

6.1      方案1:继承Thread1

6.2      方案2:实现Runnable1

6.3      问题. 1

7       解决方案. 1

7.1      synchronized1

7.2      特点. 1

7.3      改造. 1

8       扩展. 1

8.1      JVM启动是单线程还是多线程?. 1

8.2      守护线程. 1

8.3      了解多线程的其他实现方式. 1

8.4      线程共享访问数据冲突. 1

 

Day13---多线程+售票

1    进程

1.1  概念

就是正在运行的程序。也就是代表了程序所占用的内存区域。

1.2  特点

独立性:进程是系统中独立存在的实体,它可以拥有自己的独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。

动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念,进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的。

并发:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。

并行:假设计算机有多个CPU,每个CPU处理一个进程,发生的现象叫并行.

1.3  CPU分时调度

 

时间片,即CPU分配给各个程序的时间,每个进程被分配一个时间段,称作它的时间片。即该进程允许运行的时间,使各个程序从表面上看是同时进行的。

如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程,将当前进程挂起。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换,而不会造成CPU资源浪费。当又切换到之前执行的进程,把现场恢复,继续执行。

在宏观上:我们可以同时打开多个应用程序,每个程序并行,同时运行。

在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。多核提高了并发能力。

2    线程

2.1  概念

线程是操作系统能够进行运算调度的最小单位。

它被包含在进程之中,是进程中的实际运作单位。

一个进程可以开启多个线程。

多线程扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。

简而言之,一个程序运行后至少一个进程,一个进程里包含一个线程(单线程)或者多个线程。

 

2.2  进程和线程的关系

 

从上图中可以看出一个操作系统中可以有多个进程,一个进程中可以有多个线程。

每个进程有自己独立的内存,每个线程共享一个进程中的内存,每个线程又有自己独立的内存。(记清这个关系,非常重要!)

    所以想使用线程技术,得先有进程,进程的创建是OS创建的,你能实现吗?不能,一般都是c或者c++语言完成的。

3    多线程的特性

3.1  随机性

 

3.2  线程状态

 

线程生命周期,总共有五种状态:

1)   新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

2)   就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

3)   运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

4)   阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态;

 

5)   根据阻塞产生的原因不同,阻塞状态又可以分为三种:

 

a)   等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

b)   同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

c)   其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

6)   死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

4    多线程创建1:继承Thread

4.1  概述

学习多线程的目的:提高CPU的执行效率。

 

 

Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。Start()方法是一个native方法,它将通知底层操作系统,最终由操作系统启动一个新线程,操作系统将执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。

模拟开启多个线程,每个线程调用run()方法

4.2  常用方法

Thread()

          分配新的 Thread 对象。

Thread(Runnable target)

          分配新的 Thread 对象。

Thread(Runnable target, String name)

          分配新的 Thread 对象。

Thread(String name)

          分配新的 Thread 对象。

 

static Thread currentThread()

          返回对当前正在执行的线程对象的引用。

 long getId()

          返回该线程的标识符。

 String getName()

          返回该线程的名称。

 int getPriority()

          返回线程的优先级。

 StackTraceElement[] getStackTrace()

          返回一个表示该线程堆栈转储的堆栈跟踪元素数组。

 Thread.State getState()

          返回该线程的状态。

 void run()

          如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。

 static void sleep(long millis)

          在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

static void sleep(long millis, int nanos)

          在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

 void start()

          使该线程开始执行;Java 虚拟机调用该线程的 run 方法。

        

4.3  测试

创建day13工程

创建cn.tedu.thread包

创建Test1_Thread.java

package cn.tedu.thread;

 

//测试多线程的实现

public class Test1_Thread {

    public static void main(String[] args) {

       //4, 创建线程对象

       //5, 刚刚创建好的线程,处于新建状态,暂时还没有执行权

//     Thread t = new MyThread();//使用默认的线程名Thread-0

       Thread t = new MyThread("渣渣辉1:");//使用指定的线程名

       Thread t2 = new MyThread("渣渣辉2:");//使用指定的线程名

      

       //6, 调用start()是让线程的状态从新建 变成 可运行 状态 , 等待CPU的调用来自动执行run()

       //7, 开启线程,执行顺序无法控制,由CPU的调度算法执行,谁抢到资源谁执行

       t.start();

       t2.start();

      

       //8, run()虽然也能正常的执行业务,但是他只会把业务按照顺序结构执行,不会发生多线程!

//     t.run();

//     t2.run();

    }

}

//创建多线程类

//实现多线程的方案1: 继承Thread类

class MyThread extends Thread{

 

    public MyThread() {}

    public MyThread(String name) { super(name); }

   

    //2, 多线程类的业务必须放在run()里

    //source-Override/implements methods-选择要重写的方法-ok

    //3, run()的默认实现是用super.run();

    @Override

    public void run() {

       for (int i = 0; i < 100; i++) {

           //getName()获取当前正在执行业务的线程名。如果不指定,程序会自动分配线程名 。如:Thread-0

           System.out.println(getName()+i);

       }

    }

}

 

 

5    多线程创建2:实现Runnable接口

5.1  概述

如果自己的类已经extends另一个类,就无法多继承,此时,可以实现一个Runnable接口。

5.2  常用方法

void run()

          使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。

5.3  测试

package cn.tedu.thread;

 

//多线程的实现2:实现Runnable接口

public class Test2_Runnable {

    public static void main(String[] args) {

       //3, 创建线程对象

       MyRunnable r = new MyRunnable();

       //4, 接口的实现类作为参数创建Thread对象

       Thread t = new Thread(r,"毛毛姐1:");//"毛毛姐:"是指定的线程名称

       //5, 启动线程start()

       t.start();

      

       Thread t2 = new Thread(r,"毛毛姐2:");//"毛毛姐:"是指定的线程名称

       t2.start();//抢占资源

    }

}

 

//创建多线程类

//1, implements Runnable 实现接口

class MyRunnable implements Runnable{

   

    //2, 接口里的抽象方法,重写

    @Override

    public void run() {

       for (int i = 0; i < 100; i++) {

           //Thread.currentThread() 获取正在执行任务的线程对象的引用

           System.out.println(Thread.currentThread().getName()+i);

       }

    }

}

 

5.4  比较

方式

优点

缺点

Thread

编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

线程类已经继承了Thread类,所以不能再继承其他父类

Runnable

线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。

在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

 

6    售票案例

设计4个售票窗口,总计售票100张。

用多线程的程序设计并写出代码。

6.1  方案1:继承Thread

package cn.tedu.ticket;

 

//售票窗口

public class Test3_Tickets {

    public static void main(String[] args) {

       //1, 创建多线程对象

       TicketThread t = new TicketThread("1号窗口:");

       //2, 开启线程

       t.start();

      

       //TODO 多线程卖票!!!

       TicketThread t2 = new TicketThread("2号窗口:");

       t2.start();

       TicketThread t3 = new TicketThread("3号窗口:");

       t3.start();

       TicketThread t4 = new TicketThread("4号窗口:");

       t4.start();

//问题:卖了400张票,卖了-1的票??!!

 

      

    }

}

//创建多线程类

class TicketThread extends Thread{

    //复制一行:ctrl+alt+下箭头

    //删除一行:ctrl+d

   

    //提供构造方法

    public TicketThread() {}

    public TicketThread(String name) { super(name); }

   

    //卖出100张票

    //static用来把资源作为类的资源存在,保证只加载一次.售出了-1的票.

    static private int tickets = 100;

 

    //卖票业务

    @Override

    public void run() {

       while(true) {//一直卖,死循环!!!!

           System.out.println(getName()+"卖票的余数:"+tickets--);

          

           if(tickets< 0) break;//设置出口!!

       }

    }

}

 

6.2  方案2:实现Runnable

package cn.tedu.ticket;

 

//卖票业务

public class Test4_Tickets2 {

    public static void main(String[] args) {

       //3, 创建多线程对象测试

       TicketRunnable r1 = new TicketRunnable();

//     TicketRunnable r2 = new TicketRunnable();

//     TicketRunnable r3 = new TicketRunnable();

//     TicketRunnable r4 = new TicketRunnable();

       Thread t1 = new Thread(r1,"窗口1:");

       Thread t2 = new Thread(r1,"窗口2:");

       Thread t3 = new Thread(r1,"窗口3:");

       Thread t4 = new Thread(r1,"窗口4:");

      

       //4, 开启线程

       t1.start();

       t2.start();

       t3.start();

       t4.start();

      

       //问题:new4次就卖400票,改成new一次保证4个窗口共卖100张票.

       //仍然没解决,出现了超卖现象

//     窗口4:票余数:-2

//     窗口3:票余数:-3

//     窗口2:票余数:-1

    }

}

//创建多线程的类

//1, 实现Runnable接口

class TicketRunnable implements Runnable{

    private int tickets = 100;

   

    //2,重写run()

    @Override

    public void run() {

       while(true) {

           try {

              Thread.sleep(100);//让程序进入阻塞状态,CPU调度其他资源

           } catch (InterruptedException e) {

              e.printStackTrace();

           }

           System.out.println(Thread.currentThread().getName()+"票余数:"+tickets--);

           if(tickets< 0 )  break;

       }

    }

}

6.3  问题

1、      每次创建线程对象,都会生成一个tickets变量值是100,创建4次对象就生成了400张票了。不符合需求,怎么解决呢?能不能把tickets变量在每个对象间共享,就保证多少个对象都是卖这100张票。--  用静态修饰

2、      产生超卖,-1张、-2张。

3、      产生重卖,同一张票卖给多人。

4、      多线程安全问题是如何出现的?常见情况是由于线程的随机性+访问延迟。

5、      以后如何判断程序有没有线程安全问题?在多线程程序中+有共享数据+多条语句操作共享数据。

7    解决方案

把有可能出现问题的代码包起来,一次只让一个线程执行。

通过sychronized关键字实现同步。

当多个对象操作共享数据时,可以使用同步锁解决线程安全问题。

7.1  synchronized

synchronized(对象){

    需要同步的代码;

}

7.2  特点

1、 前提1,同步需要两个或者两个以上的线程。

2、 前提2,多个线程间必须使用同一个锁。

3、 同步的缺点是会降低程序的执行效率,  为了保证线程安全,必须牺牲性能。

4、 可以修饰方法称为同步方法,使用的锁对象是this。

5、 可以修饰代码块称为同步代码块,锁对象可以任意。

7.3  改造

只new一次就是100张票?把票变成静态的?

package cn.tedu.ticket;

 

//卖票业务

public class Test4_Tickets2 {

    public static void main(String[] args) {

       // 3, 创建多线程对象测试

       TicketRunnable r1 = new TicketRunnable();

//     TicketRunnable r2 = new TicketRunnable();

//     TicketRunnable r3 = new TicketRunnable();

//     TicketRunnable r4 = new TicketRunnable();

       Thread t1 = new Thread(r1, "窗口1:");

       Thread t2 = new Thread(r1, "窗口2:");

       Thread t3 = new Thread(r1, "窗口3:");

       Thread t4 = new Thread(r1, "窗口4:");

 

       // 4, 开启线程

       t1.start();

       t2.start();

       t3.start();

       t4.start();

 

       // 问题:new4次就卖400票,改成new一次保证4个窗口共卖100张票.

       // 仍然没解决,出现了超卖现象

//     窗口4:票余数:-2

//     窗口3:票余数:-3

//     窗口2:票余数:-1

    }

}

 

//创建多线程的类

//1, 实现Runnable接口

class TicketRunnable implements Runnable {

    private int tickets = 100;

 

    // 2,重写run()

    @Override

    public void run() {

       //牺牲效率,保证数据的安全性

       synchronized (this) {

// new Object() 失败:每个线程都创建自己的对象加锁,并没有对共享资源加锁

//         synchronized (new Object()) {//a, 锁起来会发生线程安全的代码,产生同步的效果。

           while (true) {

              if (tickets > 0) {

                  System.out.println(Thread.currentThread().getName() + "票余数:" + tickets--);

              } else {

                  break;

              }

           }

       }

    }

}

8    扩展

8.1  JVM启动是单线程还是多线程?

多线程,最少要启动main线程和GC线程。

8.2  守护线程

l  setDaemon(true)

后台线程、守护线程

JVM虚拟机退出条件,是所有前台线程结束,当所有前台线程结束,虚拟机会自动退出

不会等待后台线程结束

 例如:垃圾回收器是一个后台线程 

package day13;

 

import java.text.SimpleDateFormat;

import java.util.Date;

import java.util.Scanner;

 

public class Test3_v3 {

    public static void main(String[] args) {

       T1 t1 = new T1();

       t1.start();

 

       Thread t2 = new Thread() {

           @Override

           public void run() {

              System.out.println("按回车捅醒 t1");

              new Scanner(System.in).nextLine();

              t1.interrupt();

           }

       };

       //虚拟机不会等待后代线程结束

       //所有前台线程结束时,虚拟机会自动退出

       t2.setDaemon(true);

       t2.start();

    }

 

    static class T1 extends Thread {

       @Override

       public void run() {

           SimpleDateFormat f =

                  new SimpleDateFormat("HH:mm:ss");     

           for(int i=0; i<10; i++) {

              String s = f.format(new Date());

              System.out.println(s);

              try {

                  Thread.sleep(1000);

              } catch (InterruptedException e) {

                  System.out.println("被打断");

                  break;

              }

           }

       }

    }

 

}

 

8.3  了解多线程的其他实现方式

Callable

ExecutorService

ThreadPoolExecutor

了解线程并发包java.util.concurrent

8.4  线程共享访问数据冲突

package day13;

 

import java.util.Arrays;

 

public class Test6 {

  static char[] a = {'-','-','-','-','-'};

  static char c = '*';

  public static void main(String[] args) {

      Thread t1 = new Thread() {

          @Override

          public void run() {

             while(true) {

                for (int i = 0; i < a.length; i++) {

                   a[i] = c;

                }

                

                c = (c=='*'?'-':'*');

             }

          }

      };

      Thread t2 = new Thread() {

          @Override

          public void run() {

             while(true) {

                System.out.println(Arrays.toString(a));

             }

          }

      };

    

      t1.start();

      t2.start();

  }

}

 

posted @ 2020-05-25 16:24  白驼山庄庄主  阅读(115)  评论(0编辑  收藏  举报