美文网首页
多线程编程

多线程编程

作者: 昵乄称 | 来源:发表于2020-08-11 11:10 被阅读0次

    创建线程

    1. 创建线程的三种方式
    创建方式
    Thread class 继承Thread类(重点)
    Runnable 接口 实现Runnable接口(重点)
    Callable 接口 实现Callable接口(了解)

    Thread创建线程

    public class ThreadStudy extends  Thread {
        @Override
        public void run() {
            //super.run();
            for (int i = 0; i < 200; i++) {
                System.out.println("测试线程======="+i);
            }
        }
    
        public static void main(String[] args) {
            //创建一个线程对象
            ThreadStudy threadStudy = new ThreadStudy();
            //调用start()方法,开启线程
            threadStudy.start();
            for (int i = 0; i < 10000; i++) {
                System.out.println("学习线程=========="+i);
            }
        }
    }
    

    ps:线程开启不一定立即执行,由cpu调度执行

    Runable 创建线程

    /**
     * 创建线程方法二:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类
     * 推荐使用runnable接口,java单继承的局限性
     */
    public class StudyRunnable implements Runnable {
        public void run() {
            for (int i = 0; i < 200; i++) {
                System.out.println("测试线程**********************"+i);
            }
        }
    
        public static void main(String[] args) {
            StudyRunnable studyRunnable = new StudyRunnable();
            new Thread(studyRunnable).start();
            for (int i = 0; i < 1000; i++) {
                System.out.println("主线程*******************"+i);
            }
        }
    }
    

    小结:

    继承Thread类

    • 备多线程能力子类继承Thread类具

    • 启动线程:子类对象.start()

      threadStudy.start();
      
    • 不建议使用:避免oop单继承局限性

    实现Runable接口

    • 实现Runable具有多线程能力

    • 启动线程:传入目标对象+Thread对象.start()

      new Thread(studyRunnable).start();
      
    • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

    线程状态

    线程休眠:

    Thread.sleep(1000);
    
    1. sleep(时间)指定当前线程阻塞的毫秒数

    2. sleep存在异常InterruptedException;

    3. sleep时间达到后线程进入就绪转态

    4. sleep可以模拟网络延时,倒计时等

    5. 每个对象都有一个锁,sleep不会释放锁

      public class SleepThread {
          //时间倒计时
          public static void main(String[] args) {
              Date date = new Date(System.currentTimeMillis());//获取当前系统时间
              while (true){
                  try {
                      Thread.sleep(1000); //阻塞线程1000毫秒也就是1秒
                      System.out.println(new SimpleDateFormat("yyyy:MM:dd HH:mm:ss").format(date));
                      date =  new Date(System.currentTimeMillis()); //更新系统时间
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
      }
      

      输出:

      2020:08:10 16:36:21
      2020:08:10 16:36:22
      2020:08:10 16:36:23
      2020:08:10 16:36:24
      2020:08:10 16:36:25
      2020:08:10 16:36:26
      2020:08:10 16:36:27
      2020:08:10 16:36:28
      2020:08:10 16:36:29
      2020:08:10 16:36:30
      2020:08:10 16:36:31 
      

    线程礼让:

    yield()方法:表示当前线程对象提示调度器自己愿意让出CPU资源,但是调度器可以自由忽略

    • 调用该方法后,线程对象处于就绪状态,所以完全有可能:某个线程调用yield()方法后,调度器又把它调度出来重新运行。
    • 只是一种意愿,不保证行动。谁先执行还要由JVM决定。

    在开发中一般不会用到该方法,只在调试或测试的时候用

    • sleep()和yield()的区别
      • 都能是调用的线程放弃CPU,把运行机会给其他线程
      • sleep()会给其他线程的运行机会,且不考虑优先级,但是yield()只会给同优先级或更高优先级的线程运行的机会(不一定能运行)
      • 调用sleep()后,线程进入计时等待状态,而调用yield()后进入就绪状态(随时等待JVM的再次调用)
    public class StudyRunnable implements Runnable {
        public void run() {
            System.out.println("测试线程**********************");
    
        }
    
        public static void main(String[] args){
            StudyRunnable studyRunnable = new StudyRunnable();
            new Thread(studyRunnable).start();
            Thread.yield();
            System.out.println("主线程*******************");
        }
    }
    

    输出:

    测试线程**********************
    主线程*******************
    

    线程强制执行(JOIN):

    • Join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞
    • 可以认为是插队
    public class ThreadJoin implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                System.out.println("vip线程来了"+i);
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            ThreadJoin threadJoin = new ThreadJoin();
            Thread thread = new Thread(threadJoin);
            
            thread.start();
            //主线程
            for (int i = 0; i < 500; i++) {
                if(i == 20){
                    thread.join();//插队
                }
                System.out.println("测试线程"+i);
            }
        }
    }
    

    线程优先级:

    • 线程调度器按照优先级决定调度哪个线程
    public class ThreadJoin implements Runnable {
        @Override
        public void run() {
                System.out.println(Thread.currentThread().getName()+"***"+"vip线程来了");
        }
    
        public static void main(String[] args) throws InterruptedException {
            ThreadJoin threadJoin = new ThreadJoin();
            Thread thread1= new Thread(threadJoin,"最低优先级");
            thread1.setPriority(Thread.MIN_PRIORITY);
            thread1.start();
    
            Thread thread2= new Thread(threadJoin,"最高优先级");
            thread2.setPriority(Thread.MAX_PRIORITY);
            thread2.start();
    
            Thread thread3= new Thread(threadJoin,"中间优先级");
            thread3.setPriority(Thread.NORM_PRIORITY);
            thread3.start();
            
            System.out.println(thread1.getPriority());
            System.out.println(thread2.getPriority());
            System.out.println(thread3.getPriority());
    
        }
    }
    

    输出:

    最高优先级***vip线程来了
    中间优先级***vip线程来了
    最低优先级***vip线程来了
    1
    10
    5
    
    //优先级 优先级高的先执行
    //线程默认优先级为5
    Thread.MIN_PRIORITY=1
    Thread.MAX_PRIORITY=10
    Thread.NORM_PRIORITY=5
    getPriority(),setPriority(int xxx); 改变线程的优先级
    

    优先级的设定建议在start()调度前

    ps.优先级低只是意味着获得调度的概率低,并不是低优先级就不会被调度,都是看CPU的调度

    线程分类:

    • 在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)

      • 用户线程和守护线程的区别

      • 二者其实基本上是一样的。唯一的区别在于JVM何时离开。

      • 用户线程:当存在任何一个用户线程未离开,JVM是不会离开的。

      • 守护线程:如果只剩下守护线程未离开,JVM是可以离开的。

      • 在Java中,制作守护线程非常简单,直接利用.setDaemon(true)

    User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:

    • 如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,

    • 虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了

      public class DaemoThread implements  Runnable {
          public static void main(String[] args) {
              DaemoThread demoThread = new DaemoThread();
              Thread thread = new Thread(demoThread,"守护线程正在执行");
              thread.setDaemon(true); //设置守护线程
              thread.start();
              new Thread(new Runnable() {
                  @Override
                  public void run() {
                      while (true){
                          try {
                              Thread.sleep(11*1000);
                              System.out.println(Thread.currentThread().getName()+"==="+thread.isDaemon());
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              },"用户线程正在执行").start();
              try {
                  System.in.read();
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
          @Override
          public void run() {
              int i = 0;
              while(true){
      
                  try {
                      Thread.sleep(5*1000);
                      System.out.println(Thread.currentThread().getName()+"========="+i++);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
      }
      

      线程同步:

      • 线程同步其实就是一个等待机制,多个需要同时访问此对象的线程同时进入这个对象的线程池形成队列,等待前面线程使用完毕,

        下一个线程再使用

      • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,

        为了保证数据在方法中被访问时的正确性,在访问是加入锁机制synchronized,

        当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:

          • 一个线程持有锁会导致其他所有需要此锁的线程挂起
          • 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题;
          • 如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引发性能问题;\

      并发:

      同一个对象被多个线程调用

      队列和锁:

      同步方法:

      • 同步private 关键字来保护数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,就是==synchronized==关键字,包括两种用法:

        • synchronized方法和synchronized块
        • 同步方法:public synchronized void method(int args){}
      • synchronized方法控制对“对象”的访问,每个对象对应一把锁每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程阻塞,方法一旦执行,就独占该锁。直到该方法返回才释放锁,后面被阻塞线程才能获得这个锁,继续执行:

        • 缺陷:若将一个大的方法申明为synchronized将会影响效率
    public class TestSynchronized  {
        public static void main(String[] args) {
            Tiktok tiktok = new Tiktok(10);
            Thread t1 = new Thread(tiktok,"小赵");
            Thread t2 = new Thread(tiktok,"小钱");
            Thread t3 = new Thread(tiktok,"小孙");
            Thread t4 = new Thread(tiktok,"小李");
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
    
    class Tiktok implements Runnable{
        private int tickenum;
        private boolean flag = true;
        public Tiktok(int tickenum) {
            this.tickenum = tickenum;
        }
        @Override
        public void run() {
            while (flag){
                try {
                    moth();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }        
        }
        public synchronized void moth() throws InterruptedException {
            if(tickenum<=0){
                flag =false;
                return;
            }
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName()+"\t 号码为:"+tickenum--);
        }
    }
    

    相关文章

      网友评论

          本文标题:多线程编程

          本文链接:https://www.haomeiwen.com/subject/smvedktx.html