美文网首页
Java Thread类 线程

Java Thread类 线程

作者: 青果果 | 来源:发表于2018-04-23 21:25 被阅读0次

    线程

    Thread类在 API文档中的描述

    线程是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。

    每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。
    每个线程都可以或不可以标记为一个守护程序。

    当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,
    并且当且仅当创建线程是守护线程时,新线程才是守护程序。
    当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法)。

    Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:
    调用了 Runtime 类的 exit 方法,并且安全管理器允许退出操作发生。
    非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,
    还是通过抛出一个传播到 run 方法之外的异常。

    线程调度模型

    分时调度模型

    所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片

    抢占式调用模型

    优先让优先级高的线程使用CPU,如果线程的优先级相同,会随机选择一个
    优先级高的线程获取的CPU时间片相对多一些

    java使用的是抢占式调用模型

    创建线程的方法

    方法一 继承 Thread

    继承 Thread 重写run()方法

    public class ThreadDemo {
        public static void main(String args[]) {
            //方便演示直接new ,可以创建类 继承自Thread
            Thread thread = new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        System.out.println(getName()+":"+i);
                    }
                }
            };
            thread.setName("线程1");
            thread.start();
            //Thread.currentThread() 可以拿到当前所在线程
            System.out.println("main方法所在线程名字是 :"+Thread.currentThread().getName());
        }
    }
    
    运行结果

    方法二,构造传入Runnable

    可以避免单继承带来的局限性
    适合多个相同程序的代码去处理同一个资源

           //Thread 是实现了 Runnable接口
           //Thread 带参构造  Runnable是接口,且只有一个run()方法
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
    
                }
            });
            thread.start();
    

    这样看,可能看不出区别,继续看

    Thread thread1 = new Thread(new MyRunnable());
    Thread thread2 = new Thread(new MyRunnable());
    thread1.setName("线程1");
    thread2.setName("线程2");
    thread1.start();
    thread2.start();
            
    class MyRunnable implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
    
    运行结果,可能需要多次运行才能达到这种交替的效果

    线程的优先级

    线程优先级高仅仅表示线程获取的CPU时间片的几率高
    因为存在随机性,所以并不是优先级高,就一定先执行

    默认优先级是5,可以更改优先级,超过范围(1-10)会抛出异常

    /**
         * The minimum priority that a thread can have.
         */
        public final static int MIN_PRIORITY = 1;
    
       /**
         * The default priority that is assigned to a thread.
         */
        public final static int NORM_PRIORITY = 5;
    
        /**
         * The maximum priority that a thread can have.
         */
        public final static int MAX_PRIORITY = 10;
    
      //获取线程优先级
      thread1.getPriority();
      //设置线程优先级
      thread2.setPriority(8);
    

    线程休眠,线程加入,线程礼让

    有部分方法是静态方法,实际使用可能与下面示例不一致

            try {
                //线程休眠1秒
                thread1.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            try {
                //线程加入 等待该线程终止,才执行其他线程
                thread1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            //线程礼让 暂停当前正在执行的线程对象,并执行其他线程
            thread1.yield();
    

    守护线程

    将该线程标记为守护线程或用户线程
    当正在运行的线程都是守护线程时,Java 虚拟机退出。
    该方法必须在启动线程前调用

    有个例子,坦克大战,如果老窝被攻击,那就都死了
    如果坦克都死了,老窝可以还活着

            //设置线程为守护线程
            thread1.setDaemon(true);
            //设置线程为守护线程
            thread2.setDaemon(true);
            thread1.start();
            thread2.start();
            //判断线程是否为守护线程
            thread1.isDaemon();
            thread2.isDaemon();
    

    线程中断

    stop()方法已经过时,因为具有不安全性
    interrupt 中断线程,把线程设置为终止状态,线程在调用其他方法时抛出异常 InterruptedException
    通过抛出异常来中断线程

            //中断线程
            thread1.interrupt();
            thread2.interrupt();
    

    线程的生命周期

    新建 创建线程对象
    就绪 有执行资格,资源准备好,但没有执行权
    运行 拿到执行权
    阻塞:由于一些操作,让线程处于该状态,没有执行资格,没有执行权
    而由于另一些操作却可以把它激活,激活后处于就绪状态
    死亡 线程对象变成垃圾,等待被回收

    线程的生命周期

    线程安全问题

    哪些原因会导致线程安全问题

    1. 是否多线程环境
    2. 是否有共享资源
    3. 是否有多条语句操作共享数据

    可以通过同步代码块,同步方法解决,但会降低运行效率

    同步代码块的锁可以是--任意对象

    同步方法的锁是--this,静态同步方法的锁是--class

     //同步代码块
     synchronized (new Object()){
                ...
            }
    

    集合想要线程安全,可以直接通过集合工具类的Collections的方法


    获取线程安全的集合

    JDK5之后可以用Lock锁

    Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作,此实现允许更灵活的结构

    java.util.concurrent.locks  接口Lock
    
    所有已知实现类: 
    ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock 
    //锁定和取消锁定出现在不同作用范围中时,
    //必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。 
     Lock l = ...; 
         l.lock();
         try {
             // access the resource protected by this lock
         } finally {
             l.unlock();
         }
    

    等待唤醒机制

    Object类中的方法:
    wait(): 唤醒在此对象监视器上等待的单个线程
    notify(): |在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。 |

    为啥是在object类中,因为锁可以是任意对象

    线程状态转换图

    线程状态转换图

    线程组 ThreadGroup

    线程组表示一个线程的集合
    线程默认情况下属于main线程组,和main线程都属于同一个组

    public ThreadGroup(String name)
     //获取线程所在的线程组
     thread1.getThreadGroup();
    

    第三种方式实现线程 Callable

    public interface Callable<V>返回结果并且可能抛出异常的任务。
    实现者定义了一个不带任何参数的叫做 call 的方法。

    Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。
    但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。

    Executors 类包含一些从其他普通形式转换成 Callable 类的实用方法。

    配合线程池,Callable接口,创建带返回值的线程

    线程常见的面试题

    1:多线程有几种实现方案,分别是哪几种?
    继承Thread类
    实现Runnable接口
    扩展一种:实现Callable接口。这个得和线程池结合。
    2:同步有几种方式,分别是什么?
    同步代码块
    同步方法

    3:启动一个线程是run()还是start()?它们的区别?
    start();
    run():封装了被线程执行的代码,直接调用仅仅是普通方法的调用
    start():启动线程,并由JVM自动调用run()方法

    4:sleep()和wait()方法的区别
    sleep():必须指时间;不释放锁。
    wait():可以不指定时间,也可以指定时间;释放锁。

    5:为什么wait(),notify(),notifyAll()等方法都定义在Object类中
    因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。
    而Object代码任意的对象,所以,定义在这里面。

    同步异步,并行串行

    • 同步:同步很好理解,就是一段代码执行是按照顺序执行的
      上一行代码有返回结果才会执行下一行
    int i = 0;
    int k = functionA(i); // 同步执行 是在functionA返回结果后才执行下面的functionB方法
    functionB();
    
    • 异步:在发起异步调用后不会马上得到结果,就执行下面一行代码

    • 串行:简单理解为单个线程执行同一时间执行单个任务,好比单行道

    • 并行:多CPU,同一时间可以执行多个任务,多行道

    举例:下载一张图片耗时2秒, 如果要下载十张图片,串行的话就是一张图片一张图片的下载,
    总共耗时20秒,而并行的话,如果2个线程并行,那么相当于一次同时下载两张图片,时间减半只需要10秒

    并发并行

    举例:
    你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以我认为它们最关键的点就是:是否是『同时』

    链接:https://www.zhihu.com/question/33515481/answer/58849148
    来源:知乎
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    在现实机器中:
    单核的机器,都是并发 concurrence 执行的。
    多核的机器,都是并行 parallel 中嵌套着并发 concurrence 运行的。
    如果存在无限核心的机器,则所有任务都可以是并行运行的。

    一个4核心的机器,同时运行4个长时间Thread,每个核心跑一个,【对于进程来说】,这就是并行运行。

    一个4核心的机器,运行5个Thread,有一个核心跑了2个线程,他们在轮替要CPU执行上下文切换。 其余核心都是跑一个线程,【对于这个进程来说】,就是并行 parallel 中夹杂并发concurrence。

    一个单核心的机器,无论运行多少个线程,都是concurrence,即使用了.NET中的TPL,也是concurrence。


    image.png

    并发:两个队列交替使用一台咖啡机
    并行:两个队列同时使用两台咖啡机互不干扰
    串行:一个队列使用一台咖啡机

    并行和并发都可以是多个线程,就看这些线程能不能同时被多个CPU执行,
    如果能,就是并行,如果不能就是并发,多个线程被一个CPU轮流执行

    相关文章

      网友评论

          本文标题:Java Thread类 线程

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