一. 多线程基础

1.1 线程

  1. 多线程的意义

    1. 多线程的存在,不是提高程序的执行速度,而实为了提高应用程序的使用率
    2. 程序的执行本质上是在抢CPU的执行权,多个进程中如果某个进程的执行路径比较多,那么就会有更高的几率抢到CPU的执行权
    3. 我们不能保证哪一个线程能在哪个时间点抢到执行权,所以线程的执行具有随机性
  2. Java程序的运行原理

    1. 由java命令启动JVM,JVM启动就相当于启动了一个进程,接着该进程创建一个主线程去调用main方法,所以main方法运行在主线程中
    2. java程序的进程里面至少包含两个线程,主线程就是main()方法线程,另一个是垃圾回收机制线程,每当使用java命令执行一个类时,实际上都会启动一个JVM,每一个JVM实际上就是在操纵系统中启动了一个进程,java本身具备了垃圾的手机机制,所以在java运行时至少启动两个线程
  3. 线程的两种创建方式

    1. Thread类

      1. 创建一个线程匿名内部类实例,并重写其run()方法,此时t只是一个实例,只有执行t.start()方法时才会创建一个新的线程

        Thread t = new Thread("Custom-Thread"){
            @Override
            public void run(){
                .........
            }
        }.start();
      2. 创建一个extends Thread的子类实例,并重写其run()方法
    2. Runnable接口

      1. 实现Runnable接口,并重写run()方法,将Runnable接口实例作为参数传递给Thread类实例

        MyRunnable mr = new MyRunnable();
        Thread t = new Thead(mr);
        t.start();
    3. 为什么有了方式1还要有方式2

      1. 可以避免由于Java单继承单来的局限性
      2. 适合多个相同程序的代码去处理同一个资源的情况(而不需要使用static来保证每个线程使用同一份数据),将可执行单元和线程控制分离开来,使用策略设计模式的思想
    4. run()方法和start()方法的区别

      1. run()方法封装了被线程执行的代码,直接调用仅仅是普通方法的调用
      2. start()封装了run()方法,并且会启动线程
  4. 线程的生命周期

    1. 新建(new):创建线程对象
    2. 可执行(Runnable):有执行资格,没执行权---start()方法后
    3. 运行(Running):有执行权

      1. 阻塞:由于一些操作让线程处于该状态,没有执行资格,没有执行权---sleep(),wait()
      2. 唤醒:由于一些操作把线程激活,激活后处于就绪状态---notify()或时间到
    4. 死亡:线程对象变成垃圾,等待被回收---run()方法结束或interrupt()方法后![1586771889927]image.png
  5. 解决线程安全问题

    1. 要想解决问题,就要知道哪些原因会导致这些问题,这些原因也是判断一个程序是否会有线程安全问题的标准

      1. 是否是多线程环境
      2. 是否有共享数据
      3. 是否有多条语句操作共享数据(操作是否为原子性操作)
    2. 解决问题的思想就是把多条操作共享数据的代码包成一个整体,让某个线程在执行的时候,别人不能来执行

      1. 同步代码块:
      Synchronized(对象){
          需要同步的代码;
      }
      注意:同步可以解决安全问题的根本原因在于那个对象,该对象如同锁的功能,多个线程必须是同一把锁
  2. 同步方法
     1. 把同步关键字加到方法上
     2. 同步方法的锁对象为当前类对象:this

  ```java
  private synchronized void sellTicket(){
      ....
  }
  ```

  3. 静态同步方法
     1. 同步关键字加到方法上
     2. 静态同步方法的锁对象是类的字节码文件对象

  ```java
  private synchonized void sellTicket(){
      ....
  }
  ```

  4. Lock锁

1.2 Thread

  1. Thread的构造方法---Thread(ThreadGroup group , Runnable target , String name , long stackSize)

    1. 线程名称:name

      1. 当构造方法未传入name参数时,默认会使用null,其中nextThreadNum()调用ThreadInitNumber自增,ThreadInitNumber是一个static的自增常量,所以当没有指定线程名称时,其线程名称为Thread-ThreadInitNumber

         public Thread() {
                init(null, null, "Thread-" + nextThreadNum(), 0);
            }
    2. 目标函数接口:target

      1. 当构造函数未传入target参数时,默认会使用null,在其int()方法中会将target存起来,当调用start()方法的时候会调用run()方法,如果target为null时,则不会执行任何内容
      2. 所以当没有传target参数时,需要重写run()方法

        private void init(ThreadGroup g, Runnable target, String name,
                              long stackSize, AccessControlContext acc,
                              boolean inheritThreadLocals) {
                ....
                this.target = target;
                 ....
            }
        
        public void run() {
                if (target != null) {
                    target.run();
                }
            }
    3. 线程组:ThreadGroup

      1. 当构造函数未传入ThreadGroup参数时,就会使用父线程的线程组,在main线程租中有三个线程

        1. main线程
        2. monitor线程
        3. Thread-0线程
        private void init(ThreadGroup g, Runnable target, String name,
                              long stackSize, AccessControlContext acc,
                              boolean inheritThreadLocals) {
                ....
                Thread parent = currentThread();
                    /* If the security doesn't have a strong opinion of the matter
                       use the parent thread group. */
                    if (g == null) {
                        g = parent.getThreadGroup();
                    }
                 ....
            }
    4. 栈大小:stackSize

      1. 影响Java线程数量的因素:

        1. Java虚拟机本身:-Xms,-Xmx,-Xss;
        2. 系统限制:

          1. /proc/sys/kernel/pid_max : 32080
          2. /proc/sys/kernel/thread-max
          3. max_user_process(ulimit -u)
          4. /proc/sys/vm/max_map_count
        3. 增大堆内存(-Xms,-Xmx)会减少可创建的线程数量,增大线程栈内存(-Xss,32位系统中此参数值最小为60K)也会减少可创建的线程数量
      2. 一个进程可以开启的线程受可用内存限制,如果是32位的机器,那么默认一个进程有2G的可用内存,而每个线程默认分析1M的栈空间,所以这种情况下理论最线程数在2000多个。一个解决办法是创建线程时减少线程栈的大小或是使用64位的系统。64位系统应该可以忽略这个问题了。

1.3 Thread的API

1.3.1 Daemon(守护)线程

  1. 当新开的线程不是Daemon线程时,main线程结束后,Application根据ThreadGroup判断ActiveCount,仍然会继续执行子线程
  2. 当新开的线程是Daemon线程时,主线程结束时,Daemon线程也会立即结束
  3. 守护线程只能在start方法之前执行

    public static void main(Stirng[] args){
        Thread t = new Thread(){
        @Override
        public void run(){
            try{
              Thread.sleep(100_000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
        t.setDaemon(true);
        t.start();
        
        Thread.sleep(5_000);
    }

1.3.2 Join

  1. join方法在start方法之后:运行完join线程后才执行join()方法接下来的代码

    1. 以下代码的执行顺序为: t1 -> t2 -> main
    public static void main(String[] args){
        Thread t1 = new Thread(() - > intStream.range(1,100).forEach(i ->     System.out.println(Thread.currentThread().getName() +"---"+ i)))
    };
    
        Thread t2 = new Thread(() - > intStream.range(1,100).forEach(i ->     System.out.println(Thread.currentThread().getName() +"---"+ i)))
    };
    
        t1.start();
        t1.join();
        t2.start();
        t2.join();
        Optional.of("finish...").ifPresent(System:out.println);
  1. 注:防止主线程停止:Thread.currentThread().join():在主线程上执行"我要等到主线程结束,当前线程才能结束",进入死循环
  2. 有一些应用将服务设置为守护进程,防止应用结束后,其服务仍然占用资源

    1. JettyHttpServer.start()
    2. activeMqListener
public static void main(String[] args){
    Thread t1 = new Thread();
    
    t1.start();
  
    //自我阻塞,永远不会停止
    Thread.currentThread().join()
}

1.3.3 interrupt

  1. interrupt()方法:中断线程

    1. 当中断的线程中不存在阻塞方法,如sleep(),wait(),join()等方法时,线程中isInterrupt()方法会变为true,但是线程不会结束,因为不能通过阻塞方法捕获InterruotedException异常

      • 当使用interrupt()方法打断任务时,其内部把flag标识进行修改,但是线程仍然继续执行,可以通过以下两种方式查看中断标识:

        • t.isInterrupted();
        • Thread.interrupted();
    2. 当中断的线程中存在阻塞方法时,会通过阻塞方法来捕获interruotException异常来结束线程

      1. sleep()方法阻塞

        public static void main(String[] args){
            Thread t = new Thread(){
                @Override
                public void run(){
                    while(true){
                        try{
                            Thread.sleep(10L);
                            System.out.println("---"+this.isInterrupted());
                        }catch(InterruptedException e){
                            System.out.println("收到打断信号" + e);
                            e.printStackTrace();
                        }
                    }
                }
            };
            t.start();
            Thread.sleep(1000L);
            System.out.println(t.isInterrupted());//false
            t.interrupt();
            System.out.println(t.isInterrupted());//ture
        }
      2. wait()方法阻塞

        public static void main(String[] args){
            Thread t = new Thread(){
                @Override
                public void run(){
                    while(true){
                        sychronized (this){
                        try{
                            this.wait(10L);
                            System.out.println("---"+this.isInterrupted());
                        }catch(InterruptedException e){
                            System.out.println("收到打断信号" + e);
                            e.printStackTrace();
                        }
                        }
                    }
                }
            };
            t.start();
            Thread.sleep(1000L);
            System.out.println(t.isInterrupted());//false
            t.interrupt();
            System.out.println(t.isInterrupted());//ture
        }
      3. join()方法阻塞:join()方法中封装的时wait()---由Object提供,并且join阻塞的为非join线程(main线程)

        public static void main(String[] args){
            Thread t1 = new Thread(){
                @Override
                public void run(){
                    while(true){
                        try{
                            Thread.sleep(1000L);
                            System.out.println("---"+this.isInterrupted());
                        }catch(InterruptedException e){
                            System.out.println("收到打断信号" + e);
                            e.printStackTrace();
                        }
                    }
                }
            };
            t1.start();
            
            Thread main = Thread.currentThread();
            Thread t2 = new Thread(() - > {
                @Override
                public void run(){
                    try{
                        Thread.sleep(10000L);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    
                    main.interrupt();
                }
            })
            t2.start();
                
            try{
                t1.join();        
            }catch(InterruptedException e){
                e.printStackTrace
            }
        }

1.3.4 优雅的方式结束线程

  1. 设置判断标识

    public static classWoker extends Thread{
        private volatile boolean start = true;
        
        @Override
        public void run(){
            while(start){
                ...
            }
        }
        public void shutdown(){
            this.start = false;
        }
    }
    
    public static void main(String[] args){
        Worker worker = new Worker();
        worker.start();
        
        try{
            Thread.sleep(10000L);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        
        worker.shutdown();
    }
  2. 通过阻塞方法中断

    public class Worker extends Thread{
        @Overrride
        public void run(){
            while(true){
                try{
                    Thread.sleep(10L);
                }catch(InterruptedException e){
                    break;//return
                }
            }
            //...............
        }
    }
    
    public static void main(String[] args){
        Worker worker = new Worker();
        worker.start();
        
        try{
            Thread.sleep(100L);
        }catch(InterruptedException){
            e.printStackTrace():
        }
        worker.interrupt();
    }
  3. 通过Thread中断标识判断

    public class Worker extends Thread{
        @Overrride
        public void run(){
            while(true){
                if(Thread.interrupted()){
                    break;
                }
            }
            //...............
        }
    }
    
    public static void main(String[] args){
        Worker worker = new Worker();
        worker.start();
        
        try{
            Thread.sleep(100L);
        }catch(InterruptedException){
            e.printStackTrace():
        }
        worker.interrupt();
    }

1.3.5 强制性中断线程

  1. 当子线程不是执行循环逻辑时,并无校验中断标识和执行阻塞方法的逻辑,并且方法的执行时间很长,需要在方法的执行其进行中断开-----替代stop()方法

    public static void main(String[] args){
        ThreadService ts = new ThreadService();
        long start = System.currentTimeMillis();
        service.execute(() - > {
            //load a very heavy resource
            // while(true){
                //...
            //}
            try{
                Threads.sleep(5000L);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        });
        service.shutDown(10000L);
        System.out.println(System.currentTimeMillis() - start);
    }
    ------------------------------------------------------------------
        
    public class ThreadService{
        private Thread executeThread;
        private boolean finished = false;
        
        public void execute(Runnable task){
            executeThread = new Thread(){
                @Override
                public void run(){
                    Thread t = new Thread(task);
                    t.setDaemon(true);
                    
                    t.start();
                    try{
                       t.join();  
                       finished = true;
                    }catch(InterruptedException e){
                       
                    }
                }
            };
            executeThread.start();
        }
        
        public void shutDown(long miilis){
            long startTime = System.currentTimeMillis();
            while(!finished){
                if(System.currentTimeMillis() - startTime >= millis){
                System.out.println("超时中断...");
                executeThread.interrupt();
                break;
              }
                  
                try{
                    executeThread.sleep(1000L);
                }catch(InterruptedException){
                    System.out.println("执行线程被打断...");
                    break;
                }
               }
        }
    }

1.4 同步和锁的问题

1.4.1 Synchronize串行化

  1. 同步方法:this锁

    public static void main(String[] args){
        ThisLock lock = new ThisLock();
        
        new Thread(){
            @Override
            public void run(){
                lock.method1();
            }
        }.start();
        
        new Thread(){
            @Override
            public void run(){
                lock.method2();
            }
        }.start();
    }
    ------------------------------------------------------------------
        
    class ThisLock{
        public synchronized void method1(){
            try{
                System.out.println(Thread.currentThread().getName());
                Thread.sleep(5_000L);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
        
        public void method2(){
            synchronized(this){
            try{
                System.out.println(Thread.currentThread().getName());
                Thread.sleep(5_000L);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
          } 
        }
    }
  2. 静态同步方法:Class锁

    class StaticLock{
        public static synchronized void method1(){
            try{
                System.out.println(Thread.currentThread().getName());
                Thread.sleep(5_000L);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
        
        public static void method2(){
            synchronized(StaticLock.class){
            try{
                System.out.println(Thread.currentThread().getName());
                Thread.sleep(5_000L);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
          } 
        }
    }
  3. 死锁:通过jps和jstack查看日志

    class DeadLock{
        private OtherService otherService;
        private final Object lock = new Object;
        private DeadLock(OtherService otherService){
            this.otherService = otherService;
        }
        
        public void m1(){
            synchronized(lock){
                otherService.s1();
            }
        }
        
        public void m2(){
             synchronized(lock){
                otherService.s2();
            }
        }
    }
    
    
    class OtherService{
        private DeadLock deadLock;
        private final Object lock = new Object;
        private OtherService(DeadLock deadLock){
            this.deadLock = deadLock;
        }
        
        public void s1(){
            synchronized(lock){
                deadLock.m1();
            }
        }
        
        public void s2(){
             synchronized(lock){
                deadLock.m2();
            }
        }
    }
    ------------------------------------------------------------------
    
    public static void main(String[] args){
        OtherService otherService = new OtherService();
        DeadLock deadLock = new DeadLock(otherService);
        otherService.setDeadLock(deadLock);
        
        new Thread(){
            @Override
            public void run(){
                while(true){
                    deadLock.m1();
                }
            }
        }.start();
        
        new Thread(){
            @Override
            public void run(){
                while(true){
                    otherService.s2();
                }
            }
        }.start();
    }

1.4.2 线程间的通讯

  1. 通过wait()和notify(),notifyAll()方法进行线程间的通信

    1. wait():线程进入阻塞,调用的同时会释放锁
    2. notify():随机唤醒一条阻塞的线程,线程执行逻辑从wait()处开始,使线程处于runnable状态,仍需要抢执行执行权

      1. 当有多消费者和生产者时,会产生假死的问题,原因是由于同类线程之间的唤醒,使得所有线程进入wait状态
    3. notifyAll():唤醒所有阻塞的线程

      public static void main(String[] args){
          ProducerConsumerService service = new ProducerConsumerService();
          Stream.of("P1","P2","P3").forEach(n -> {
              new Thread(n){
                  @Override
                  public void run(){
                      service.produce();
                      try{
                          Thread.sleep(10L);
                      }catch(InterruptedException e){
                          e.printStackTrace();
                      }
                  }
              }.start();
          })
              
          Stream.of("C1","C2","C3").forEach(n -> {
              new Thread(n){
                  @Override
                  public void run(){
                      service.consume();
                      try{
                          Thread.sleep(10L);
                      }catch(InterruptedException e){
                          e.printStackTrace();
                      }
                  }
              }.start();
          })
      }
      ------------------------------------------------------------------
          
      class ProducerConsumerService{
          private final Object LOCK = new Object();
          private Boolean isProduced = false;
          private Integer i = 0;
          
          public void produce(){
              synchronized(LOCK){
                  while(isProduced){
                      try{
                          LOCK.wait();
                      }catch(InterruptedException e){
                          e.printStackTrace();
                      }
                  }
                  i++;
                  System.out.println("P -> "+i);
                  LOCK.notifyAll();
                  isProduced = true;
              }
          }
          
          public void consume(){
              synchronized(LOCK){
                  while(!isProduced){
                      try{
                          LOCK.wait();
                      }catch(InterruptedException e){
                          e.printStackTrace();
                      }
                  }
                  System.out.println("C -> "+i);
                  LOCK.notifyAll();
                  isProduced = false;
              }
          }
      }
  2. wati()和sleep()的区别

    1. sleep()方法是属于Thread的方法,而wait()是属于Object的方法
    2. sleep()方法不会释放锁(Object monitor),而wait()方法会释放锁

      public static void main(String[] args){
          Stream.of("t1","t2").forEach(n -> {
              new Thread(n){
                  @Override
                  public void run(){
                      m1();//线程之间间隔20s
                      m2();//几乎同时
                  }
              }.start();
          })
      ------------------------------------------------------------------  
              
      public static void m1(){
          synchronized(LOCK){
            try{
                System.out.println(Thread.currentThread().getName());
                Thread.sleep(20_000L); 
            }catch(InterruptedException e){
                e.printStackTrace();
            }
         }
       }
      ------------------------------------------------------------------
          
      public static void m2(){
          synchronized(LOCK){
            try{
                System.out.println(Thread.currentThread().getName());
                LOCK.wait(20_000L); 
            }catch(InterruptedException e){
                e.printStackTrace();
            }
         }
       }    
      }
    3. sleep()方法不需要依赖Synchronize,而wait()方法必须要和Synchronize关键字一起使用
    4. sleep()方法不需要被唤醒,而wait()方法必须要被唤醒(除wait(int times)外)

1.4.3 案例:控制最大效率线程切换数

public class CpatureService{
    private final static LinkedList<Object> list = new LinkedList<>();
    private final static int MAX_Thread = 5;
    
    public static void main(String[] args){
        List<Thread> threads = new ArrayList<>();
        Arrays.asList("M1","M2","M3","M4","M5","M6","M7","M8","M9").stream()
            .map(CaptureService::createCaptureThread)
            .forEach(t -> {
            t.start();
            threads.add(t);
        }
   });
        
        threads.stream().forEach(t -> {
            try{
                t.join();
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        });
        
        Optional.of("All of Threads Have Finished...").ifPresent(System.out::println);
    }
------------------------------------------------------------------
    
    public static Thread createCaptureThread(String name){
        return new Thread({
            Optional.of(Thread.currentThread().getName() + "begin")
                .ifPresent(System.out::println);
            
            synchronized(list){
                while(list.size > MAX_THREAD){
                    try{
                        list.wait()
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
                list.addLast(new Object());
            }
            
             Optional.of(Thread.currentThread().getName() + "working")
                .ifPresent(System.out::println);
        });
        
        try{
            Thread.sleep(10_000L);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        
        synchronized(list){
            Optional.of(Thread.currentThread().getName() + "working")
                .ifPresent(System.out::println);
        });
        list.removeFirst();
        list.notifyAll();
        }
    }   
}

1.4.4 自定义Lock锁

  1. 当synchronized同步时,执行过程很长,调用者进入阻塞状态,也不能进行打断和立即返回,即使打断也不会释放锁,从而抢不到锁,仍然处于阻塞状态

    1. Lock和synchronized区别

      1. Lock是一个接口,而synchronized是关键字
      2. synchronized会自动释放锁,而lock必须手动释放锁
      3. Lock可以让等待锁的线程响应中断并且释放锁,而synchronized不会,线程会一直等待下去(Lock的定时功能)
      4. 通过Lock可以知道线程有没有拿到锁,而synchronized不能
      5. synchronized能所著类,方法和代码块,而Lock是块方位内的
      6. Lock能提高多个线程读操作的效率
    2. 自定义锁

      public static void main(String[] args){
          BooleanLock lock = new BooleanLock();
          Stream.of("T1","T2","T3","T4").forEach(t -> {
              new Thread(t){
                  @Override
                  public void run(){
                      try{
                          lock.lock();
                          Optional.of(Thread.currentThread().getName() + "get lock")
                          .ifPresent(System.out::println);
                          work();
                      }catch(InterruptedException e){
                          e.printStackTrace();
                      }finllly{
                          lock.unlock();
                      }
                  }
              }.start();
          })
      }
      
      private static void work(){
          Optional.of(Thread.currentThread().getName() + "is working")
              .ifPresent(System.out::println);
          Thread.sleep(10_000L);
      }
      ------------------------------------------------------------------
      
          public class BooleanLock implement Lock{
              private boolean initValue;
              private Collection<Thread> collection = new ArrayList<>();
              private Thread currentThread;
              
              public BooleanLock(){
                  this.initValue = false;
              }
              //非定时锁...
              @Override
              public synchronized void lock() throws interruptedException{
                  while(initValue){
                      collection.add(Thread.currentThread());
                      this.wait();
                  }
                  
                  collection.remove(Thread.currentThread());
                  this.initValue = ture;
                  this.currentThread = Thread.currentThread();
              }
             //定时锁...
              @Override
              public synchronized void lock(Long millis) throws interruptedException{
                  if(millis <= 0){
                      lock();
                      return;
                  }
                  
                  long hasRemain = millis;
                  long endTime = System.currentTimeMillis() + millis;
                  while(initValue){
                      if(hasRemain < 0){
                          throw new TimeOutException("Time out");
                      }
                      collection.add(Thread.currentThread());
                      this.wait(millis);
                      hasRemain = System.currentTimeMillis() - endTime;
                  }
                  
                  collection.remove(Thread.currentThread());
                  this.initValue = true;
                  this.currentThread = Thread.currentThread();
              }
              
              @Override
              public synchronized void unlock(){
                  if(this.currentThread() == Thread.currentThread()){
                      this.initValue = false;
                      Optional.of(Thread.currentThread().getName() + "release the lock")
                          .ifPresent(System.out::println);
                      this.notifyAll();
                  }
              }
              
              @Override
              public Collection<Thread> getBlockThread(){
                  return this.collection;
              }
          }
      

NTFA
24 声望3 粉丝

自我约束,刻意练习


引用和评论

0 条评论