线程

  • 文章目录

  • 线程和进程

    • 并发和并行
    • 线程的状态
    • 线程的构造方法和属性
    • 创建线程的方式
  • 线程安全问题

  • 如何解决线程安全

  • 单例模式

    • 饿汉模式
    • 懒汉模式
  • 阻塞队列

  • 定时器

线程和进程

  • 进程:进程是程序的一次执行过程,是资源分配的最小单位
  • 线程:是轻量级进程,是作业分配的最小单位
  • 关系:一个程序运行起来至少有一个进程(对于Java来说,程序执行起来后main()是必须执行的),一个进程中可以包含多个进程

并发和并行

  • 并发:指的是两个或者多个事件在同一时间段内发生

  • 并行:值得是两个或者多个事件在同一时刻发生
    在操作系统中,如果安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,在单核 CPU 中,每一时刻只能有一道程序执行,也就是微观上这些程序是分时的交替运行,只不过在宏观上感觉是同时运行;
    而在多核 CPU 系统中,这些程序可以并发执行并且可以分配到多个处理器(CPU)上,实现多任务并行执行,也就是利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。
    在这里插入图片描述
    线程的状态

  • 新建状态(New)
    此时线程已经有了相应的内存空间和其它资源,但是还没有开始执行。

  • 就绪状态(Runnable)
    新建线程对象后,调用该线程的 start()方法就可以启动线程。当线程启动时,线程进入就绪状态。
    由于还没有分配CPU,线程将进入线程队列排队,等待 CPU 服务,这表明它已经具备了运行条件。当系统挑选一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态。系统挑选的动作称之为“CPU调度"。一旦获得CPU线程就进入运行状态并自动调用自己的run方法。

  • 运行状态(Running)
    当就绪状态的线程被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的 run()方法。
    run()方法定义了该线程的操作和功能。运行状态中的线程执行自己的run方法中代码,直到调用其他方法或者发生阻塞而终止。

  • 阻塞状态(Blocked\waitting\TimedWaiting)
    一个正在执行的线程在某些特殊情况下,如被人为挂起或I/O请求,将让出 CPU 并暂时中止。
    自己的执行,进入堵塞状态。在可执行状态下,如果调用 sleep()、 suspend()、 wait()等方法,线程都将进入堵塞状态。
    堵塞时,线程不能进入排队队列,只有当引起堵塞的原因被消除后,线程转入就绪状态。重新到就绪队列中排队等待,这时被CPU调度选中后会从原来停止的位置开始继续执行。
    注意:阻塞被消除后是回到就绪状态,不是运行状态。

  • 死亡状态(Teminated)
    线程调用 stop()方法、destory()方法或 run()方法执行结束后,线程即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。![在这里

  • 线程的构造方法和属性

      - 常见构造方法
    
方法说明
Thread创建线程对象
Thread(Runnable target)使用Runnable对象创建线程对象
Thread(String name)创建线程对象并且命名
Thread(Runnable target,String name)使用Runnable对象创建线程对象并且命名
	 - 常见属性
属性获取方法
IdgetId( )
名称getName( )
状态getState( )
优先级getPriority( )
是否后台线程getDaemo( )
是否存活isAlive( )
是否被中断isInterruptted( )

属性说明:
(1)Id:线程的唯一标识,不会重复
(2)名称在调试的时候便于观察线程的活动
(3)状态表示线程当前所处的一个情况
(4)优先级高的线程理论上来说更容易被调度到
(5)关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行
(6)是否存活,即简单的理解,为 run 方法是否运行结束了

  • 创建线程的方式

      - 继承Thread类
    
public class MyThread extends Thread {
    //run方法中的代码块是线程调用start方法获得CPU时执行的语句
    @Override
    public void run() {
        System.out.println("我是继承Thread的线程");
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}
	 - 实现Runnable接口
public class MyRunnable implements Runnable{
    @Override
    public void run(){
        System.out.println("我是实现Runnable接口的新线程");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}

		- 匿名内部类
public class ThreadDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(){
          public void run(){
              System.out.println("我是线程1");
          }
        };
        t1.start();
        Thread t2 = new Thread(){
            public void run(){
                System.out.println("我是线程2");
            }
        };
        t2.start();
    }
}

  • 线程安全问题
    在多线程并发运行的过程中,会涉及到调度,线程之间抢占CPU等,这些问题就会致使程序运行的结果与我们预期结果不相符合,于是乎线程安全问题就来了!导致线程安全问题,我归纳了如下几点:
    (1)抢占式执行方式(线程不安全的万恶之源)
    线程间的调度完全是由内核负责,现成的先后执行顺序用户无法控制和感知
    (2)操作非原子性
    例如经典的自增操作,有三个步骤:首先将数据从内存读取到CPU的寄存器上,对数据进行自增操作(保存在寄存器中)
    最后将寄存器的数据写回内存,期间的操作如果是并发执行,就可能出现差错
    (3)多个线程尝试修改同一个变量
    一个线程可以修改一个变量,多个线程尝试读取同一个变量也没问题,多个线程修改多个变量也没问题
    (4)内存可见性
    (5)指令重排序
    代码在编译过程中,会被优化(编译器在保证不改变逻辑的情况下会调节指令顺序,提高代码的执行效率)
    既然会出现线程不安全,会影响程序执行结果,那么作为程序媛我们 当然就会想办法对以上问题提出解决措施!
    解决线程安全问题
    - 针对抢占式执行,由于线程在抢占CPU时是在操作系统内核进行的,这个就无能为力啦(#.#)
    - 针对操作非原子性,这个我们就提出了一个关键字Synchronized,采用加锁方式去解决线程不安全,举个简单例子:
    加锁前:
/**
 * 这里我假设两个线程各自自增10000次
 * 预期结果是它们自增的总次数应该是20000
 * */
public class ThreadDemo2 {
    static class Counter{
        public int count;
        public void increase(){
            count++;
        }
    }

    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread t1 = new Thread(){
          public void run(){
              for (int i = 0; i < 10000; i++) {
                  counter.increase();
              }
          }  
        };
        t1.start();
        Thread t2 = new Thread(){
          public void run(){
              for (int i = 0; i < 10000; i++) {
                  counter.increase();
              }
          }  
        };
        t2.start();
        System.out.println(counter.count);
    }
}
运行结果:11535

加锁后:

public class ThreadDemo2 {
    static class Counter{
        public int count;
       synchronized public void increase(){
            count++;
        }
    }

    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread t1 = new Thread(){
          public void run(){
              for (int i = 0; i < 10000; i++) {
                  counter.increase();
              }
          }
        };
        t1.start();
        Thread t2 = new Thread(){
          public void run(){
              for (int i = 0; i < 10000; i++) {
                  counter.increase();
              }
          }
        };
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(counter.count);
    }

}
运行结果:20000

锁的特点:锁是互斥的,一时刻只有一个进程可以获取到锁,如果其他进程也在竞争这个锁,就会发生阻塞等待;当且仅当第一个锁释放后,其他进程才能竞争锁
使用锁的方式:
(1)Synchronized加在普通方法前,相当于给this加锁
(2)Synchronized加在静态方法前,相当于给类对象(JVM在运行时将.class文件加载到内存获取到的)加锁
(3)Synchronized加到某个代码块前,相当于显示指定给某个对象加锁
综上所述:Synchronized的使用场景是多个线程尝试修改同一个变量

	 - 针对内存可见性,使用关键字Volatile(只能解决一个线程进行读操作,一个线程进行写操作)方式编译器过度优化,这里就不详细介绍,下面单利模式中的懒汉模式会提到
  • 单例模式
    单例模式是一种常见的设计模式,应用的场景是代码中有些概念不应该存在多个实例(比如说数据库连接池中的DataSource);

     	- 懒汉模式:在类加载过程中就实例化对象,线程安全
    
public class ThreadDemo3 {
    static class SingleTon{
        //将SingleTon的无参构造方法设置为private,这样它就只能在类内实例对象,不支持类外实例化对象
        private SingleTon(){
            
        }
        //将SingleTon实例对象设置为static,这样就仅有一份实例且不依赖于对象,仅与类有关
        private static SingleTon instance = new SingleTon();
        //获取SingleTon实例的方法
        public static SingleTon getInstance(){
            return instance;
        }
    }
    public static void main(String[] args) {
        SingleTon s = SingleTon.getInstance();
    }
}

	 - 懒汉模式:类加载过程没有初始化,第一次调用getInstance方法的时候初始化,由于操作非原子性,线程不安全
public class ThreadDemo5 {
    static class SingleTon{
        //将SingleTon的无参构造方法设置为private,这样它就只能在类内实例对象,不支持类外实例化对象
        private SingleTon(){

        }
        //还有一个问题,就是线程在实例化的时候编译器会自动优化,在此过程中也可能会出现现场不安全问题
        //在下面实例化SingleTon的步骤存在读操作和修改操作,因此需要使用Volatile关键字来防止过度优化造成线程不安全
        private volatile static SingleTon instance = null;
        public static SingleTon getInstance(){
            //这里存在一个线程安全问题,操作非原子性,此时我们就需要用到Synchronized关键字
            //假设存在两个线程在(2)(3)步骤中就会违背单例模式的意图,两个对象就实例对象2次,线程越多,实例的对象越多
            //实例化SingleTon的步骤:
            //(1)读取当前SingleTon对象
            //(2)判断SingleTon对象是否为null
            //(3)如果是null就new SingleTon
            //(4)返回结果
            //第一次if循环的作用是减少锁的开销,只有在第一次实例化时才会加锁
            if(instance == null){
                synchronized (SingleTon.class){
                    if (instance == null){
                        instance = new SingleTon();
                    }
                }
            }
            return instance;
        }

    }
    public static void main(String[] args) {
       SingleTon s = SingleTon.getInstance();
    }
}

  • 阻塞队列
    特性:
    ①具备两个方法,入队列和出队列
    ②入队列时如果队列满了就会被阻塞,直到其他线程有元素出队列时才可以继续出队列操作;
    ③出队列操作时如果队列空了就会被阻塞,直到有其他线程有其他元素入队列才可以继续进行出队列操作;
    可以用于模拟实现生产者消费者问题
public class ThreadDemo {
    static class BlockingQueue {
        private int head = 0;
        private int tail = 0;
        //此处的size设计要读取判断和修改,可能会出错,因此要加上volatile关键字
        private volatile int size = 0;
        private int[] array = new int[1000];

        public void put(int value) {
            //由于此处的修改操作非原子性,那么就需要给当前对象加锁
            synchronized (this) {
                //判断当前队列是不是满了,若队列已满就阻塞
                while (tail == array.length) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }


                    array[tail] = value;
                    tail++;
                    if (tail == array.length) {
                        tail = 0;
                    }
                    size++;
                    //已经完成插入操作,可以唤醒线程(这里是唤醒出队列线程)
                    notifyAll();
                }
            }

        }

        public int take() {
            int ret = -1;
            //由于此处的修改操作非原子性,那么就需要给当前对象加锁
            synchronized (this) {
                //判断当前队列是不是为空,如果为空就阻塞
                while (size == 0) {
                        try {
                            wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                    ret = array[head];
                    head++;
                    if (head == array.length) {
                        head = 0;
                    }
                    //出队列操作已完成,可以唤醒线程(这里是唤醒入队列线程)
                    size--;
                    notifyAll();
                }
                return ret;
            }

        }
    //模拟生产者消费者
        public static void main(String[] args) {
        BlockingQueue queue = new BlockingQueue();
        Thread producer = new Thread(){
          public void run(){
              for (int i = 0; i < 10000; i++) {
                  queue.put(i);
                  try {
                      Thread.sleep(500);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.println("生产者生产元素:"+i);
              }
          }
        };
        producer.start();
        Thread costumer = new Thread(){
           public void run(){
               while(true){
                   int ret = queue.take();
                   System.out.println("消费者消费元素:"+ret);
               }
           }
        };
        costumer.start();
        }
    }
}

  • 定时器
    定时器就像一个极其灵敏的闹钟,时间非常精确甚至可以达到微秒毫秒级,经常用于Internet
    定时器的组成:
    ①有一个Task类,负责描述任务逻辑,规定任务的执行时间
    ②有一个Timer类,用阻塞优先队列来组织Task对象
    ③有一个扫描线程,来循环检测判断当前进程是否可以执行任务
    ④有一个schedule方法负责安排任务,让调用者调用
public class ThreadDemo2 {
    //此处的Task描述的是一个一段要执行的任务逻辑,由于一会儿要在优先级阻塞队列中使用
    //所以Task类要可比较
    static class Task implements Comparable<Task>{
        //Runnable中有一个run()用来描述要执行任务的内容
        private Runnable command;
        //执行任务的时间,此处是一个相对时间
        private long time;
        public Task(Runnable command,long after){
            this.command = command;
            this.time = System.currentTimeMillis()+after;
        }

        @Override
        public int compareTo(Task o) {
            return (int) (this.time-o.time);
        }
        public void run(){
            command.run();
        }
    }
    //扫描线程类
    static class Worker extends Thread{
        private PriorityBlockingQueue<Task> queue = null;
        private Object mailBox = null;
        public Worker(PriorityBlockingQueue<Task> queue,Object mailBox){
            this.queue = queue;
            this.mailBox = mailBox;
        }
        public void run(){
            while(true){
                //取出队首元素,判断是否时间到了
                try {
                    Task task = queue.take();
                    //检查当前任务时间是否到了
                    long curTime = System.currentTimeMillis();
                    //任务时间大于当前时间,证明时机未到,就把任务塞回优先级队列
                    //此处有可能出现"忙等现象",CPU一直在不停地比较执行任务时间和curTime,造成资源浪费
                    if (task.time > curTime){
                        queue.put(task);
                        synchronized (mailBox){
                            mailBox.wait(task.time - curTime);
                        }
                    }else {
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }

            }
        }
    }
    static class Timer{
        //为了避免忙等现象,需要借用辅助对象
        private Object mailBox = new Object();
        private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
        public Timer(){
            //创建线程
            Worker worker = new Worker(queue,mailBox);
            worker.start();
        }
        //提供方法,安排任务,让调用者调用
        public void schedule(Runnable command,long after){
                Task task = new Task(command,after);
                queue.put(task);
                synchronized (mailBox){
                    mailBox.notify();
                }
        }
    }

    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hehe");
                timer.schedule(this,1000);
            }
        },1000);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值