多线程-基础

    0)什么是线程?

    1. 一个Java应用是一个进程。
    2. 一个进程可以有多个线程,线程是操作系统调度的最小单位,每个线程都拥有单独的栈内存用来存储本地数据。

    线程并不是越多越好。
    A)线程是宝贵的资源,一个线程大约10M。
    B)多线程运行时存在上下文切换。

    1)如何创建一个线程?

    继承Thread类 Or 实现 Runnable接口/Callable接口
    Runnable接口无返回值,无法抛出异常;Callable接口有返回值。
    
    实现Runnable接口的run方法实际实现是线程执行任务的内容;继承Thread类,重写run方法即线程执行的任务内容。
    

    为什么说实现 Runnable 接口比继承 Thread 类实现线程要好?
    A:Runnable 里只有一个 run() 方法,它定义了需要执行的内容,在这种情况下,实现了 Runnable 与 Thread 类的解耦,Thread 类负责线程启动和属性设置等内容,权责分明。
    B:Java 语言不支持双继承,如果我们的类一旦继承了 Thread 类,那么它后续就没有办法再继承其他的类,这样一来,如果未来这个类需要继承其他类实现一些功能上的拓展,它就没有办法做到了,相当于限制了代码未来的可拓展性。

    2)如何开启或调度一个线程,调用哪个方法?

    ()->{
    }.start();  //start方法
    

    3)如何正确地停止线程?

    while (!Thread.currentThread().isInterrupted() && more work to do) {
        do more work
    }
    

    4)线程有几种状态?状态切换

    线程的状态共有6种。(JDK,API中定义的。)
    1:创建好线程还未执行(未调用Start()) 初始化状态
    2:()->{
    }.start(); 调用start()方法后,由操作系统调度。获取到时间片就执行,时间片到期后就绪,继续等待操作系统调度。 这个过程统称为运行状态。
    3:synchronized关键字,等待其它线程释放锁。   阻塞状态
    4:超时等待   将执行权交给其它线程,有限时间等待其它线程通知它再回到运行状态。   自动回到运行时状态。
    设置了时间参数的 Thread.sleep(long millis) 方法;
    设置了时间参数的 Object.wait(long timeout) 方法;
    设置了时间参数的 Thread.join(long millis) 方法;
    设置了时间参数的 LockSupport.parkNanos(long nanos) 方法和 LockSupport.parkUntil(long deadline) 方法。
    
    5:等待状态   将执行权交给其它线程,等待其它线程通知它再回到运行状态。
    没有设置 Timeout 参数的 Object.wait() 方法。
    没有设置 Timeout 参数的 Thread.join() 方法。
    LockSupport.park() 方法。
    
    6:终止状态
    

    线程生命周期不可逆:一旦进入 Runnable 状态就不能回到 New 状态;一旦被终止就不可能再有任何状态的变化。所以一个线程只能有一次 New 和 Terminated 状态,只有处于中间状态才可以相互转换。

    5)线程优先级

    setPriority()设置线程的优先级
    1-10
    线程优先级依赖于操作系统。
    

    6)守护线程

    thread.setDaemon(true);
    设置守护线程要在调用前。
    

    7)sleep方法和wait方法有什么区别?

    sleep方法和wait方法都可以用来放弃CPU一定的时间,不同点在于如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器。
    
    sleep方法是线程的方法,wait方法是Object的方法。
    
    wait 方法必须在 synchronized 保护的代码中使用,而 sleep 方法并没有这个要求。
    

    8)如何检测一个对象持有对象监视器?

    Thread类提供了一个holdsLock(Object obj)方法,当且仅当对象obj的监视器被某条线程持有的时候才会返回true,注意这是一个static方法,这意味着”某条线程”指的是当前线程。
    

    9)一个线程出现异常会怎么样?

    如果这个异常没有被捕获的话,这个线程就停止执行了。另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放。
    

    10)线程的基本操作

    yield
    public static native void yield();这是一个静态方法,一旦执行,它会是当前线程让出CPU,但是,需要注意的是,让出的CPU并不是代表当前线程不再运行了,如果在下一次竞争中,又获得了CPU时间片当前线程依然会继续运行。
    
    sleep
    public static native void sleep(long millis)方法显然是Thread的静态方法,很显然它是让当前线程按照指定的时间休眠,其休眠时间的精度取决于处理器的计时器和调度器。需要注意的是如果当前线程获得了锁,sleep方法并不会失去锁。
    
    join
    public final synchronized void join(long millis) 
    public final synchronized void join(long millis, int nanos) 
    public final void join() throws InterruptedException
    

    11)写一个Java线程死锁程序

    1)两个线程里面分别持有两个Object对象:lock1和lock2。这两个lock作为同步代码块的锁;
    2)线程1的run()方法中同步代码块先获取lock1的对象锁,Thread.sleep(xxx),时间不需要太多,50毫秒差不多了,然后接着获取lock2的对象锁。这么做主要是为了防止线程1启动一下子就连续获得了lock1和lock2两个对象的对象锁
    3)线程2的run)(方法中同步代码块先获取lock2的对象锁,接着获取lock1的对象锁,当然这时lock1的对象锁已经被线程1锁持有,线程2肯定是要等待线程1释放lock1的对象锁的
    这样,线程1"睡觉"睡完,线程2已经获取了lock2的对象锁了,线程1此时尝试获取lock2的对象锁,便被阻塞,此时一个死锁就形成了。