Java教程

【笔记】Java多线程

本文主要是介绍【笔记】Java多线程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

遇见狂神说的Java多线程教学笔记

线程的创建

三种创建方式

  • Thread class

    • 自定义线程类继承Thread class
    • 重写run()方法,编写线程执行体
    • 创建线程对象,调用start()方法启动线程 注意:线程开启不一定立即执行,由 CPU 调度执行

    不建议使用:OOP单继承局限性

    public class TestThread extends Thread{
    
       @Override
       public void run() {
          for (int i = 0; i < 200; i++) {
             System.out.println("线程运行"+i);
          }
       }
    
       public static void main(String[] args) {
    
          TestThread thread = new TestThread();
    
          thread.start();
    
          for (int i = 0; i < 1000; i++) {
             System.out.println("主线程运行"+i);
          }
       }
    }
    
  • Runnable接口

    • 自定义MyRunnable类实现Runnable接口
  • 实现run()方法,编写线程执行体

    • 通过线程对象代理 new Thread(myRunnable).start()

    推荐使用:避免单继承局限性、方便同一个对象被多个线程使用

    public class TestThread4 implements Runnable{
    
       private int ticketNums = 10;
    
       @Override
       public void run() {
          while (true) {
             if(ticketNums < 0) {
                break;
             }
             try {
                Thread.sleep(200);
             } catch (InterruptedException e) {
                e.printStackTrace();
             }
             System.out.println(Thread.currentThread().getName()+" got "+ticketNums--+"ticket");
          }
       }
    
       public static void main(String[] args) {
          TestThread4 ticket = new TestThread4();
    
          new Thread(ticket,"ming").start();
          new Thread(ticket,"lao").start();
          new Thread(ticket,"huang").start();
          //涉及数据紊乱问题,通过加锁解决
       }
    }
    
  • Callable接口

    • 实现Callable接口,需要返回值类型
    • 重写call方法,需要抛出异常
    • 创建目标对象
    • 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);//线程池里的线程数为1
    • 提交执行:Future<Boolean> result1 = ser.submit(t1);//t1是目标对象
    • 获取结果:boolean r1 = result1.get();
    • 关闭服务:ser.shutdownNow();

    可以定义返回值,抛出异常

    public class TestCallable implements Callable<Boolean> {
       private String name;
    
       public TestCallable(String name) {
          this.name = name;
       }
       @Override
       public Boolean call() throws Exception {
          System.out.println(name);
          return true;
       }
    
       public static void main(String[] args) throws ExecutionException, InterruptedException {
          TestCallable t1 = new TestCallable("ming");
          TestCallable t2 = new TestCallable("hong");
          ExecutorService ser = Executors.newFixedThreadPool(2);
          Future<Boolean> result1 = ser.submit(t1);
          Future<Boolean> result2 = ser.submit(t2);
          boolean rs1 = result1.get();
          boolean rs2 = result2.get();
          System.out.println(rs1);
          System.out.println(rs2);
          ser.shutdownNow();
       }
    }
    

停止线程

不使用已废弃的stop()、destroy()方法。

使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。

public class TestStop implements Runnable{
    private boolean flag = true;
    
    @Override
    public void run(){
        while(flag){
         //自己的代码
        }    
    }
    
    public void stop() {
        this.flag = false;
    }
}

线程休眠

  • Thread.sleep(long millis)
  • sleep时间到达后线程进入就绪状态
  • 可以模拟网络延时、倒计时等
  • 每个对象都有一个锁,sleep不会释放锁
//实现倒计时或时钟显示
public class TestSleep { 
   public static void main(String[] args) throws InterruptedException {
//    TestSleep.tenDown();
	//时钟显示
      Date startTime = new Date(System.currentTimeMillis());
      while (true){
         try {
            Thread.sleep(1000);
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
            startTime = new Date(System.currentTimeMillis());
         }catch (Exception e){
            e.printStackTrace();
         }
      }
   }

//倒计时
   public static void tenDown() throws InterruptedException {
      int num = 10;

      while (true){
         Thread.sleep(1000);
         System.out.println(num--);
         if(num<=0){
            break;
         }
      }
   }
}

线程礼让

  • Thread.yield();

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞

  • 将线程从运行态转为就绪态

  • 让CPU重新调度,礼让不一定成功(可能还会调用暂停的那个线程)

public class TestYield {
   public static void main(String[] args) {
      MyYield myYield = new MyYield();
      new Thread(myYield,"a").start();
      new Thread(myYield,"b").start();
   }
}

class MyYield implements Runnable{
   @Override
   public void run() {
      System.out.println(Thread.currentThread().getName()+" start");
      Thread.yield();
      System.out.println(Thread.currentThread().getName()+" stop");
   }
}

Join方法

join在线程里面意味着“插队”,哪个线程调用join代表哪个线程插队先执行——但是插谁的队是有讲究了,不是说你可以插到队头去做第一个吃螃蟹的人,而是插到在当前运行线程的前面,比如系统目前运行线程A,在线程A里面调用了线程B.join()方法,则接下来线程B会抢先在线程A面前执行,等到线程B全部执行完后才继续执行线程A

public class TestJoin implements Runnable{
   @Override
   public void run() {
      for (int i = 0; i < 1000; i++) {
         System.out.println("vip"+i);
      }
   }

   public static void main(String[] args) throws InterruptedException {
      TestJoin testJoin = new TestJoin();
      Thread thread = new Thread(testJoin);
      thread.start();

      for (int i = 0; i < 500; i++) {
         if(i==200){
            thread.join();
         }
         System.out.println("main"+i);
      }
   }
}

线程优先级

Java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。

线程的优先级用数字表示,范围从1-10。

  • getPriority() 获取优先级

  • setPriority(int p) 设置优先级

  • 优先级的设置应在start()之前;

守护线程(daemon)

  • setDaemon(true) 注:在start()之前设置;
  • 线程分为用户线程和守护线程
  • 只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作, 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。Daemon的作用是为其他线程的运行提供便利服务。
  • 守护线程不要用来执行 I/O 操作或计算逻辑等,因为一旦非守护线程全部退出,守护线程也会随之退出,假如这时候还有 I/O 操作或计算逻辑未完成,那将是毁灭性的打击。

如:后台记录操作日志、监控内存、垃圾回收等

线程同步

解决多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用。

synchronized关键字

synchronized是Java中解决并发问题的一种最常用最简单的方法 ,它可以确保线程互斥的访问同步代码。

synchronized 应用:

  • 普通同步方法,锁是当前实例对象。
  • 同步代码块,锁是括号中的对象。
  • 静态同步方法,锁是当前类的class对象。
public class TestSynchronized {

   SyncObject syncObject;

   //既是对象锁也是方法锁
   public synchronized void method1(){}

   // 对象锁:形式2(代码块形式) 
   public void method2(){
      synchronized(syncObject){
         // 访问或修改被锁保护的共享状态
      }
   }

    //类锁:形式1 :锁静态方法
   public static synchronized void method3(){}
    
    // 类锁:形式2 :锁静态代码块
   public void method4() {
	  synchronized (TestSynchronized.class) {}
   }
    
}

注:

  • synchronized修饰方法时存在缺陷:若修饰1个大的方法,将会大大影响效率

  • 若使用synchronized关键字修饰 线程类的run(),由于run()在线程的整个生命期内一直在运行,因此将导致它对本类任何synchronized方法的调用都永远不会成功

    解决方案:使用 synchronized关键字声明代码块,该解决方案灵活性高:可针对任意代码块 & 任意指定上锁的对象

更多有关synchronized的知识可通过该篇博文学习https://blog.csdn.net/carson_ho/article/details/82992269

Lock(锁)

  • Lock是一个类,通过这个类可以实现同步访问。

  • Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

  • 采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。

  • ReentrantLock是唯一实现了Lock接口的类

class A{
    private final Lock lokc = new ReenTrantLock();
    public void method(){
        lock.lock();
        try{
            
        }catch(Exception e){
            
        }finally{
            lock.unlock();
        }
    }
}

更多https://www.cnblogs.com/dolphin0520/p/3923167.html

  • ReentrantReadWriteLock 有两把锁,读锁和写锁
    • 多个线程可以同时进行读操作,此时不能进行写操作
    • 只能有一个线程进行写操作,此时不能有任何别的线程进行读写操作
class RWLDemo{
   private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
   private final Lock writeLock = rwl.writeLock();
   private final Lock readLock = rwl.readLock();

   public void m1() {
      //上读锁,其他线程只能读不能写
      readLock.unlock();
      try {
         System.out.println("get read lock");
      } catch (Exception e) {

      } finally {
         readLock.unlock();
      }

      //上写锁,其他线程既不能读也不能写
      writeLock.lock();
      try {
         System.out.println("get write lock");
      }catch (Exception e){

      }finally {
         writeLock.unlock();
      }

      //先读再写的情况,请先释放读锁,再上写锁,ReentrantReadWriteLock不支持读锁升级为写锁
      readLock.lock();
      try {
         System.out.println("get read lock");
      }catch (Exception e){

      }finally {
         readLock.unlock();
      }
      writeLock.lock();
      try {
         System.out.println("get write lock");
      }catch (Exception e){

      }finally {
         writeLock.unlock();
      }

      /**
		 先写再读的情况,可以在上写锁的情况,再上读锁,解锁的时候先解读锁再解写锁(ReentrantReadWriteLock支持锁降级,因此这段代码不会造成死锁)
		 **/
      writeLock.lock();
      try {
         System.out.println("get write lock");

         readLock.lock();
         try {
            System.out.println("get read lock");
         }catch (Exception e){

         }finally {
            readLock.unlock();
         }
         
      }catch (Exception e){

      }finally {
         writeLock.unlock();
      }
   }
}
  • 想了解ReentrantReadWriteLock的使用的可以--->https://blog.csdn.net/weixin_33779515/article/details/92575574

线程通信

Java 提供了几个方法解决线程之间的通信问题:

  • wait()表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
  • wait(long timeout)指定等待的毫秒数
  • notify()唤醒一个处于等待状态的线程
  • notifyAll()唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先调度

均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常。

深入实践可以去了解同步问题:生产者-消费者问题、读者-写作问题、哲学家进餐问题、吸烟者问题等

生产者消费者问题(管道法)

public class TestPC {

   public static void main(String[] args) {
      SynContainer container = new SynContainer();

      new Productor(container).start();
      new Consumer(container).start();
   }
}
//生产者
class Productor extends Thread{
   SynContainer container;

   public Productor(SynContainer container) {
      this.container = container;
   }

   //生产

   @Override
   public void run() {
      for (int i = 0; i < 100; i++) {
         container.push(new Procduct(i));
         System.out.println("生产了"+i+"件产品");
      }
   }
}
//消费者
class Consumer extends Thread{
   SynContainer container;

   public Consumer(SynContainer container) {
      this.container = container;
   }

   //消费

   @Override
   public void run() {
      for (int i = 0; i < 100; i++) {
         container.pop();
      }
   }
}
//产品
class Procduct{
   int id;
   public Procduct(int id) {
      this.id = id;
   }
}
//缓存区
class SynContainer{
   //容器大小
   Procduct[] procducts = new Procduct[10];
   int count = 0;
   //生产者放入产品
   public synchronized void push(Procduct procduct){
      //如果容器满了,等待消费者消费
      if (count == procducts.length){
         //通知消费者消费,生产等待
         try {
            System.out.println("生产等待");
            this.wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
      //没满就需要丢入产品
      procducts[count++] = procduct;

      this.notifyAll();
      //可以通知消费者消费了
   }

   //消费者消费产品
   public synchronized Procduct pop(){
      //判断能否消费
      if (count == 0){
         //等待生产者生产,消费者等待
         try {
            System.out.println("消费者等待");
            this.wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }

      //消费
      count--;
      Procduct procduct = procducts[count];
      System.out.println("消费了"+procduct.id+"件产品");

      //通知生产者生产
      this.notifyAll();

      return procduct;
   }
}

生产者-消费者问题(信号灯法)

public class TestPC2 {
   public static void main(String[] args) {
      TV tv = new TV();
      new Player(tv).start();
      new Watcher(tv).start();
   }
}

//生产者--演员
class Player extends Thread{
   TV tv;

   public Player(TV tv) {
      this.tv = tv;
   }

   @Override
   public void run() {
      for (int i = 0; i < 20; i++) {
         if (i%2==0){
            this.tv.play("快乐大本营");
         }else {
            this.tv.play("天天向上");
         }
      }
   }
}
//消费者--观众
class Watcher extends Thread{
   TV tv;

   public Watcher(TV tv) {
      this.tv = tv;
   }

   @Override
   public void run() {
      for (int i = 0; i < 20; i++) {
         tv.watch();
      }
   }
}
//产品--节目
class TV{
   //演员表演,观众等待
   //观众观看,演员等待
   String voice; //表演的节目
   boolean flag = true;

   //表演
   public synchronized void play(String voice){
      if (!flag){
         try {
            this.wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
      System.out.println("Player played"+voice);
      //通知观众观看
      this.voice = voice;

      this.notifyAll();
      this.flag = !this.flag;
   }

   //观看
   public synchronized void watch(){
      if (flag){
         try {
            this.wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
      System.out.println("watched"+voice);
      this.notifyAll();
      this.flag = !this.flag;
   }
}

线程池

提前创建好多个线程,放入线程池中,使用时直接获取,使用完后放回池中。可以避免频繁地创建销毁、实现重复利用,减少对系统性能的影响。

好处:

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
  • 便于线程管理
    • corePoolSize核心池的大小
    • maixmunPoolSize最大线程数
    • keepAliveTime线程无任务时最多保持多长时间后会终止
public class TestTP {
   public static void main(String[] args) {
      ExecutorService service = Executors.newFixedThreadPool(10);
      service.execute(new MyRunnable());
      service.execute(new MyRunnable());
      service.execute(new MyRunnable());
   
      service.shutdownNow();
   }
}

class MyRunnable implements Runnable{

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName());
	}
}

阿里提供的Java规范明确表明了线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors 返回的线程池对象的弊端如下: 1. FixedThreadPoolSingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 2.CachedThreadPoolScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

自定义线程池:

public class TestThreadPoolExecutor{

   public static void main(String[] args) {
       //等待队列
      BlockingQueue<Runnable> bq = new ArrayBlockingQueue<Runnable>(10);
       
       //创建一个带名字的线程池生产工厂
		NamingThreadFactory factory = new NamingThreadFactory("--name--");

       //3个核心线程,6个最大线程,1000毫秒线程空闲存活时间
      ThreadPoolExecutor tpe = new ThreadPoolExecutor(3,6,1000, TimeUnit.MILLISECONDS,bq,factory);

      tpe.execute(new MyRunnable());
      tpe.execute(new MyRunnable());
      tpe.execute(new MyRunnable());
      
      tpe.shutdown();
   }
}

自定义线程工厂

public final class NamingThreadFactory implements ThreadFactory {
   private final AtomicInteger threadNum = new AtomicInteger();
   private final String name;

   /**
    * 创建一个带名字的线程池生产工厂
    */
   public NamingThreadFactory(String name) {
      this.name = name;
   }

   @Override
   public Thread newThread(Runnable r) {
      Thread t = new Thread(r);
      t.setName(name + threadNum.incrementAndGet());
      return t;
   }
}

更多了解https://juejin.cn/post/6882992964709122055

这篇关于【笔记】Java多线程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!