• JavaSE学习总结第23天_多线程1


     

    23.01  多线程程序的引入

    如果一个程序只有一个执行流程,所以这样的程序就是单线程程序。

    如果一个程序有多条执行流程,那么,该程序就是多线程程序。

    23.02  进程概述及多进程的意义

    要想说线程,首先必须得知道进程,因为线程是依赖于进程存在的

    进程:正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。

     

    多进程意义:多进程的作用不是提高执行速度,而是提高CPU的使用率

    单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),所以我们常见的操作系统都是多进程操作系统。

    比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。

     

    对于单核计算机来讲,游戏进程和音乐进程不是同时运行的,因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。

    23.03  线程概述及多线程的意义

    在一个进程内部又可以执行多个任务,而这每一个任务就可以看成是一个线程。线程是程序中单个顺序的控制流,是程序使用CPU的基本单位。

    线程:

    是进程中的单个顺序控制流,是一条执行路径

    一个进程如果只有一条执行路径,则称为单线程程序。

    一个进程如果有多条执行路径,则称为多线程程序。

    多线程意义:   多线程的作用也不是提高执行速度,而是为了提高应用程序的使用率。

    多个线程共享同一个进程的资源(堆内存和方法区),但是栈内存是独立的,一个线程一个栈。所以他们仍然是在抢CPU的资源执行。一个时间点上只有能有一个线程执行。而且谁抢到,这个不一定,所以,造成了线程运行的随机性。

    23.04  并行和并发的区别

    注意两个词汇的区别:并行和并发

    并行是逻辑上同时发生,指在某一个时间内同时运行多个程序

    并发是物理上同时发生,指在某一个时间点同时运行多个程序

    23.05  Java程序运行原理和JVM的启动是多线程的吗

    Java程序运行原理:java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个“主线程”,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。

    由于JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的

    23.06  实现多线程及多线程方式1的思路

    由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。Java是不能直接调用系统功能的,但是Java可以去调用C/C++写好的程序来实现多线程程序。由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,然后提供一些类供我们使用。我们就可以实现多线程程序了。

    Java提供的类 Thread

    通过查看API,知道有2中方式实现多线程程序。

    方式1:继承Thread类

    步骤:

    1:自定义类MyThread继承Thread类。

    2:MyThread类重写run()方法

    3:创建对象

    4:启动线程

    23.07  多线程方式1的代码实现

    例:

     1 public class Practice 
     2 {
     3     public static void main(String[] args)
     4     {
     5         //创建对象
     6         //MyThread mt = new MyThread();
     7         //run()方法直接调用其实就相当于普通的方法调用,所以看到的是单线程的效果
     8         //要想看到多线程的效果,就必须使用另一个方法:start()
     9         //mt.run();
    10         // 创建两个线程对象
    11         MyThread my1 = new MyThread();
    12         MyThread my2 = new MyThread();
    13 
    14         my1.start();
    15         my2.start();
    16         
    17     }
    18 }
    19 //继承Thread类
    20 class MyThread extends Thread 
    21 {
    22     //重写run()方法
    23     @Override
    24     public void run() 
    25     {
    26         // 一般来说,被线程执行的代码肯定是比较耗时的。所以用循环改进
    27         for (int x = 0; x < 200; x++) 
    28         {
    29             System.out.println(x);
    30         }
    31     }
    32 }

    run()和start()的区别

    run():仅仅是封装被线程执行的代码,直接调用是普通方法

    start():首先启动了线程,然后再由jvm去调用该线程的run()方法

    继承Thread类的类为什么要重写run()方法?

    不是类中的所有代码都需要被线程执行的。而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。

    23.08  获取和设置线程对象名称

    public final String getName():返回该线程的名称

    public final void setName(String name):改变线程名称,使之与参数 name 相同。

    例:

     1 public class Practice 
     2 {
     3     public static void main(String[] args)
     4     {
     5 //        MyThread my1 = new MyThread();
     6 //        MyThread my2 = new MyThread();
     7 //
     8 //        //调用方法设置名称
     9 //        my1.setName("线程1");
    10 //        my2.setName("线程2");
    11 //        //启动线程
    12 //        my1.start();
    13 //        my2.start();
    14         
    15         //带参构造方法给线程起名字
    16          MyThread my1 = new MyThread("线程1");
    17          MyThread my2 = new MyThread("线程2");
    18          my1.start();
    19          my2.start();
    20          
    21         //获取main方法所在的线程对象的名称
    22         //public static Thread currentThread():返回当前正在执行的线程对象
    23         System.out.println(Thread.currentThread().getName());
    24         
    25     }
    26 }
    27 class MyThread extends Thread 
    28 {
    29     
    30     public MyThread()
    31     {
    32         super();
    33     }
    34     
    35     public MyThread(String name) 
    36     {
    37         super(name);
    38     }
    39     @Override
    40     public void run() 
    41     {
    42         for (int x = 0; x < 200; x++) 
    43         {
    44             System.out.println(getName()+":"+x);
    45         }
    46     }
    47 }

    运行结果:

    main
    线程1:0(省略部分结果)
    线程2:8(省略部分结果)

    通过Thread类的getName( )方法可以获取线程的名称,默认的命名方式是:Thread-编号(从0开始)

    为什么名称是:Thread-编号?

    看源码:

     1 class Thread
     2 {
     3     private char name[];
     4     private static int threadInitNumber;
     5     private static synchronized int nextThreadNum() 
     6     {
     7         return threadInitNumber++;
     8     }
     9     public Thread() 
    10     {
    11         init(null, null, "Thread-" + nextThreadNum(), 0);
    12     }
    13     public final String getName() 
    14     {
    15         return String.valueOf(name);
    16     }
    17     private void init(ThreadGroup g, Runnable target, String name,long stackSize) 
    18     {
    19         init(g, target, name, stackSize, null);
    20     }
    21     
    22     private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc) 
    23     {
    24         //省略部分代码
    25         this.name = name.toCharArray();
    26     }
    27 }

    23.09  线程调度及获取和设置线程优先级

    线程调度:

    假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?

    线程有两种调度模型:

    分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片

    抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。

    Java使用的是抢占式调度模型。

     

    设置和获取线程优先级

    public final int getPriority():返回线程的优先级。

    public final void setPriority(int newPriority):更改线程的优先级。

    例:

     1 public class Practice 
     2 {
     3     public static void main(String[] args)
     4     {
     5         MyThread my1 = new MyThread();
     6         MyThread my2 = new MyThread();
     7         MyThread my3 = new MyThread();
     8         
     9         //设置优先级,最小为1,最大为10
    10         my3.setPriority(9);
    11         //获取优先级,默认为5
    12         System.out.println(my1.getPriority());//5
    13         System.out.println(my2.getPriority());//5
    14         System.out.println(my3.getPriority());//9
    15         
    16         my1.start();
    17         my2.start();
    18         my3.start();//获取的 CPU 时间片相对多一些
    19     }
    20 }
    21 class MyThread extends Thread 
    22 {
    23     @Override
    24     public void run() 
    25     {
    26         for (int x = 0; x < 200; x++) 
    27         {
    28             System.out.println(getName()+":"+x);
    29         }
    30     }
    31 }

    23.10  线程控制之休眠线程

    public static void sleep(long millis)throws InterruptedException

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

    例:

     1 class MyThread extends Thread 
     2 {
     3     @Override
     4     public void run() 
     5     {
     6         for (int x = 0; x < 5; x++) 
     7         {
     8             System.out.println(getName()+":"+x+":"+new Date());
     9             try 
    10             {
    11                 //休眠1秒
    12                 Thread.sleep(1000);
    13             } 
    14             catch (InterruptedException e) 
    15             {
    16                 e.printStackTrace();
    17             }
    18         }
    19     }
    20 }

    23.11  线程控制之加入线程

    public final void join()throws InterruptedException

    等待该线程终止。

    例:

     1 public class Practice 
     2 {
     3     public static void main(String[] args)
     4     {
     5         MyThread my1 = new MyThread();
     6         MyThread my2 = new MyThread();
     7         MyThread my3 = new MyThread();
     8         
     9         my1.setName("线程1");
    10         my2.setName("线程2");
    11         my3.setName("线程3");
    12         
    13         my1.start();
    14         try 
    15         {
    16             //等待线程1线程,线程1运行完成后,线程2线程3开始争夺资源
    17             my1.join();
    18         } 
    19         catch (InterruptedException e) 
    20         {
    21             e.printStackTrace();
    22         }
    23         my2.start();
    24         my3.start();
    25     }
    26 }

    23.12  线程控制之礼让线程

    public static void yield()

    暂停当前正在执行的线程对象,并执行其他线程。

    例:

     1 class MyThread extends Thread 
     2 {
     3     @Override
     4     public void run() 
     5     {
     6         for (int x = 0; x < 100; x++) 
     7         {
     8             System.out.println(getName()+":"+x);
     9             //暂停当前线程,并执行其他线程,不能保证机会均等
    10             Thread.yield();
    11         }
    12     }
    13 }

    23.13  线程控制之守护线程

    public final void setDaemon(boolean on)

    将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。

    例:

     1 public class Practice 
     2 {
     3     public static void main(String[] args)
     4     {
     5         MyThread my1 = new MyThread();
     6         MyThread my2 = new MyThread();
     7         
     8         my1.setName("线程1");
     9         my2.setName("线程2");
    10         
    11         //将该线程标记为守护线程
    12         my1.setDaemon(true);
    13         my2.setDaemon(true);
    14         
    15         my1.start();
    16         my2.start();
    17         
    18         Thread.currentThread().setName("主线程");
    19         for (int i = 0; i < 20; i++) 
    20         {
    21             System.out.println(Thread.currentThread().getName()+":"+i);
    22         }
    23     }
    24 }

    23.14  线程控制之中断线程

    1.public void interrupt() 中断线程。

    2.public final void stop()  停止线程,已过时。该方法具有固有的不安全性。

    例:

     1 public class Practice 
     2 {
     3     public static void main(String[] args)
     4     {
     5         MyThread my = new MyThread();
     6         my.setName("线程1");
     7         my.start();
     8         try 
     9         {
    10             Thread.sleep(2000);
    11             //2秒后终止my线程,使用stop()后面的语句都不会执行
    12             //my.stop();该方法已过时,具有不安全性
    13             my.interrupt();//后面的语句会执行
    14         } 
    15         catch (InterruptedException e) 
    16         {
    17             e.printStackTrace();
    18         }
    19     }
    20 }

    23.15  线程生命周期图解

    23.16  多线程方式2的思路及代码实现

    步骤:

    1:自定义类MyRunnable实现Runnable接口

    2:重写run()方法

    3:创建MyRunnable类的对象

    4:创建Thread类的对象,并把3步骤的对象作为构造参数传递

     1 public class Practice 
     2 {
     3     public static void main(String[] args)
     4     {
     5         // 创建MyRunnable类的对象
     6         MyRunnable my = new MyRunnable();
     7 
     8         // 创建Thread类的对象,并把C步骤的对象作为构造参数传递
     9         // 构造方法 Thread(Runnable target)
    10         // Thread t1 = new Thread(my);
    11         // Thread t2 = new Thread(my);
    12         // t1.setName("小明");
    13         // t2.setName("小红");
    14 
    15         // 构造方法 Thread(Runnable target, String name)
    16         Thread t1 = new Thread(my, "小明");
    17         Thread t2 = new Thread(my, "小红");
    18 
    19         t1.start();
    20         t2.start();
    21     }
    22 }
    23 class MyRunnable implements Runnable
    24 {
    25     @Override
    26     public void run() 
    27     {
    28         for (int i = 0; i < 200; i++) 
    29         {
    30             System.out.println(Thread.currentThread().getName()+":"+i);
    31         }
    32     }
    33 }

    23.17  实现接口方式的好处

    1.可以避免由于Java单继承带来的局限性。

    2.适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。

    23.18  继承Thread类的方式卖电影票案例

    某电影院电影票共有100张,有3个售票窗口售票,请设计一个程序模拟该电影院售票。

     1 public class Practice 
     2 {
     3     public static void main(String[] args)
     4     {
     5         sellTicket st1 = new sellTicket();
     6         sellTicket st2 = new sellTicket();
     7         sellTicket st3 = new sellTicket();
     8         
     9         st1.setName("窗口1");
    10         st2.setName("窗口2");
    11         st3.setName("窗口3");
    12         
    13         st1.start();
    14         st2.start();
    15         st3.start();
    16         
    17     }
    18 }
    19 class sellTicket extends Thread
    20 {
    21     // 定义100张票
    22     // private int tickets = 100;
    23     // 为了让多个线程对象共享这100张票,我们其实应该用静态修饰
    24     private static int tickets = 100;
    25 
    26     @Override
    27     public void run() 
    28     {
    29         // 定义100张票
    30         // 每个线程进来都会走这里,这样的话,每个线程对象相当于买的是自己的那100张票,这不合理,所以应该定义到外面
    31         // int tickets = 100;
    32 
    33         // 是为了模拟一直有票
    34         while (true) 
    35         {
    36             if (tickets > 0) 
    37             {
    38                 System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
    39             }
    40         }
    41     }
    42 }

    23.19  实现Runnable接口的方式卖电影票案例

     1 public class Practice 
     2 {
     3     public static void main(String[] args)
     4     {
     5         SellTicket st = new SellTicket();
     6         
     7         new Thread(st, "窗口1").start();
     8         new Thread(st, "窗口2").start();
     9         new Thread(st, "窗口3").start();
    10     }
    11 }
    12 class SellTicket implements Runnable 
    13 {
    14     // 定义100张票
    15     private int tickets = 100;
    16 
    17     @Override
    18     public void run() 
    19     {
    20         while (true) 
    21         {
    22             if (tickets > 0) 
    23             {
    24                 System.out.println(Thread.currentThread().getName() + "正在出售第"
    25                         + (tickets--) + "张票");
    26             }
    27         }
    28     }
    29 }

    23.20  买电影票出现了同票和负数票的原因分析

    例:

     1 class SellTicket implements Runnable 
     2 {
     3     // 定义100张票
     4     private int tickets = 100;
     5 
     6     @Override
     7     public void run() 
     8     {
     9         while (true) 
    10         {
    11             //该语句存在安全隐患,如果当票数还剩1张的时候,此时4个线程可能
    12             //由于CPU的随机切换而通过该语句,当执行下面的语句时就会出现负的
    13             //票数,同时也可能出现相同的票卖出多次
    14             if (tickets > 0) 
    15             {
    16                 try 
    17                 {
    18                     Thread.sleep(10);
    19                 } 
    20                 catch (InterruptedException e) 
    21                 {
    22                     e.printStackTrace();
    23                 }
    24                 System.out.println(Thread.currentThread().getName() + "正在出售第"
    25                         + (tickets--) + "张票");
    26             }
    27         }
    28     }
    29 }

    相同的票出现多次:CPU的一次操作必须是原子性的

    出现了负数的票:随机性和延迟导致的

    23.21  线程安全问题的产生原因分析

    线程安全问题产生的原因:

    1.多个线程在操作共享的数据

    2.操作共享数据的线程代码有多条

    当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生

    23.22  同步代码块的方式解决线程安全问题

    思想:把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,其他线程不能来执行。Java提供了:同步机制。

    同步代码块:

    synchronized(对象)

    {需要同步的代码;}

    例:

     1 public class SellTicket implements Runnable 
     2 {
     3     // 定义100张票
     4     private int tickets = 100;
     5     //创建锁对象
     6     private Object obj = new Object();
     7     @Override
     8     public void run() 
     9     {
    10         while (true) 
    11         {
    12             //锁对象必须是同一个
    13             synchronized(obj)
    14             {
    15                 if (tickets > 0) 
    16                 {
    17                     try 
    18                     {
    19                         Thread.sleep(100);
    20                     } 
    21                     catch (InterruptedException e) 
    22                     {
    23                         e.printStackTrace();
    24                     }
    25                     System.out.println(Thread.currentThread().getName() + "正在出售第"
    26                             + (tickets--) + "张票");
    27                 }
    28             }
    29         }
    30     }
    31 }

    23.23  同步的特点及好处和弊端

    同步的前提:多个线程且多个线程使用的是同一个锁对象

    同步的好处:同步的出现解决了多线程的安全问题

    同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

    23.24  同步代码块的锁及同步方法应用和锁的问题

    同步方法就是把同步关键字加到方法上

    例:

     1 //同步方法
     2     private synchronized void sellTicket()
     3     {
     4         if (tickets > 0) 
     5         {
     6             try 
     7             {
     8                 Thread.sleep(100);
     9             } 
    10             catch (InterruptedException e) 
    11             {
    12                 e.printStackTrace();
    13             }
    14             System.out.println(Thread.currentThread().getName() + "正在出售第"
    15                     + (tickets--) + "张票");
    16         }
    17     }

    1.同步代码块的锁对象是任意对象

    2.同步函数使用的锁是this

    3.静态的同步函数使用的锁是该函数所属的字节码文件对象可以用getClass方法获取,也可以用当前类名.class表示

    23.25  以前的线程安全的类回顾

    Collections中让集合同步功能

    例:

    // public static <T> List<T> synchronizedList(List<T> list)
    List<String> list1 = new ArrayList<String>();// 线程不安全
    List<String> list2 = Collections.synchronizedList(new ArrayList<String>()); // 线程安全
  • 相关阅读:
    单例设计模式
    程序员眼中的中国传统文化王阳明《传习录》10
    程序员眼中的中国传统文化王阳明《传习录》8
    程序员眼中的中国传统文化王阳明《传习录》16
    程序员眼中的中国传统文化王阳明《传习录》11
    程序员眼中的中国传统文化王阳明《传习录》15
    程序员眼中的中国传统文化王阳明《传习录》14
    程序员眼中的中国传统文化王阳明《传习录》9
    程序员眼中的中国传统文化王阳明《传习录》13
    程序员眼中的中国传统文化王阳明《传习录》12
  • 原文地址:https://www.cnblogs.com/zhy7201/p/4541393.html
Copyright © 2020-2023  润新知