• java中的多线程 // 基础


    java 中的多线程

    简介

      进程 : 指正在运行的程序,并具有一定的独立能力,即 当硬盘中的程序进入到内存中运行时,就变成了一个进程

      线程 : 是进程中的一个执行单元,负责当前程序的执行。线程就是CPU通向程序的路径

           一个进程中只有一个线程,单线程程序

           一个进程中是可以有多个线程的,这个应用程序是多线程程序

    程序的运行分类

      分时调度

        所有线程轮流使用CPU 的使用权,平均分配每个线程占用CPU 的时间

      抢占式调度

        优先让优先级高的线程使用CPU,如果线程的优先级相同,那么就会随机选择一个线程(线程的随机性)。

        java 使用的为抢占式调度

       抢占式调度简介:

        现在的操作系统都支持多进程并发运行,比如:一边用office ,一边使用QQ,一边看着视频  等等,

        看着好像这些程序都在同一时刻运行,实际上是 CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。

        对于CPU的一个核而言,某个时刻只能执行一个线程,而CPU 在多个线程之间切换的速度相对我们而言感觉要快,看上去就是在同一时刻运行。

        注意:

          多线程并不能提高程序的运行速度,但能够提高程序的运行效率,让CPU 的使用效率更高。

    多线程的由来  

      jvm启动后,必然有一个执行线程(路径)从main方法开始的,一直执行到main方法结束,这个线程在java中称之为主线程(main线程)。 

      若主线程遇到循环,并循环次数较多,则导致程序在指定的位置停留时间过长,无法马上执行下面的程序,则需要等待循环结束后才能够执行代码。效率慢。 

      主线程负责执行其中的一个循环,由另一个线程执行另一个循环,最终实现多部分代码同时执行。多线程之间互不影响。

     多线程的创建方式

      1、继承 Thread 类

        创建一个类,继承 Thread 类,并重写Thread 类的 run 方法

        创建对象,调用 run 方法 ,就相当于执行其他线程的 main 方法。

        步骤:

          1、自定义一个类,继承Thread 类

          2、重写Thread 类中的 run 方法 ,设置线程任务

          3、创建自定义类的实例

          4、实例化 Thread 类,并传入自定义的类

          4、调用start ,开启进程。

        示例:

     1 1、自定义类
     2 // 创建一个类,继承Thread
     3 public class Thread_01 extends Thread{
     4     // 重写run 方法,在run方法中定义线程任务
     5     public void run(){
     6         System.out.println(Thread.currentThread().getName());
     7     }
     8 }
     9 2、main方法
    10 public class ThreadDemo {
    11     public static void main(String[] args) {
    12         // 创建线程对象 t1
    13         Thread_01 t1 = new Thread_01();
    14         // 为了启动Thread_01 这个线程,需要实例化Thread,并传入自己的Thread_01实例
    15         Thread thread = new Thread(t1);
    16         // 通知CPU 要启动线程
    17         thread.start();
    18         System.out.println(Thread.currentThread().getName());
    19     }
    20 }
    多线程继承Thread 示例

    2、实现 Runnable 接口

         创建一个类,实现 Runnable 接口,重写 run  方法

        步骤:

          1、自定义一个类,实现 Runnable 接口

          2、重写run 方法,在run方法中设置线程任务

          3、在main 方法中,创建自定义类的实例化

          4、实例化Thread 类,并传入自定义类的实例化

          5、调用start 方法,开启进程

     1 1、自定义类,实现runnable 接口
     2 // 自定义类,实现Runnable 接口
     3 public class Runnable_01 implements Runnable{
     4     // 重写run 方法,在run方法中设置线程任务
     5     @Override
     6     public void run() {
     7         System.out.println(Thread.currentThread().getName());
     8     }
     9 
    10 }
    11 
    12 2、在main方法中,调用
    13 public class ThreadDemo {
    14     public static void main(String[] args) {
    15         // 创建线程对象 t1
    16         Runnable_01 t1 = new Runnable_01();
    17         // 为了启动Runnable_01 这个线程,需要实例化Thread,并传入自己的Runnable_01实例
    18         Thread thread = new Thread(t1);
    19         // 通知CPU 要启动线程
    20         thread.start();
    21         System.out.println(Thread.currentThread().getName());
    22     }
    23 }
    多线程实现 Runnable 接口

        注意:

          调用 start 方法,开启新线程。若没有调用start 方法,只调用run 方法,只是调用了一个方法而已,并没有开启新线程

          一个线程只能调用一次start 方法,若线程执行结束后,不能再调用。

    常用API 

     1 public class Demo {
     2     public static void main(String[] args) {
     3         // 调用currentThread().getName(),获取当前线程的名称
     4         System.out.println(Thread.currentThread().getName() + "123");
     5         // 调用currentThread().getId(),获取当前线程的标识符
     6         System.out.println(Thread.currentThread().getId());
     7         // 调用currentThread().getPriority(),获取当前线程的优先级
     8         System.out.println(Thread.currentThread().getPriority());
     9         // 调用currentThread().setName() 给当前线程设置新名称
    10         Thread.currentThread().setName("线程新名称");
    11         // 调用currentThread().getName(),获取当前线程的名称
    12         System.out.println(Thread.currentThread().getName() + "123");
    13         /**
    14          * 打印结果 :main123
    15          *         1
    16          *         5
    17          *         线程新名称123
    18          */
    19 
    20     }
    21 }
    线程API 使用示例

     

    线程安全

      若多线程调用全局变量时,会出现线程安全问题。

      即:使用java 模拟窗口卖票时,一个窗口就是一个线程,若同时卖票,可能会出现几个窗口同时卖一张票,或者卖出不存在的票(就剩一张票时,两个窗口同时卖出)

      所以,使用多线程时,要注意线程安全问题,解决线程安全问题有三种方式,

      方式一:同步代码块

        同步代码块:就是在方法块声明上加上 synchronized

    synchronized (锁对象) {
    	可能会产生线程安全问题的代码
    }
    

      注意: 

        同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。

        对象可以是this, 哪个对象调用,方法中的this就是哪个对象

      示例:

     1 /*
     2  * 开启3个线程,同时卖100张票
     3  */
     4 public class Demo01PayTicket {
     5     public static void main(String[] args) {
     6         //创建接口的实现类对象
     7         RunnableImpl r = new RunnableImpl();
     8         //创建线程对象
     9         Thread t0 = new Thread(r);
    10         Thread t1 = new Thread(r);
    11         Thread t2 = new Thread(r);
    12         //开启多线程
    13         t0.start();
    14         t1.start();
    15         t2.start();
    16     }
    17 }
    18 
    19 /*
    20  * 发现程序出现了安全问题:卖出了重复的票和不存在的票
    21  * 
    22  * 多线程安全问题的第一种解决方案:使用同步代码块
    23  * 
    24  * 注意:
    25  *         代码块中传递的锁对象必须保证唯一,多个线程使用的是同一个锁对象
    26  *         锁对象可以是任意的对象
    27  * 
    28  */
    29 public class RunnableImpl implements Runnable{
    30     
    31     //定义一个共享的票源
    32     private int ticket = 100;
    33     //创建一个锁对象
    34     Object obj = new Object();
    35 
    36     @Override
    37     public void run() {
    38         //让卖票重复执行
    39         while(true){
    40             //同步代码块
    41             synchronized (obj) {
    42                 //判断是否还有票
    43                 if(ticket>0){
    44                     
    45                     //提高安全问题出现的概率,增加一个sleep
    46                     try {
    47                         Thread.sleep(10);
    48                     } catch (InterruptedException e) {
    49                         e.printStackTrace();
    50                     }
    51                     
    52                     //进行卖票
    53                     System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
    54                     ticket--;
    55                 }
    56             }
    57         }    
    58     }
    59 }
    多线程 同步代码块

      方式二:同步方法

        1、同步方法:在方法声明上加上 synchronized

    public synchronized void method(){
       	可能会产生线程安全问题的代码
    } 

      同步方法中的锁对象是 this,哪个对象调用,方法中的this就是哪个对象

        2、静态同步方法:在方法声明上加上 static synchronized

    public static synchronized void method(){
         可能会产生线程安全问题的代码
    }
    

      静态同步方法中的锁对象不是对象,是本类的 .class 文件

       示例:

     1 /*
     2  * 开启3个线程,同时卖100张票
     3  */
     4 public class Demo01PayTicket {
     5     public static void main(String[] args) {
     6         //创建接口的实现类对象
     7         RunnableImpl r = new RunnableImpl();
     8         //创建线程对象
     9         Thread t0 = new Thread(r);
    10         Thread t1 = new Thread(r);
    11         Thread t2 = new Thread(r);
    12         //开启多线程
    13         t0.start();
    14         t1.start();
    15         t2.start();
    16     }
    17 }
    18 
    19 /*
    20  * 发现程序出现了安全问题:卖出了重复的票和不存在的票
    21  * 
    22  * 多线程安全问题的第二种解决方案:使用同步方法
    23  * 
    24  * 实现步骤:
    25  *         1.把访问了共享数据的代码提取出来放在一个方法中
    26  *         2.在方法上添加一个synchronized修饰符
    27  * 
    28  * 格式:
    29  *     修饰符 synchronized 返回值类型 方法名(参数){
    30  *         访问了共享数据的代码;
    31  *     }
    32  * 
    33  * 把选中的代码提取到方法中快捷键:alt+shift+m
    34  * 
    35  */
    36 public class RunnableImpl implements Runnable{
    37     
    38     //定义一个共享的票源
    39     private static int ticket = 100;
    40 
    41     @Override
    42     public void run() {
    43         //让卖票重复执行
    44         while(true){
    45             payTicketStatic();
    46         }
    47         
    48     }
    49     
    50     /*
    51      * 静态的同步方法,锁对象不是this
    52      * 静态优先于非静态加载到内存中,this是创建对象之后才有的
    53      * 锁对象是本类的class属性(反射-->class文件对象)
    54      */
    55     public static synchronized void payTicketStatic() {
    56         synchronized (RunnableImpl.class) {
    57             //判断是否还有票
    58             if(ticket>0){
    59                 //提高安全问题出现的概率,增加一个sleep
    60                 try {
    61                     Thread.sleep(10);
    62                 } catch (InterruptedException e) {
    63                     e.printStackTrace();
    64                 }
    65                 //进行卖票
    66                 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
    67                 ticket--;
    68             }
    69         }
    70     }
    71 
    72     /*
    73      * 定义一个卖票的方法
    74      * 使用synchronized修饰
    75      * 使用锁对象把方法锁住
    76      * 这个锁对象是谁?
    77      * 创建的实现类对象new RunnableImpl();
    78      * 也就是this,哪个对象调用的方法,方法中的this就是哪个对象
    79      */
    80     public synchronized void payTicket() {
    81         //System.out.println(this);//cn.itcast.demo08.RunnableImpl@bcda2d
    82         synchronized (this) {
    83             //判断是否还有票
    84             if(ticket>0){
    85                 //提高安全问题出现的概率,增加一个sleep
    86                 try {
    87                     Thread.sleep(10);
    88                 } catch (InterruptedException e) {
    89                     e.printStackTrace();
    90                 }
    91                 
    92                 //进行卖票
    93                 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
    94                 ticket--;
    95             }
    96         }
    97     }
    98 }
    多线程 同步方法 synchronized

      方式三:使用lock 锁

        Lock 是接口,  ReentrantLock 是Lock 的实现类

        API

          

        调用

          1、创建ReentrantLock 对象

          2、在可能产生安全问题代码前调用 lock() 方法,获得锁

          3、调用unlock()方法,解锁

        

      ReentrantLock rl = new ReentrantLock();
      //获得锁
      rl.LOCK
      可能会产生线程安全问题的代码
      Rl.unlock
    

      示例:

     1 /*
     2  * 开启3个线程,同时卖100张票
     3  */
     4 public class Demo01PayTicket {
     5     public static void main(String[] args) {
     6         //创建接口的实现类对象
     7         RunnableImpl r = new RunnableImpl();
     8         //创建线程对象
     9         Thread t0 = new Thread(r);
    10         Thread t1 = new Thread(r);
    11         Thread t2 = new Thread(r);
    12         //开启多线程
    13         t0.start();
    14         t1.start();
    15         t2.start();
    16     }
    17 }
    18 
    19 /*
    20  * 发现程序出现了安全问题:卖出了重复的票和不存在的票
    21  * 
    22  * 多线程安全问题的第三种解决方案:使用Lock锁
    23  * java.util.concurrent.locks.Lock接口
    24  * Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
    25  * JDK1.5之后出现的新特性
    26  * 
    27  * 实现步骤:
    28  *     1.在成员位置创建一个ReentrantLock对象
    29  *     2.在访问了共享数据的代码前,调用lock方法,获取锁对象
    30  *     3.在访问了共享数据的代码后,调用unlock方法,释放锁对象
    31  * 
    32  */
    33 public class RunnableImpl implements Runnable{
    34     
    35     //定义一个共享的票源
    36     private int ticket = 100;
    37     //1.在成员位置创建一个ReentrantLock对象
    38     Lock l = new ReentrantLock();
    39     
    40     @Override
    41     public void run() {
    42         //让卖票重复执行
    43         while(true){
    44             //2.在访问了共享数据的代码前,调用lock方法,获取锁对象
    45             l.lock();
    46             try {
    47                 //可能会出现安全问题的代码
    48                 //判断是否还有票
    49                 if(ticket>0){
    50                     //提高安全问题出现的概率,增加一个sleep
    51                     Thread.sleep(10);
    52                     //进行卖票
    53                     System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
    54                     ticket--;
    55                 }
    56                 
    57             } catch (Exception e) {
    58                 //异常的处理逻辑
    59                 System.out.println(e);
    60             } finally {
    61                 //一定会执行的代码,资源释放
    62                 //3.在访问了共享数据的代码后,调用unlock方法,释放锁对象
    63                 l.unlock();//无论是否异常,都会释放掉锁对象
    64             }
    65         }
    66         
    67     }
    68 }
    多线程 lock 锁 示例

    多线程线程图

      线程的五种状态:

        新建状态-->运行状态-->死亡(结束)状态

                            阻塞状态

              冻结状态(休眠/无限等待)

        新建 :刚创建出来的线程,即 new Thread();

        阻塞 :没有抢到CPU,在等待CPU调用

        运行 :调用run 方法,在运行状态

        死亡 :方法结束,调用完成

        休眠 :调用sleep() 方法,进入休眠状态

                              sleep 是Thread 的一个函数

                              sleep 指占用CPU 不工作,其他线程无法进入。即:sleep不会让出系统资源;

        无限等待 : 调用wait() 方法,未被唤醒

                              wait 是object 的一个函数,需要调用notify() 来唤醒

                              wait 指不占用CPU 不工作,其他线程可以进入。即:wait是进入线程等待池中等待,让出系统资源。

    作者:大角牛 出处:http://www.cnblogs.com/dajiaoniu/ 本博客中未标明转载的文章归作者大角牛和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    一篇文章读懂JSON
    不该被忽视的CoreJava细节(四)
    Java面试题总结(二)
    Java面试题总结(一)
    不该被忽视的CoreJava细节(三)
    不该被忽视的CoreJava细节(一)
    逐步解读String类(一)
    JSP注释格式
    命令行启动mysql服务
    经典进程的同步问题之——生产者&&消费者
  • 原文地址:https://www.cnblogs.com/dajiaoniu/p/10027520.html
Copyright © 2020-2023  润新知