java 多线程基础学习

2022-09-20 18:34:55 浏览数 (1)

一、线程概念

  1. 进程:程序运行资源分配的最小单位,每个进程都有自己独立的代码和数据空间,操作系统为进程分配各种资源。
  2. 线程:CPU调度的最小单位,也叫轻量级进程,每个线程都有各自的堆栈、计数器和局部变量等属性。
  3. 线程和进程关系:线程依赖于进程而存在,多个线程共享进程的内存空间。

异步和同步:

  • 同步:在同一个线程中执行一段业务逻辑时,按顺序执行,在前面的结果没有返回时,后面的程序就不能往下执行,必须等待前一个结果返回时后面的才能往下执行。
  • 异步:多线程是实现异步的一个手段,异步是当一个请求发送给被调用者,调用者可以不用等待结果返回而可以做其他的事情。

就像我们每天去到公司一样,先打开电脑,在电脑开机过程中可以去接点水喝,而不用等待电脑开机再去接水喝。

并发和并行:

  • 并行:同一时刻可以处理事情的能力,比如一台四核的电脑,可以同时运行四个任务,我们就说这台电脑并行度是4
  • 并发:在单位时间内可以处理的事情,主要还是看这台电脑时间分片的长短,如果这台电脑的时间分片为100ms,在1s内就可以处理10个任务,那么就说这台电脑的并发度是40。因为任务是在交替执行的,并发的任务就会让我们以为这些任务是同时执行的,其实还是顺序执行的。

二、线程的优势

现如今服务器多采用多处理器,CPU的基本调度单位是线程,如果一个程序只有一个线程的话,那么就只能发挥一个CPU的作用,其他CPU的资源将会闲置,这在很大程度上浪费了CPU的资源,如果能够多个CPU同时发挥作用,在设计正确的情况下,可以通过提高CPU资源的利用率来提高系统的吞吐率。

三、线程状态

通过Thread.state 可以查看线程的状态:

  1. NEW:新建状态。这种状态下线程还没有开始,也就是还没有调用start方法
  2. RUNNABLE:可运行状态。这个状态下的线程可能处于执行阶段,但是也有可能在等待来自操作系统的其他资源,例如等待CPU为其分配时间片。ready和running在这两种状态在Java中统称为RUNNABLE,分开写是为了更好的理解
  3. BLOCKED:阻塞状态。表示线程阻塞于锁
  4. WAITING:等待状态。进入等待状态的线程需要其他线程做出一些特定动作,例如通知或中断
  5. TIMED_WAITING:超时等待状态。该状态不同于WAITING状态,它可以在指定时间内自动返回。
  6. TERMINATED:终止状态。表示当前线程已经执行结束

t.start()之后并不代表线程已经启动,此时它只是在可运行池中,随时等待被CPU调度,一旦获取到CPU时间片才真正的处于可运行状态。

线程的优先级:

1、线程有1-10,10个优先级选择,最小优先级是1(MIN_PRIORITY),默认优先级5(NORM_PRIORITY),

最大优先级是10(MAX_PRIORITY)

代码语言:javascript复制
/**
     * 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;

2、如果没有指定线程的优先级,默认是普通优先级,也就是NORM_PRIORITY

3、通过t.setPriority(int newPriority)为线程设定优先级

注意:不要试图通过设定优先级来指定线程的启动顺序,优先级的设定只是说明这个线程在CPU调度时有更大的概率被选中,而且这种优先级设定有不确定性,在某些操作系统中可能优先级的设定并不起作用。

方法介绍:

  • join():在线程A中调用B的join()方法,线程B将会先于线程A执行,线程A必须等待线程B执行完成才能执行,有点类似于插队。同时join()方法还有两个重载的带有时间的方法,join(long millis)、join(long millis, int nanos),它表示如果线程B没有在指定时间内完成,则返回。相当于给插队线程加了时间限制,超时自动返回。
  • yield():Thread的静态方法。当前线程让出CPU执行权,当前线程会从运行状态变为可运行状态,重新回到可运行线程池,但是他还是会可能被再次选中执行。
  • sleep(long millis):Thread的静态方法,使当前线程t休眠n毫秒,如果当前线程t持有锁,休眠期间不会释放锁,其他线程将无法获得锁,当其他线程调用t.interrupt()唤醒休眠线程。当到了休眠时间当前线程t会自动进入RUNNABLE状态。
  • wait():对象方法,使当前线程进入等待状态,它不会返回,除非我们调用t.notify()或者t.notifyAll()方法唤醒,唤醒之后将和其他线程竞争获得锁。调用wait()方法的前提是当前线程必须持有锁,否则会抛异常,在调用wait()方法之后会释放锁,让其他线程有机会获得锁。

sleep()和wait()的区别:

1、sleep()是Thread的静态方法,wait()是Object的方法。

2、当调用sleep()的线程获得锁时不会自动释放锁,调用wait()会释放锁。

3、sleep()方法需要其他线程调用当前线程的interrupt()方法或者时间过期才能唤醒,wait()方法需要调用t.notify()或者t.notifyAll()方法唤醒。

线程的中断:

如果线程执行完成或者抛出未处理的异常,线程就会终止。

线程中断的方法有stop()、resume()、suspend()和interrupt(),但是前三个已经废弃了,stop()会使线程不正确释放资源,resume()只是为了suspend()而存在,这两个方法会导致死锁。所以最后只剩下interrupt()了。

java线程是协作式的,意思就是当调用当前线程t的interrupt()时,当前线程t并不会立马终止,而是跟当前线程打个招呼,“兄弟,你死期到了,我跟你说一下,你爱死不死”,同时会把t的中断标志设置为true。当线程抛出InterruptedException时,线程中断标志将会清除,调用静态方法Thread.interrupted()可以判断线程是否中断,同时将中断标志将被清除并设置为false。

实例如下:

主函数

代码语言:javascript复制
public static void main(String[] args) throws InterruptedException {
    //创建两个线程
    InterruptThread interruptThread = new InterruptThread();
    interruptThread.setName("thread1");

    InterruptThread2 interruptThread2 = new InterruptThread2();
    interruptThread2.setName("thread2");

    //启动两个线程
    interruptThread.start();
    interruptThread2.start();

    //主线程休眠5秒 使两个线程充分运行
    TimeUnit.SECONDS.sleep(5);

    //中断InterruptThread
    interruptThread.interrupt();

}

静态内部线程1:

代码语言:javascript复制
private static class InterruptThread extends Thread{
    @Override
    public void run() {
        interrupt(); //将中断标志设置为true
        System.err.println("before thread1 interrupt status:" isInterrupted());
        while (true){
            try {
                sleep(6000);
            } catch (InterruptedException e) {
                System.err.println("exception thread1 interrupt status:" isInterrupted());
            }
        }
    }
}

静态内部线程2:

代码语言:javascript复制
private static class InterruptThread2 extends Thread{
    @Override
    public void run() {
        long start = System.currentTimeMillis();
        while (true){
            if (System.currentTimeMillis() - start > 2000){
                interrupt();   //将中断标志设置为true
                System.out.println("before  thread2 interrupt status:" isInterrupted());
                break;
            }
       }
        Thread.interrupted();  //清除中断标志,置为false
        System.out.println("after  thread2 interrupt status:" isInterrupted());
    }
}

1、在InterruptThread 中先把中断标志设置为true,并打印是否中断,在主线程中再次将处于休眠状态中的InterruptThread 再次中断,此时会抛出异常,再次打印是否中断。在这个类中我们要证明的是,抛出InterruptedException 线程中断标志会被清除。

2、在InterruptThread2 中我们让InterruptThread2 正常运行2s,并在循环中将中断标志置为true,正常退出循环时,调用Thread.interrupted(); 清除中断标志,并打印是否中断。在这个类中我们要证明调用当前线程Thread.interrupted();会将中断标志清除。

在这两个类中证明了,java线程中断是协作式的,将中断标志置为true并不会立马就中断线程。

打印结果:

代码语言:javascript复制
before thread1 interrupt status:true
exception thread1 interrupt status:false
before  thread2 interrupt status:true
after  thread2 interrupt status:false
exception thread1 interrupt status:false

四、创建线程

创建线程有三种方法:

  • 继承Thread类
代码语言:javascript复制
public class CreateThread {

    public static void main(String[] args) {
        ExtendThread extendThread = new ExtendThread();
        extendThread.start();
    }

    private static class ExtendThread extends Thread{
        @Override
        public void run() {
            System.out.println("class extends thread start");
        }
    }

}
  • 实现Runable接口
代码语言:javascript复制
public class CreateThread {
    public static void main(String[] args) {

        Thread thread = new Thread(new ImplRunnable());
        thread.start();
    }

    private static class ImplRunnable implements Runnable{

        @Override
        public void run() {
            System.out.println("class implements Runnable start");
        }
    }
}
  • 实现Callable接口

Callable是可以获取到返回值, 但是只能通过线程池来调用,它返回的是一个Future对象f,通过f.get()就能获取到结果。

代码语言:javascript复制
public class CreateThread {
    private static ExecutorService executorService = Executors.newFixedThreadPool(4);

    public static void main(String[] args) {
      
        Future<String> submit = executorService.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "success";
            }
        });

        try {
            String s = submit.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

能力一般,水平有限,如有错误,请多指出。

0 人点赞