并发编程线程基础(一)

1、关于进程与线程的理解

       进程是操作系统对资源进行分配、调度的单位,操作系统在分配资源时是把资源分配给进程的,对于CPU资源比较特殊,线程是调度 CPU资源的基本单位。
       操作系统中一个程序的执行周期叫进程;每个程序有很多任务构成,每一个任务都是一个线程,进程中的多个线程共享进程的资源。
       在 Java 中,当我们启动 main 函数时其实就启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程的一个线程,也称 主线程。
       不同的操作系统,对 CPU 调度资源的方式是不同的:
       Unix 系统是时间片机制:有个就绪队列,给每个线程分时间片,线程用完时间片或主动放弃时间片后,进入就绪队列的队尾。
       Windows 系统是抢占式调度。CPU 会根据各线程的优先级、饥饿时间等得到总优先级,先把时间片给总优先级最高的线程,线程用完时间片或主动放弃时间片后,再次对所有线程判断总优先级… …

       在JVM进程共享:堆(对象实例、数组)、方法区(加载的类变量、);
       线程专有:栈(方法调用的内存模型)、程序计数器。

2、线程创建与运行

       Java 中有三种线程创建方式,分别为 实现 Runnable 接口的 run 方法,继承 Thread 类并重写 run 方法,使用 FutureTask 方式。
(1)
       当创建完 thread 对象后 该线程并没有被启动执行,直到调用了 start 方法后才真正启动了线程,调用 start 方法后,线程没有马上执行,而是进入了就绪状态,就绪状态是指 该线程已经获取了除了 CPU 资源以外的其他资源,等待获取 CPU 资源后 才会真正处于运行状态。

public class ThreadTest {
    public static class MyThread extends Thread
    {
        @Override
        public void run()
        {
            System.out.println("I am a child thread.");
        }
    }

    public static void main(String[] args) {

        //创建线程
        MyThread thread=new MyThread();

        //启动线程
        thread.start();
    }
}

在这里插入图片描述

       使用继承的好处是,子类 MyThread 所有的变量确实是MyThread 自己拥有的,而不像实现接口,只是作为参数传进去。MyThread 可以用 this 获取当前线程,而实现接口的话只能用 Thread.currentThread 。
(2)实现Runnable接口。

public class RunnableTask implements Runnable{

    @Override
    public void run()
    {
        System.out.println("I am a child thread.");
    }

    public static void main(String[] args) {

        RunnableTask runnableTask=new RunnableTask();
        
        new Thread(runnableTask).start();
        new Thread(runnableTask).start();
    }
}

在这里插入图片描述
       实现接口的方法更好些,可以实现多个接口。
       以上两种 都是通过thread.start() 方法启动线程。 在start 方法中会调用 Runnable 类型的 target 的 run() 方法。
       以上两种方式都有一个缺点,就是任务没有返回值。
(3)使用 FutureTask 的方式

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallerTask implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "hello";
    }

    public static void main(String[] args) throws InterruptedException {

        //创建异步任务
        FutureTask<String> futureTask=new FutureTask<>(new CallerTask());

        //启动线程
        new Thread(futureTask).start();

        try
        {
            //等待任务执行完毕,并返回结果
            String result=futureTask.get();
            System.out.println(result);
        }
        catch (ExecutionException e)
        {e.printStackTrace();}
    }
}

       CallTask 类实现了 Callable 接口的 call() 方法,在 main 函数中首先创建了一个 FutureTask 对象,构造函数为 CallerTask 的实例,然后使用创建的 FutureTask 对象作为任务创建了一个线程并启动它,最后通过 futureTask.get() 等待任务执行完毕并返回结果。
在这里插入图片描述
       

3、线程通知和等待

(1)wait()

       必须在同步块中使用。也就是说,必须事先获取该对象的监视器锁 🔒 ,否则会抛出 IllgealMonitorStateException 异常。

👉 获取一个共享变量的监视器锁的方法:

  • 通过 synchronized 同步代码块,使用该共享变量作为参数:
synchronized(Object resource)
{
//同步代码块
}
  • 调用共享变量的方法,并且该方法使用了 synchronized 修饰。
public synchronized void Method1()
{
//同步方法
}

       
        将获取到该资源监视器锁的这个线程挂起,同时释放锁(只释放当前共享变量上的锁,如果当前线程还持有其他共享变量的锁,则这些锁是不会被释放的。),直到遇到其他线程调用了该线程的 interrupt() 方法,该线程抛出 InterruptedException 中断异常 或是 其他线程调用了该共享变量的 notify/notifyAll 方法 唤醒。
        另外要注意的是 ,一个线程可以从 挂起状态变为 可以运行状态(也就是被唤醒),即使该线程没有被其他线程调用 notify()、notifyAll() 方法进行通知,或者被中断,或者等待超时,这就是所谓的 虚假唤醒
        虽然虚假唤醒在实践中很少发生,但防患于未然,做法是不停地去测试 该线程被唤醒的条件是否满足,不满足则继续等待。 也就是说 在一个循环中调用 wait() 方法进行防范。退出循环的条件是 满足了唤醒该线程的条件。

synchronizedc (obj)
   {
       while (条件不满足)
         {obj.wait();}
    }

     比如,到条件 n>100 时才唤醒,而虚假唤醒是指,当不满足唤醒条件 ,即 事实是 n<100 时,线程就变为就绪状态了。所以需要 while( n<100)时,让资源处于 wait 等待状态。
     

(2)wait(long timeout)

    如果一个线程调用共享对象的该方法挂起后,没有在指定的 timeout ms【毫秒,1 ms = 0.001 s】时间内被其他线程调用该变量的 notify() 或者 notifyAll() 方法唤醒,那么该函数还是会因为超时而返回。
    如果 timeout 设置为 0 则和 wait 方法效果一样,因为在 wait 方法内部就是调用 wait (0) .。如果在调用该函数时,传递了一个负的 timeout 则会抛出 IllegalArgumentException 。
    

(2)wait(long timeout,int nanos)

    在其内部调用的是 wait(long timeout)函数,只有在 nano>0 时 才使参数 timeout 递增 1。

public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
            timeout++;
        }

        wait(timeout);
    }
(4)notify()

     一个线程调用共享对象的 notify() 方法后会唤醒一个 在该共享变量上 调用 wait 系列方法后被挂起线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程,是随机的。
     此外,被唤醒的线程不能马上从 wait 方法返回并继续执行,它必须在获取了共享对象的监视器后,才可以返回,也就是 唤醒它的线程释放了共享变量的锁后(也就是说,到 notify 所在的同步块执行结束,刚才拥有锁的线程也就释放了锁,自然而然的啊,退出同步块,也就是退出 synchronized 块,也就释放锁),被唤醒的线程也不一定会获取到共享对象的监视器锁😵,这是因为该线程还需要和其他线程一起竞争该锁,只有该线程竞争到了共享变量的监视器锁后,才可以继续执行。
     类似 wait() 方法,只有当前线程获取到了共享变量的监视器锁后,才可以调用共享变量的 notify() 方法,否则会抛出 IllegalMonitorStateException 异常。
    

(5)notifyAll()

    notify() 方法只会唤醒 一个 在该共享变量上调用 wait 系列方法后被挂起的线程。notifyAll() 方法会唤醒 所有 … …

例👀 notify 与 notifyAll 的应用:

public class NotifyExam {
    //创建资源 resourceA
    private static volatile Object resourceA = new Object();

    public static void main(String[] args) throws InterruptedException {

        //创建线程
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                //获取资源 resourceA 的监视器锁
                synchronized (resourceA)
                {
                    System.out.println("threadA get resourceA lock");

                    try
                    {
                        //resourceA 调用 wait 方法
                        System.out.println("threadA begin wait");
                        resourceA.wait();
                        System.out.println("threadA end wait");}
                        catch (InterruptedException e)
                        {e.printStackTrace();}
                }
            }
        });

        //创建线程
        Thread threadB=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA)
                {System.out.println("threadB get resourceA lock");
                try
                {System.out.println("threadB begin wait");
                resourceA.wait();
                System.out.println("threadB end wait");}
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
                }
            }
        });

        //创建线程
        Thread threadC=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA)
                {System.out.println("threadC begin notify");
                resourceA.notify();}
            }
        });

        //启动线程
        threadA.start();
        threadB.start();

        //让主线程先睡眠 1 s,
        // 这样才能让线程 A 和 B 都能调用到 wait 方法
        //然后线程 C 才能调用 notify 方法
        Thread.sleep(1000);
        threadC.start();

        //等待线程结束
        threadA.join();
        threadB.join();
        threadC.join();

        System.out.println("main over");

    }
}

运行结果:
在这里插入图片描述
    可以看到,只有线程 A 被唤醒,线程 B 没有被唤醒,说明 notify() 方法,只会唤醒一个线程。而如果改成 调用 notifyAll 方法,运行结果如下:
在这里插入图片描述
    从运行结果看来,线程 A 和 B 被挂起后,线程 C 调用 notifyAll() 方法会唤醒 resourceA 的等待集合里面的所有线程,这里 线程 A 和 B 都会被唤醒,只是 线程 B 先获取到 resource A 上的锁,然后从 wait() 方法返回。然后打印 “threadB end wait”,线程 B 执行完毕后,线程 A 又获取到 resource A 上的锁,然后从 wait() 方法返回。线程 A 执行完毕后,主线程返回,然后打印输出。

4、等待线程执行终止的 join 方法

       在项目实践中可能会遇到一个场景,就是需要等待 某几件事情完成后才能继续往下执行,比如,多个线程加载资源,需要等待多个线程全部加载完毕 再 汇总处理。Thread 类中的 join 方法就可以做这个事情。比如:在main主线程中启动了子线程 A,在主线程中调用threadA.join(); 执行到这里时,主线程被阻塞,直到其他线程调用了主线程的 interrupt() 方法中断了主线程,主线程抛出 InterrptedException 异常而返回 或线程 A 执行完毕,才会继续向下执行。
例 1 👀
     在主线程中启动两个子线程,然后分别调用它们的 join() 方法,那么主线程首先会在调用 threadOne.join 方法后阻塞,等待 threadOne 执行完毕后返回,然后主线程调用 threadTwo.join 方法后在此被阻塞,同样也是 等待 threadTwo 执行完毕后返回。

package Basic.Join;

public class JoinExam1 {
    public static void main(String[] args) throws InterruptedException {
        Thread threadOne=new Thread(new Runnable() {
            @Override
            public void run() {
                try
                {Thread.sleep(1000);}
                catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("child threadOne over!");
            }
        });

        Thread threadTwo=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("child threadTwo over!");
            }
        });

        //启动子线程
        threadOne.start();
        threadTwo.start();

        System.out.println("wait all child thread over!");

        //等待子线程执行完毕,返回
        threadOne.join();
        threadTwo.join();

        System.out.println("all child thread over!");
    }
}

    ❓为什么 threadOne 和 threadTwo 都需要先 sleep 1s呢❓我的理解是:如果没有 sleep 主线程先运行 threadOne.join,这时主线程阻塞,而 CPU 可能给 threadOne,也可能给 threadTwo,要是给了 threadTwo,就会先执行完 threadTwo 的 run(),再执行 threadOne 的,就看不到之后 threadTwo.join 的效果了。

运行结果:
在这里插入图片描述

例👀 2
     还有一种情况,线程 A 调用 线程 B 的 join 方法后会被阻塞,当其他线程 (我想,例子中的 threadTwo 休眠了 1 s 的原因,可能就是为了让 threadOne 被分到 CPU ,一定会被执行,这样的话,threadTwo 就是 “其他线程”)调用了 线程 A 的 interrupt() 方法中断了线程 A 时,线程 A 会抛出 InterruptedException 异常而返回,如:

public class JoinExam2 {

    public static void main(String[] args) throws InterruptedException {

        //线程 one
        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                {
                    System.out.println("threadOne begin run");
                }
                for(;;)
                {}
        }});

        //获取主线程
        final Thread mainThread=Thread.currentThread();

        //线程two
        Thread threadtwo=new Thread(new Runnable() {
            @Override
            public void run() {
                //休眠1s
                {
                    Thread.sleep(1000);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //中断主线程
                mainThread.interrupt();
                }});

        //启动子线程
        threadOne.start();

        //延迟1s启动线程
        threadtwo.start();

       try {
           //等待线程one执行结束
           threadOne.join();
       }
       catch (InterruptedException e)
       {System.out.println("main thread:" + e);}
    }
}

运行结果:
在这里插入图片描述
    以上代码 在 threadOne 线程里执行死循环,主线程调用 threadOne 的 join 方法,阻塞自己,等待线程 threadOne 执行完毕,从运行结果可以看到,主线程被阻塞后,CPU 是先给到 threadOne了,输出 “threadOne begin run” ,然后执行死循环,而 死循环不会永久霸占 CPU , 所以接下来时间片给到了 threadTwo ,休眠 1 s 后,(这 1 s 内会执行 threadOne 的死循环)等 CPU 给到 threadTwo 时,就会 调用主线程的 interrupt() 方法设置主线的中断标志,从结果看 ,在 主线程中的 threadOne.join() 处会抛出 InterruptedException 异常。
    这个例子就不是因为 threadOne 的 run 方法执行完毕(死循环是不可能执行完毕的🤓 ),而使得主线程得以继续向下进行,是因为抛出了中断异常… …

🤞 注意:
    需要先 start() ,再 join() 。因为在 join() 方法中有:

 while (isAlive()) {
                wait(0);
            }

5、让线程睡眠的 sleep 方法

    Thread类的静态方法,当一个执行中的线程调用了 Thread 的 sleep() 方法,调用线程会暂时让出指定时间的 执行权,也就是 在这期间不参与 CPU 的调度,但是该线程所拥有的监视器资源,比如锁🔒 还是持有不让出的。指定的睡眠时间到了后该函数会正常返回,线程处于就绪状态,然后参与 CPU 调度,获取到 CPU 资源后就可以继续运行了。如果在睡眠期间其他线程调用了该线程的 interrupt() 方法中断了该线程,则 该线程会在调用 sleep 方法的地方抛出 InterruptedException 异常而返回。
     如果传入的时间 long millis 是负数,会抛 IllegalArgumentPException 异常。
     如果传入的时间 long millis 是 0 ,意味着对当前所有的线程进行总优先级重新排序。
(👉 注意:sleep 的源码中,我看到关于抛出异常的注解:

 * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */

    如果其他线程中断 interrupt 了当前线程,当前线程抛出异常的时候,它的 interrupted 状态会被清除。

    
例 1 👀

package Basic.Sleep;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SleepTest {
    //创建一个独占锁
    private static final Lock lock=new ReentrantLock();
    public static void main(String[] args) {

        //创建线程A
        Thread threadA=new Thread(new Runnable() {
            @Override
            public void run() {
                //获取独占锁
                lock.lock();
                try
                {
                    System.out.println("child threadA is in sleep.");

                    Thread.sleep(10000);

                    System.out.println("child threadA is in awaked");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                finally {
                    //释放锁
                    lock.unlock();
                }
            }
        });

        //创建线程B
        Thread threadB=new Thread(new Runnable() {
            @Override
            public void run() {
                //获取独占锁
                lock.lock();
                try {
                    System.out.println("child threadB is in sleep");
                    Thread.sleep(10000);
                    System.out.println("child threadB is in awaked");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                finally {
                    lock.unlock();
                }
            }
        });

        //启动线程
        threadA.start();
        threadB.start();
    }
}

运行结果:
在这里插入图片描述
    以上代码,先创建了一个独占锁,然后创建了两个线程,每个线程在内部先获取锁🔒,然后睡眠,睡眠结束后释放锁,无论执行多少遍,都是 线程 A 先输出,还是线程 B 先输出,不会出现线程 A 和 B 交叉输出的情况。
    
例2 👀:当一个线程处于睡眠状态时,如果另一个线程中断了它,会抛出异常。

package Basic.Sleep;

public class SleepTest2 {
    public static void main(String[] args) throws InterruptedException {

        //创建线程
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                try
                {System.out.println("child thread is in sleep");
                Thread.sleep(10000);
                System.out.println("child thread is awaked");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });

        //启动线程
        thread.start();

        //主线程休眠2s
        Thread.sleep(2000);

        //主线程中断子线程
        thread.interrupt();
    }
}    

在这里插入图片描述
    因为在 thread 的 run 方法中是 try catch 形式的,所以代码正常结束。

6、让出 CPU 执行权的 yield 方法

     Thread 类有一个静态的 yield 方法,当一个线程调用 yield 方法时,实际是 暗示线程调度器 当前线程请求让出自己的 CPU 使用,是在告诉线程调度器 自己占有的时间片中还没有使用完的部分 自己不想使用了。但是线程调度器可以无条件忽略这个暗示。
    当一个线程调用 yield 方法时,当前线程会让出 CPU 使用权,然后处于就绪状态,线程调度器 会从 线程就绪队列里面 获取一个线程优先级最高的线程,当然也可能会调度到刚刚让出 CPU 的哪个线程来获取 CPU 执行权。
例 👀

package Basic.Yield;

public class YieldTest implements Runnable{
YieldTest()
{
    //创建并启动线程
    Thread t=new Thread(this);
    t.start();

}
    @Override
    public void run() {
        for(int i=0;i<5;i++)
        {
            //当 i=0 时 让出 CPU 执行权,放弃时间片,进行下一轮调度
            if(i%5==0)
            {    System.out.println(Thread.currentThread()+"yield cpu...");
               Thread.yield();
            }
        }
        System.out.println(Thread.currentThread()+" is over");
    }

    public static void main(String[] args) {
        new YieldTest();
        new YieldTest();
        new YieldTest();
    }
}

运行结果:
在这里插入图片描述

如果注释掉 Thread.yield() ,那就是没有 yield 的效果,运行结果:
在这里插入图片描述
    那个 “5” 是优先级,子线程的生命周期并不受父线程影响,子线程的初始优先级与父线程相同

    一般很少用这个方法,在调试或测试时 这个方法或许可以帮助复现由于并发竞争条件导致的问题,在设计并发控制时或许会有用途,在 java.util.concurrent.locks 包里面的锁时会看到该方法的使用。


✨ sleep() 方法 和 yield() 方法的区别:
    sleep()方法是使线程进入阻塞挂起状态,在这期间线程调度器不会去调度该线程,而 yield() 是使线程进入就绪状态(线程只是让出自己剩余的时间片,并没有被阻塞挂起,而是处于就绪状态,线程调度器下一次调度时就有可能调度到当前线程执行)。


✨ 会抛InterruptedException异常的方法:

  • wait()、join()、sleep()。

7、线程中断

    Java 中线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止线程的执行,而是被中断的线程根据中断状态自行处理。
    

  • void interrupt() 方法:
        中断线程,例如,线程 A 运行时,线程 B 可以调用线程 A 的 interrupt() 方法来设置线程 A 的中断标志为 true 并立即返回。设置标志位 仅仅是设置标志,线程 A 实际并没有被中断,它会继续往下执行。如果线程 A 因为调用了 wait 系列函数、join 方法 或者 sleep 方法而被阻塞挂起,这时候 若线程 B 调用线程 A 的 interrupt() 方法,线程 A 会在调用这些方法的地方抛出 InterruptedException 异常。(点解啊?没想明白,调用这些方法的时候不能持有 CPU ,那是怎么判断中断标志位的呢?怎么实现能抛出异常的呢?❔)
        
  • boolean isInterrupted() 方法:
        检测当前线程是否被中断,如果是返回 true,否则返回 false。不是 static 方法。源码:
public boolean isInterrupted() {
        return isInterrupted(false);
    }

    

  • boolean interrupted() 方法:
        检测 当前线程 是否被中断,如果是返回 true,否则返回 false。与 isInterrupted 不同的是,该方法如果发现当前线程被中断,则会清除中断标志,并且该方法是 static 方法,可以通过 Thread 类直接调用。该方法是获取 当前调用线程currentThread 的中断标志而不是 调用 interrupted() 方法的实例对象的中断标志。
public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

例1 👀 :使用 interrupted 优雅退出

public void run()
{
  try
  {
  ...
  //线程退出条件
  while(!Thread.currentThread().isInterrupted() && more work to do)
  //do more work
 }
  catch(InterruptedException e){
  //thread was interrupted during sleep or wait
}   
  finally
  {
  //cleanup,if required
  }
}

例2 👀 :根据中断标志判断线程是否终止

package Basic.Interrupt;

public class InterruptedTest {
    public static void main(String[] args) throws InterruptedException {
        //子线程
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                //如果当前线程被中断则退出循环
                while (!Thread.currentThread().isInterrupted())
                //没有被中断就输出hello
                {
                    System.out.println(Thread.currentThread() + "hello");
                }
            }
        });

        //启动子线程
        thread.start();

        //主线程休眠 1s,以便中断让子线程输出
        Thread.sleep(1000);

        //中断子线程
        System.out.println("main thread interrupt thread");
        //这个间隙,CPU 给子线程了,所以中间还会输出 hello
        thread.interrupt();

        //等待子线程执行完毕
        thread.join();
        System.out.println("main is over");
    }
}

    
例3 👀:当线程为了等待一些特定条件的到来时,一般会调用 sleep、wait 系列函数 或者 join 函数来阻塞当前线程。比如一个线程调用了 Thread.sleep(3000),那么线程就会阻塞,直到 3s 后才会从阻塞状态变为激活状态。但是有可能在 3s 内条件已被满足,如果一直等到 3s 后再返回,有点浪费时间,这时可以调用该线程的 interrupt 方法,强制 sleep 抛出 InterruptedException 异常而返回,线程恢复到激活状态:

package Basic.Interrupt;

public class InterruptedTest2 {
    public static void main(String[] args) throws InterruptedException {
        Thread threadOne=new Thread(new Runnable() {
            @Override
            public void run() {
                try
                {System.out.println("threadOne begin sleep for 2000 seconds.");
                Thread.sleep(2000000);
                System.out.println("threadOne awaking");
                }
                catch (InterruptedException e) {
                   System.out.println("threadOne is interrupted while sleeping.");
                   return;
                }
                System.out.println("threadOne-leaving normally");
            }
        });

        //启动线程
        threadOne.start();

        //主线程休眠,CPU 给到子线程,子线程休眠 2000 s
        Thread.sleep(1000);

        //打断子线程的休眠,让子线程从 sleep 方法返回
        threadOne.interrupt();

        //等待子线程执行完毕
        threadOne.join();
        System.out.println("main thread is over.");
    }
}

运行结果:
在这里插入图片描述
    本来 threadOne 线程是要休眠 2000s 后才会被唤醒的,本例就是通过调用 threadOne.interrupt() 方法打断了该线程的休眠,该线程在调用 sleep 方法处 抛出 InterruptedException 异常后返回。

例4 👀:
    interrupted() 和 isInterrupted() 方法的不同之处:

package Basic.Interrupt;

public class InterruptedAndIs {
    public static void main(String[] args) throws InterruptedException {
        Thread threadOne=new Thread(new Runnable() {
            @Override
            public void run() {
                for(;;)
                {}
            }
        });

        //启动线程
        threadOne.start();

        //设置中断标志
        threadOne.interrupt();

        //获取中断标志
        System.out.println("isInterrupted:"+threadOne.isInterrupted());

        //获取中断标志并重置
        //interrupted 是 static 方法,发现当前线程,也就是主线程的中断标志位
        //所以会输出 false
        System.out.println("isInterrupted:"+threadOne.interrupted());

        //获取中断标志
        System.out.println("isInterrupted:"+Thread.interrupted());
        
        //获取中断标志
        System.out.println("isInterrupted"+threadOne.isInterrupted());

        threadOne.join();
        System.out.println("main thread is over");
    }
}

在这里插入图片描述

8、线程上下文切换

    在多线程编程中,线程个数一般大于 CPU 个数,而 每个 CPU 同一时刻只能被一个线程使用 ,为了让用户感受多个线程是在同时执行的,CPU 资源的分配采用时间片轮询的策略,也就是 给每个线程分配一个时间片,线程在时间片内占用 CPU 执行任务。当前线程使用完时间片后,就会处于就绪状态,并让出 CPU 让其他线程占用,这就是上下文切换。那 让出 CPU 的线程 等下次自己占有 CPU 时 如何直到自己之前运行到哪儿了呢? 所以在切换线程上下文时 需要保存当前线程的执行现场。当在此执行时根据保存的执行现场信息恢复执行现场。

9、线程死锁

    死锁 是指 两个 或 两个以上的线程在执行过程中,因为争夺资源 而造成的 互相等待的现象。在无外力作用的情况下,这些线程会一直相互等待而无法继续运行下去:
在这里插入图片描述

✨ 死锁的产生必须具备以下四个条件:

  • 互斥条件
        线程对已经获取到的资源进行排他性使用,即该资源同时只由一个线程占用。如果此时还有其他线程请求获取该资源,则请求者只能等待,直至占有资源的线程释放该资源。
  • 请求并持有条件
        一个线程已经持有了至少一个资源,但又提出了新的资源请求,而 新资源已被其他线程占有,所以当前线程会被阻塞,但阻塞的同时并不释放自己已经获取的资源。
  • 不可剥夺条件
        线程获取到的资源 在自己使用完之前 不能被其他线程抢占,只有在自己使用完毕后 才由自己释放该资源。
  • 环路等待条件
    指在发生死锁时,必然存在一个线程 — 资源的环形链,即线程集合{T0,T1,T2,… …,Tn} 中的 T0 正在等待 T1 占用的资源,T1 正在等待 T2 占用的资源 … … Tn 正在等待已被 T0 占用的资源。
        
    例 👀:
package Basic.DeadLock;

public class DeadLockTest {
    // 创建资源
    private static Object resourceA;
    private static Object resourceB;

    public static void main(String[] args) {
        // 创建线程 A
        Thread threadA=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA) {
                    System.out.println(Thread.currentThread()+"get ResourceA");
                    try {
                        // 这里睡眠是为了让另一个线程获取资源
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread()+"Waiting get sourceB");

                    synchronized (resourceB) {
                    System.out.println(Thread.currentThread()+"get resourceB");
                    }
                }
                }
        });

        // 创建线程 B
        Thread threadB=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceB) {
                   System.out.println(Thread.currentThread()+"get ResourceB");
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }

                   System.out.println(Thread.currentThread()+"waiting get resourceA");
                   synchronized (resourceA) {
                   System.out.println(Thread.currentThread()+"get ResourceA");
                   }
                }
            }
        });

        //启动线程
        threadA.start();
        threadB.start();
    }
}

在这里插入图片描述

如何避免线程死锁

    要想避免死锁,只需要破坏掉至少一个构造死锁的必要条件即可。目前只有 请求并持有环路等待 条件是可以被破坏的。
    造成死锁的原因其实和 申请资源的顺序 有很大关系,使用资源申请的有序性原则 就可以避免死锁,资源的有序性会破坏资源的请求并持有条件 和 环路等待 条件。如:把上述代码 threadB 中的代码修改为 先获取 resourceA ,即 让 thread B 中运行结果如下:

在这里插入图片描述

10、守护线程与用户线程

    线程分为 用户线程 user 与 守护线程 daemon 。在 JVM 启动时会调用 main 函数,main 函数所在的线程就是一个用户线程,其实在 JVM 内部同时还启动了好多守护线程,比如垃圾回收线程。
    只有当用户线程全部执行完后,(无论守护线程是否执行完毕)JVM才会正常退出。也就是说,守护线程是否结束,并不影响 JVM 的退出。
例👀

package Basic.UserAndDaemon;
/**
 * 创建守护线程
 * */
public class CreateDaemon {
    public static void main(String[] args) {
        Thread daemonThread=new Thread(new Runnable() {
            @Override
            public void run() {               
            }
        });
        
        // 设置为守护线程
        daemonThread.setDaemon(true);
        daemonThread.start();
    }
}

       可以通过isDaemon(Thread thread)判断是否是守护线程,通过thread.SetDaemon( true )将线程设置为守护线程。

在线程中加入死循环 for ( ; ; ) { } 并且不把它设置为守护线程,运行结果:
在这里插入图片描述
       JVM 进程并没有退出,之前说过,死循环不会永久占用 CPU,所以执行了循环之后, CPU 会给到主线程,但是即使 主线程执行完毕了, JVM 也没有退出,这个结果说明:当父线程结束后,子线程还是可以继续存在的,也就是说 子线程的 生命周期 不受父线程的影响 也说明了 ,在用户线程还存在的情况下,JVM 进程并不会终止

如果把线程再设置为守护线程,运行结果:
在这里插入图片描述

       可以看到,主线程 main 运行结束后,就会终止 JVM 进程。这也说明了,如果当前进程中不存在用户线程,但是还存在 正在执行任务的守护线程,JVM 不等守护线程运行完毕 就会结束 JVM 进程
       main 线程运行结束后,JVM 会自动启动一个 DestoryJavaVM 的线程,该线程会等待所有童虎线程结束后 终止 JVM进程。

       如果希望在主线程结束后 ,JVM 进程马上结束,那么在创建线程时 可以将其设置为 守护线程;如果希望在主线程结束后 子线程继续工作,等子线程结束后 再让 JVM 结束,那么就将子线程设置为 用户线程。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值