并发基础总结

本文总结了Java并发基础,包括Runnable和Thread的区别,以及并行与并发的概念。深入探讨了线程池的原理,介绍了四种常见的线程池类型:CachedThreadPool、FixedThreadPool、SingleThreadExecutor,并对比了线程与线程池的优劣。此外,还讨论了线程的优先级、让步和后台线程的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

并发基础总结

Runnable和Thread要点:

  • 当从Runnable导出一个类时,他必须具有run方法,但是这个方法并无特殊之处-----他不会产生任何内在的线程能力.要实现线程行为,你必须现实的将一个任务附着到线程上.
  • 将Runnable对象转变为工作任务的传统方式就是把它提交给一个Thread构造器.
  • 当main创建Thread对象时,他并没有捕获任何的这些对象的引用,在使用普通对象时,这对于垃圾回收来说是一场公平的游戏,但是在使用Thread时,情况就不同了,每个Thread都注册了它自己,因此确实有一个对它的引用,而且在它的任务退出其run()并死亡之前,垃圾回收器无法清除它.

并行和并发

  • 并发:从宏观方面来讲.并发就是同时进行多种事件,实际上,这几种事件,并不是同时进行的,而是交替进行的,而由于CPU的运算速度非常的快,会造成我们的一种错觉,就是在同一时间内进行了多种事情.
  • 并行:则是真正意义上的同时进行多种事情,这种只可以在多核CPU的基础上完成.

线程池详解

线程池概念

​ 在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程.而创建并销毁线程的过程势必会消耗内存.而在Java中内存又是极其宝贵的,所以我们就提出了线程池的概念.,所以从应用场景看,线程池的好处就是可以方便的管理线程,也可以减少内存的消耗.

​ java.util.concurrent包中的执行器(Executor)将为你管理Thread对象.Executor在客户端和任务执行之间提供了一个间接层.Executor允许你管理异步任务的执行,而无需显式的管理线程的生命周期.

线程池底层原理

​ 线程池的接口是Executor这个接口,具体实现为ThreadPoolExecutor类,对线程池的配置,就是对ThreadPoolExecutor构造函数的参数的配置.

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
  }

这就是ThreadPoolExecutor的构造器

  • int corePoolSize:该线程池中核心线程数最大值,线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程,核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干。

  • int maximumPoolSize:该线程池中线程总数最大值。线程总数=核心线程数+非核心线程数。

  • long keepAliveTime:非核心线程闲置超时时常,如果一个非核心线程不干活的时间超过这个参数所规定的,就会被销毁掉。

  • TimeUnit unit:TimeUnit是一个枚举类型,其包括:NANOSECONDS; MICROSECONDS; MILLISECONDS; MINIUTES; HUOURS;DAYS

  • BlockingQueue workQueue :该线程池汇中的任务队列,维护着等待执行的Runnable对象,当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待,如果队列满了,则新建非核心线程执行任务.

    ​ 常用的workQueue类型:

    1. SynchronousQueue:这是一种没有内部容量的阻塞队列,因此每个put()都必须等待一个take(),反之亦然.这个队列收到任务时,会直接提交给线程处理,而不保留它.就好比你在把一个对象交给某人,但是没有任何桌子放置这个对象,因此只有在这个伸出手,准备好接对象时,你才能工作.

    2. LinkedBlockingQueue:这个队列没有最大值,即所有超过核心线程数的任务都将被添加到队列中.

    3. ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建核心线程执行任务,如果达到了,则入队等待,如果队列已满,则新建非核心线程执行任务,如果总线程数达到了maximumPoolSize,并且队列也满了,则发生错误.

    4. DelayQueue

四种常见的线程池
CachedThreadPool()

​ 可缓存线程池:

​ 1.线程数无限制(没有核心线程,全部是非核心线程)

​ 2.有空闲线程则复用空闲线程,若无空闲线程在新建线程

​ 3.一定程度减少频繁创建/销毁线程,减少系统靠小

​ 创建方法:

ExecutorService service = Executors.newCachedThreadPool();

​ 源码:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}
FixedThreadPool()

​ 定续航线程池:

​ 1.有核心线程,核心线程数就是线程的最大数量(没有非核心线程)

​ 2.可控制线程最大并发数

​ 3.超出的线程会在队列中等待

​ 创建方法:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads);

​ 源码:

 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
 }
SingleThreadExecutor()

​ 单线程化的线程池:

​ 1.有且仅有一个工作线程执行任务

​ 2.所有任务按照指定顺序执行,即遵循队列的入队出队规则。

​ 创建方法:

ExecutorService singleThreadPool = Executors.newSingleThreadPool();

​ 源码:

 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

线程和线程池的比较

线程:
  • 每次都新建,性能较差
  • 线程缺乏统一管理,可能无限制的创建线程,互相竞争
  • 可控性太差,比如定时定期执行
线程池:
  • 降低资源消耗,不是每次都需要新建
  • 统一管理,可有效控制最大并发量,提高资源利用率,避免过多资源竞争
  • 可控性很好,可以定期定时执行、线程中断机制等。

从任务中产生返回值

Sleep()和Join()

Sleep()和Join()都有阻塞线程的作用.但是他们的用途却不同. 
  • sleep()用于让当前线程沉睡一段指定时间
  • 而join()用在,很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束.但是这时如果主线程想等待子线程执行完成后再结束,比如子线程处理一个数据,主线程要取得这个数据的值,就要用到join()方法了.方法join()的作用是等待线程对象销毁.
  • 同样的和sleep一样,join()在遇到interrupt()时也会发生异常.

join()方法示例:

class Sleeper extends Thread {
  private int duration;
  public Sleeper(String name, int sleepTime) {
    super(name);
    duration = sleepTime;
    start();
  }
  public void run() {
    try {
      sleep(duration);
    } catch(InterruptedException e) {
      print(getName() + " was interrupted. " +
        "isInterrupted(): " + isInterrupted());
      return;
    }
    print(getName() + " has awakened");
  }
}

class Joiner extends Thread {
  private Sleeper sleeper;
  public Joiner(String name, Sleeper sleeper) {
    super(name);
    this.sleeper = sleeper;
    start();
  }
  public void run() {
   try {
      sleeper.join();
    } catch(InterruptedException e) {
      print("Interrupted");
    }
    print(getName() + " join completed");
  }
}

public class Joining {
  public static void main(String[] args) {
    Sleeper
      sleepy = new Sleeper("Sleepy", 1500),
      grumpy = new Sleeper("Grumpy", 1500);
    Joiner
      dopey = new Joiner("Dopey", sleepy),
      doc = new Joiner("Doc", grumpy);
    grumpy.interrupt();
  }
} /* Output:
Grumpy was interrupted. isInterrupted(): false
Doc join completed
Sleepy has awakened
Dopey join completed
*///:~

可以看到无论是否Sleeper是否中断或是正常结束,Joiner都必须等待Sleeper.isAlive()返回为假是才能执行.因为他们的run方法首行就调用了sleeper.join().
可以看到上面的is.Interrupted()是返回false的,这有点反常,这是因为:在Thread对象上调用isInterrupted()方法可以检查任何线程的中断状态,但是:线程一旦被中断,isInterrupted()方法便会返回true,而一旦sleep()方法抛出异常,它将恢复中断标志,isInterrupted()方法在此时就会返回false。

方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join()具有释放锁的特点.
方法join(long)的示例代码:

public class ThreadB extends Thread {
    @Override
    public void run() {
        try {
            System.out.println("b run begin timer=" + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("b run end timer=" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public void bService() {
        System.out.println("打印了bService timer=" + System.currentTimeMillis());
    }

}


public class ThreadC extends Thread {

    private ThreadB b;

    public ThreadC(ThreadB b) {
        super();
        this.b = b;
    }

    @Override
    public void run() {
        b.bService();
    }
}

public class Run15_join_sleep_2 {
    public static void main(String[] args) {
        try {
            ThreadB b = new ThreadB();
            ThreadA2 a = new ThreadA2(b);
            a.start();
            Thread.sleep(1000);
            ThreadC c = new ThreadC(b);
            c.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class ThreadA2 extends Thread {
        private ThreadB b;

        public ThreadA2(ThreadB b) {
            super();
            this.b = b;
        }

        @Override
        public void run() {
            try {
                synchronized (b) {
                    b.start();
                    b.join();
                    System.out.println("a run before for " + System.currentTimeMillis());
                    for (int i = 0; i < 100000000; i++) {
                        Math.random();
                    }
                    System.out.println("a run end " + System.currentTimeMillis());
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/*
输出:
b run begin timer=1460637677744
打印了bService timer=1460637678746
b run end timer=1460637682745
a run before for 1460637682745
a run end 1460637684923
 */

可以看到在a线程中调用b.join()后,会释放掉a线程获得的b对象的锁,所以c线程拿到了这个锁,执行了bService()方法.
而sleep(long)是不会释放锁的,下面是sleep(long)的示例:

public class ThreadA extends Thread {
    private ThreadB b;

    public ThreadA(ThreadB b) {
        super();
        this.b = b;
    }

    @Override
    public void run() {
        try {
            synchronized (b) {
                b.start();
                Thread.sleep(6000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Run15_join_sleep {
    public static void main(String[] args) {
        try {
            ThreadB b = new ThreadB();
            ThreadA a = new ThreadA(b);
            a.start();
            Thread.sleep(1000);
            ThreadC c = new ThreadC(b);
            c.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/*
输出:
b run begin timer=1460637031625
b run end timer=1460637036626
打印了bService timer=1460637037626

由输出结果可以看到,c线程在a线程执行完毕后,才获得了b对象的锁.

线程的优先级

  • 记住当线程的优先级没有指定时,所有线程都携带普通优先级。
  • 优先级可以用从1到10的范围指定。10表示最高优先级,1表示最低优先级,5是普通优先级。
  • 记住优先级最高的线程在执行时被给予优先。但是不能保证线程在启动时就进入运行状态。
  • t.setPriority()用来设定线程的优先级。
  • 记住在线程开始方法被调用之前,线程的优先级应该被设定。
  • 你可以使用常量,如MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY来设定优先级。

线程的让步Thread.yeild()

从原理上讲其实Thread.yeild方法其实只是给线程调度机制一个暗示:我的任务处理的差不多了,可以让给相同优先级的线程CPU资源了;不过确实只是一个暗示,没有任何机制保证它的建议将被采纳.

public class LiftOff implements Runnable {

    protected int countDown = 5;

    private static int taskCount = 0;
    private final int id = taskCount++;

    public LiftOff() { }

    public LiftOff(int countDown) {
        this.countDown = countDown;
    }

    public String status() {
        // System.out.println(Thread.currentThread().getName());
        return Thread.currentThread().getName() + "#" + id + "(" + (countDown > 0 ? countDown + ")" : "Liftoff!" + ")");
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        /*
         * --countDown:先减去,再赋值
         * countDown--先赋值,再减去
         */
        while (countDown-- > 0) {
            System.out.println(status());
            Thread.yield();// 让步【表示我的任务处理的差不多了,可以让步给其他线程资源了】;
            //CPU资源给其他线程使用;t.join当前线程挂起,直到t线程调用结束后切回
       }
 }

public class ExecuteMethodPool {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
		for (int i = 0; i < 3; i++) {
			executorService.execute(new LiftOff());
        }
        executorService.shutdown();
     }
}

pool-1-thread-2#1(4)
pool-1-thread-1#0(4)
pool-1-thread-3#2(4)
pool-1-thread-2#1(3)
pool-1-thread-3#2(3)
pool-1-thread-3#2(2)
pool-1-thread-1#0(3)
pool-1-thread-2#1(2)
pool-1-thread-3#2(1)
pool-1-thread-1#0(2)
pool-1-thread-2#1(1)
pool-1-thread-3#2(Liftoff!)
pool-1-thread-1#0(1)
pool-1-thread-2#1(Liftoff!)
pool-1-thread-1#0(Liftoff!)

以上标注的两个一样的线程先后执行 并没有遵守yeild给线程调度的建议.

后台线程

所谓后台线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中或不可缺的部分.因此当所有的非后台线程结束时,程序也就终止了,同时会杀死进程中所有的后台线程.反过来说只要有任何非后台线程还在运行,程序就不会终止.比如,执行main()的就是一个非后台线程.

daemon线程的特点:

  • A.守护线程创建的过程中需要先调用setDaemon方法进行设置,然后再启动线程.否则会报出IllegalThreadStateException异常.(问题:为什么不能动态更改线程为daemon线程?)
  • B.由于daemon线程的终止条件是当前是否存在用户线程,所以我们不能指派daemon线程来进行一些业务操作,而只能服务用户线程.
  • C.daemon线程创建的子线程任然是daemon线程.
//对应于A的特点
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Thread2());
        thread1.start();
        thread1.setDaemon(true);
        Thread.sleep(10);
        System.out.println("用户线程退出");
    }
}
class Thread2 implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            System.out.println("1+1="+(1+1));
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
运行结果:
Exception in thread "main" java.lang.IllegalThreadStateException
    at java.lang.Thread.setDaemon(Thread.java:1352)
    at ThreadTest.main(ThreadTest.java:5)
1+1=2

通过源码可以发现,如果setDaemon()的线程是alive的,我们的程序就会抛出异常

public final void setDaemon(boolean on) {
        checkAccess();
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }
//对应于B的特点
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Thread2());
        thread1.setDaemon(true);
        thread1.start();
        Thread.sleep(10);
        System.out.println("用户线程退出");
    }
}
class Thread2 implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            System.out.println("1+1="+(1+1));
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

运行结果: 用户线程退出

上面并没有获取到任何信息,是因为为一个非后台线程也就是用户线程main已经早早结束,所以JVM直接杀死了非后台线程.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值