• java--多线程基础


    进程:正在运行的程序,是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源
     
    多进程:在同一个时间段内执行多个任务
     
    多线程的意义:多线程的存在,不是提高程序的执行速度,其实是为了提高应用程序的使用率(一个进程中更多的线程更容易抢到CPU资源,CPU执行权),就会使进程有更高的几率抢到CPU的执行权
     
    注意:不能保证哪一个线程能够在哪一个时刻抢到,所以线程的执行有随机性
    并行和并发的区别:
    并行:逻辑上同时发生,指在某一个时间内同时发生
    并发:物理上同时发生,指在某一个时间点运行多个程序
     
    java程序运行原理:java命令会启动java虚拟机,启动JVM,等于启动一个应用程序,也就是启动了一个进行。该进程会自动启动一个“主线程”,然后主线程去调用某个类的main方法。所以main方法允许在主线程中。在此之前的程序都是运行在单线程中的。
    问题:JVM虚拟机的启动是单线程还是多线程?
       JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的
     
    如何实现多线程的程序?
    由于线程是依赖于进程而存在的,所以我们应该先创建一个进程出来。而进程是由系统创建的,所以我们应该调用系统功能创建一个进程。
    java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。
    但是java可以去调用C/C++写好的程序来实现多线程程序。即利用C/C++去调用系统功能创建进程,java对于这些C/C++的进程程序进行了封装,包装成一些类,这样就可以直接调用这些类去实现多线程程序。
     
    java提供了Thread类中的run()用来包含哪些被线程执行的代码
     

     
    主要内容:线程概述、多线程实现方案、线程调度和线程控制、线程生命周期、线程同步、死锁、线程间的通信、定时器的使用
     
    多线程实现方案:基本是两种方式,还有一种作为扩展方式(实现Callable接口,依赖于线程池)
    线程调度和控制:调度:设置线程的优先级    控制:线程的运行状态控制(具体涵盖:start/sleep/yield/join/...)
    线程生命周期:创建、就绪、运行、阻塞、死亡(状态)
    线程同步:防止多线程带来的安全问题
    死锁:因多个不同种类的线程对同一个资源的抢占而产生的循环等待现象
    线程间通信:java中的等待唤醒机制
     
    1  实现多线程的方式:
    方式一:继承Thread类
    A:自定义类MyThread继承Thread类
    B:在MyThread类中重写run( )方法
    C:创建MyThread对象
    D:启动线程对象
    问题:
    1、为什么要重写run( )方法?
         run()方法里封装的是被线程执行的代码
    2、启动线程对象的方法?
         start()
    3、run()和start()方法的区别?
         run()直接调用仅仅是普通方法
         start():先启动线程,再由JVM调用run( )方法
     
    方式二:
    A:自定义类MyRunnable实现Runnable接口
    B:在MyRunnable里面重写run()方法
    C:创建MyRunnable对象
    D:创建Thread类的对象,并把C步骤的对象作为构造参数传递
     
    问题:
    1、方式1 方式2为什么有两种实现机制?
         方式二解决了单继承的局限性
         适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码、数据有效分离,较好体现面向对象的设计思想
     
    线程的状态转换:
     
     

    线程的生命周期图:

    2  线程的安全问题:
    判断一个 程序是否有线程安全的标准?
    1)是否有多线程环境
    2)是否有共享数据
    3)是否对共享数据进行操作
     
    java提供了同步机制:
         格式:synchronized(对象){
                       需要同步的代码......
                   }
         注意:同步问题可以解决的根本原因就在那个对象上,该对象如同锁的功能。
                   多个线程要求必须是同一把锁。
     
    同步解决线程安全问题:
    A:同步代码块:
              synchronized(对象){
                       需要同步的代码......
              }
     
    B:同步方法
    把同步加在方法上
    这里的锁对象是this
     
    C: 静态同步方法
     把同步加在方法上
    这里的锁对象是当前类的字节码文件对象(Class对象)

    注意:当时在集合中提到,Vector是线程安全的类,但是即使在多线程条件下也不使用该类,这是因为什么呢?
    主要是因为Collections这个集合工具类的存在:它有很多静态方法可以将一个其他不线程安全的集合例如ArrayList/LinkedList集合转成一个线程安全的集合:
    例如:
         ArrayList<String> list1 = new ArrayList<String>( );    //线程不安全的ArrayList集合
         ArrayList<Stirng> list2 = Collections.synchronizedList(new ArrayList<String>( ));   //线程安全的ArrayList集合
     
    Lock锁:
    虽然可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
    Lock接口:java.util.concurrent.locks.Lock
    提供了两个方法:void  lock( )和void  unlock( )
    其实现子类:
         ReentrantLock
     
    格式:
    ReentrantLock lock = new ReentrantLock( );//创建ReentrantLock对象
    lock.lock( );  //加锁
         需要同步的代码;
    lock.unlock( );//释放锁
     
    通常考虑到同步的代码块出错的可能性,所以使用以下方式:
    try{
         lock.lock( );
         需要同步的代码块...............
    }finally{
         lock.unlock( );
    }

    同步锁的弊端:
    1)效率低:安全
    2)如果出现同步嵌套,就容易产生死锁问题:相互等待
    死锁问题及其代码:
    是指两个或者两个以上的线程在执行过程中,因争夺资源产生的一种相互等待的现象
    同步嵌套代码的:最少两个锁
    同步嵌套代码的举例:
    Class myThread extends Thread{
         private boolean flag;
         private static final Object objA = new Object( );
         private static final Object objB = new Object( );
     
         public myThread( boolean flag ){
              this.flag = flag;
         }
         
         public void run( ){
              if(flag){
                   synchronized(objA){
                       System.out.println("if ObjA"); 
                        synchronized(objB){
                             System.out.println("if ObjB");
                        }
                   }
              }else{         
                    synchronized(objB){
                       System.out.println("else ObjB"); 
                        synchronized(objA){
                             System.out.println("else ObjA");
                        }
                   }
              }
     
         }
    }
    在线程测试类中,创建两个线程,给与不同的flag值
    可能就会出现死锁现象

    线程之间的通信问题:不同种类的线程间针对同一个资源的操作
    生产者和消费者:
    不同种类的线程在操作时需要加锁
    不同种类的线程加同一把锁
    正常消费者和生产者的思路:
    A:生产者:先看是否有数据,有就等待,没有就生产,生产完之后通知消费者来消费
    B:消费者:先看有没有数据,有就消费,没有就等待,通知生产者生产数据
     
    为了处理这样的问题,java就提供了一种机制:等待唤醒机制 保证一人一次
    Object类中提供的方法:
    1)wait( ):等待,该方法的特点是:一旦等待就释放锁,并且一旦被唤醒,就是在哪里开始等待的就从哪里开始继续
    2)notify( ):唤醒单个线程。 被唤醒并不代表你可以立刻执行,还得去抢CPU执行权
    3)notifyAll( ):唤醒相关的所有线程
    问题:
     为什么这些方法不定义在Thread类中呢??
    因为这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象,所以这些方法必须定义在Object中。

     
    线程组:ThreadGroup,java.lang.ThreadGroup
    线程默认情况下都属于main线程组
    如何修改线程所在的组???
    1)创建一个线程组
    2)创建线程的时候,把其他线程的组指定为新建的线程租
     
    ThreadGroup tg = new ThreadGroup("producerThreadGroup");//创建一个线程组
    Thread t1 = new Thread(tg,runnable,"thread1");                        //创建一个线程对象1,其所属的线程组是新创建的
    Thread t2 = new Thread(tg,runnable,"thread2");                        //创建一个线程对象2,其所属的线程组是新创建的
    //现在改线程组就包含了两个线程
     
    但是线程组出现的意义在哪里呢???
    可以实现统一管理整个线程组中所有的线程,
    例如通过设置该组的线程优先级:tg.setMaxPriority( );可以使组内的所有线程都是最高优先级
     

    线程池:
    程序启动一个新线程成本是比较高的,因为它涉及到与操作系统进行交互,而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存周期很短的线程时,更应该考虑使用线程池
    线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲 状态。等待下一个对象来使用。
    在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,java内置支持线程池
     
    位于:java.util.concurrent.Executors
    JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法:
    • public  static  ExecutorService  newCachedThreadPool( )    //并没有指定创建几个线程
    • public  static  ExecutorService  newFixedThreadPool( int  nThreads ) //可以指定在线程池放几个线程
    • public  static  ExecutorService  newSingleThreadExecutor( )             //该方法就是nThreads=1的情况下
    这些方法的返回值都是ExecutorService接口(实现的父接口:Executor)对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下的方法:
    • Future<?>  submit( Runnable  task )
    • <T> Future<T>  submit( Callable<T>  task)
    例如:
    1)创建线程池对象:控制要创建几个线程对象
    2)创建Runnable实例(创建一个实现Runnable接口或者Callable接口的类)
    3)提交Runnable实例
    4)关闭线程池(如果在使用线程后想关掉线程池,使用shutdown( )方法)
     
    例如:
    1)创建一个实现Runnable接口的类
    public class MyRunnable implements Runnable {  
         @Override
         public void run() {
               for( int i=0; i<50; i++){
                   System. out.println(Thread. currentThread().getName()+":"+i);
              }
         }
    }
    2)创建测试类
    public class ThreadPoolDemo {
         public static void main(String[] args) {
               //newFixedThreadPool()返回的是ThreadPoolExecutor:ExecutorService的子类
              ExecutorService pool = Executors.newFixedThreadPool(3);//在线程池中创建三个线程
               pool.submit( new MyRunnable()); //启动线程
               pool.submit( new MyRunnable()); //启动线程
     
               pool.shutdown(); //启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
         }
    }
     
    在之前提到创建线程的方式:
    1)继承Thread类
    2)实现Runnable接口
    而在线程池中有一种实现Callable接口创建线程的方式,但是这种方式只能依赖线程池,该接口有个特点,线程运行后有返回值。
    而其他两种方式中的run( )方法的返回值是void。这也是该种方法的特点所在。
     
    3)实现Callable接口
    使用的好处:可以有返回值,可以抛出异常(而在run方法中的异常只能try----catch处理)
    弊端:代码复杂,一般不用
     

    匿名内部类实现多线程:

    第一种方式:
    new Thread(){
                   @Override
                   public void run() {
                        for( int i=1; i<50; i++){
                            System. out.println(Thread. currentThread().getName()+"----"+i);
                       }
                  }
              }.start();

    第二种方式
    new Thread(new Runnable(){
                   @Override
                   public void run() {
                        for( int i=1; i<50; i++){
                            System. out.println( "java"+ "----"+ i);
                       }
                  }
              }).start();

    第三种方式:开发中不会出现,但是在一般面试时会遇到:
    1)该方式会报错吗?  不会
    2)线程会执行哪段代码?  会执行 new Thread中{ }中的代码,即下面用红色标注的部分有效
    new Thread(new Runnable(){
                   @Override
                   public void run() {
                        for( int i=1; i<50; i++){
                            System. out.println(Thread. currentThread().getName()+"----"+i);
                       }
                  }
              }){
                  @Override
                  public void run() {
                        for( int i=1; i<50; i++){
                            System. out.println( "hello"+ "----"+ i);
                       }
                  }
              }.start();

    定时器:
    在java中可以通过Timer和TimerTask类来实现定义调度的功能
     
    Timer:  java.util.Timer(具体类)
    • public  Timer( )
    • public void schedule( TimerTask  task, long  delay)
    • public void schedule( TimerTask  task, long  delay,long  period)
     
    TimerTask:java.util.TimerTask(抽象类),实现了Runnable接口
    • public abstract void run( )
    • public boolean cancel( )
     
    开发中,Quariz是一个完全由java编写的开源调度框架
     

    多线程相关的问题:
    1、多线程有几种实现方案,分别有哪几种?
         实现方式有两种:
              1)继承Thread类:
              2)实现Runnable接口
         
    2、同步有几种方式,分别是什么?
         有两种方式。
         1)同步块的方式          (锁的是任意对象)
         2)同步方法的方式   (锁的是当前类对象)
     
    3、启动一个线程是run( )还是start( )?他们的区别是?
         启动线程用start( )。
         run方法封装了线程执行的代码,直接调用仅仅是普通方法的调用
         start方法启动线程,并由JVM自动调用run方法
     
    4、sleep( )和wait( )方法的区别?(sleep是在Thread类中定义的,而wait方法定义在Object类)
         首先:sleep方法必须指定时间,而wait可以指定时间也可以不指定时间
         其二:sleep方法在线程休眠期间不释放资源(锁),而wait方法在进入等待状态时释放自己持有的锁。
     
    5、为什么wait( ) notify( ) notifyAll( )等方法都定义在Object类中?
         因为wait notify notifyAll等方法的调用依赖于锁对象,而同步代码块的锁对象是任意锁。
         由因为Object对象代表是任意一个对象,所以这些方法的定义必须在Object类中

    银联笔试看到这样一道题:
    class NewThread extends Thread implements Runnable(){
       public void run(){
        System.out.println("run()");
      }
    }
     
    class Demo{
      public static void main(String[] args){
        Thread t1 = new Thread(new NewThread());
        t1.start();
      }
    }
    试问这道题会正常运行吗?如果不能运行,会在第几行报错?
     经过测试这是可以的,NewThread 既继承了Thread又实现了Runnable接口,new NewThread()就可以看成一个Thread或者一个Runable接口的实现类,在测试类中
     Thread t1 = new Thread(new NewThread());Thread构造函数的参数是一个实现Runnable接口的实现类,而NewThread类正好实现类Runnable接口,故可以当做参数传入。
     
     
  • 相关阅读:
    WAF绕过方法
    ”非常危险“的Linux命令
    CSRF--花式绕过Referer技巧
    安卓手机的后门控制工具SPADE
    基于RedHat发行的Apache Tomcat本地提权漏洞
    SQL注入--宽字节注入
    MySQL提权
    CF1067D. Computer Game(斜率优化+倍增+矩阵乘法)
    CF1063F. String Journey(后缀数组+线段树)
    BZOJ4732. [清华集训2016]数据交互(树链剖分+线段树+multiset)
  • 原文地址:https://www.cnblogs.com/zwbg/p/5902774.html
Copyright © 2020-2023  润新知