import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadTest { public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor( 2, //线程池核心线程数 即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut 10, //线程池最大数 超过最大则不创建线程执行拒绝策略 60L, //空闲线程存活时间 TimeUnit.SECONDS, //时间单位 new LinkedBlockingQueue<>(1), //线程池所使用的缓冲队列 如果队列已满且小于线程池最大数则创建临时线程处理任务 Executors.defaultThreadFactory(),//线程池创建线程使用的工厂 new ThreadPoolExecutor.CallerRunsPolicy() //线程池对拒绝任务的处理策略 ); // 使用execute()的话,事先定义一个存放返回结果的集合,开辟线程时,将集合的元素作为参数代入自定义的Runnable接口的实现类中,多线程执行完毕后遍历集合即可获得运算结果。 // // 使用submit()的话,可以在submit()中执行一个Callable接口的实现类,submit()方法可以返回一个Future<> future,然后使用future.get()方法获得返回结果, // 由于future.get()会使调用线程池的线程阻塞,等待返回结果,所以可以先将Future<> future放入一个集合,等多线程执行完毕后再遍历集合获得运算结果。 for (int i = 0; i < 20; i++) { // Runnable run = () -> { // try { // Thread.sleep(1000); // System.out.println("线程:"+ Thread.currentThread().getName() + "正在执行"); // } catch (InterruptedException e) { // e.printStackTrace(); // } // }; // pool.execute(run); // or pool.execute(()->{ System.out.println("线程:"+ Thread.currentThread().getName() + "正在执行"); }); // or // pool.execute(new MyTask(i)); } //shutdownNow 不等待 pool.shutdown();//关闭线程池 等待线程执行完毕 } // 1. Running 的线程 小于 corePoolSize ,直接创建新的线程在Pool执行 // 2. Running 的线程 等于corePoolSize ,则任务加入工作队列 // 3.Running 的线程 等于corePoolSize,工作队列已满,则加入 大于corePoolSize 小于 maxPoolSize 线程 // 4. 全部满,执行拒绝策略 // 线程池创建线程使用的工厂策略 // CallerRunsPolicy // 该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任。 // AbortPolicy // 该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。 // DiscardPolicy // 该策略下,直接丢弃任务,什么都不做。 // DiscardOldestPolicy // 该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列 // 默认情况下,ThreadPoolExecutor构造器传入的ThreadFactory 参数是Executors类中的defaultThreadFactory(),相当于一个线程工厂,帮我们创建了线程池中所需的线程。 // 自定义ThreadFactory /** ThreadPoolExecutor pool = new ThreadPoolExecutor( 2, //线程池核心线程数 即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut 10, //线程池最大数 超过最大则不创建线程执行拒绝策略 60L, //空闲线程存活时间 TimeUnit.SECONDS, //时间单位 new LinkedBlockingQueue<>(1), //线程池所使用的缓冲队列 如果队列已满且小于线程池最大数则创建临时线程处理任务 new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); System.out.println("我是线程" + r); return t; } },//线程池创建线程使用的工厂 new ThreadPoolExecutor.CallerRunsPolicy() //线程池对拒绝任务的处理策略 ); **/ //-----------------------------有界-------------------------------------- // 线程池所使用的缓冲队列 // ①ArrayBlockingQueue // 基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。 // ②LinkedBlockingQuene // 基于链表的无界阻塞队列(如不指定大小其实最大容量为Interger.MAX(无界)),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。 // ③SynchronousQuene // 一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。 //-----------------------------无界-------------------------------------- // ④PriorityBlockingQueue // 具有优先级的无界阻塞队列,优先级通过参数Comparator实现。 // ⑤ConcurrentLinkedQueue // 无锁队列,底层使用CAS操作,通常具有较高吞吐量,但是具有读性能的不确定性,弱一致性——不存在如ArrayList等集合类的并发修改异常,通俗的说就是遍历时修改不会抛异常 // ⑥DelayedQueue // 延时队列,使用场景 缓存:清掉缓存中超时的缓存数据 任务超时处理补充:内部实现其实是采用带时间的优先队列,可重入锁,优化阻塞通知的线程元素leader // ⑦LinkedTransferQueue // 简单的说也是进行线程间数据交换的利器,在SynchronousQueue 中就有所体现,并且并发大神 Doug Lea 对其进行了极致的优化,使用15个对象填充,加上本身4字节,总共64字节就可以避免缓存行中的伪共享问题, // 其实现细节较为复杂,可以说一下大致过程: // 比如消费者线程从一个队列中取元素,发现队列为空,他就生成一个空元素放入队列 , 所谓空元素就是数据项字段为空。然后消费者线程在这个字段上旅转等待。这叫保留。 // 直到一个生产者线程意欲向队例中放入一个元素,这里他发现最前面的元素的数据项字段为 NULL,他就直接把自已数据填充到这个元素中,即完成了元素的传送。大体是这个意思,这种方式优美了完成了线程之间的高效协作。 // 1)用 Runnable 还是 Thread ? // 我们都知道可以通过继承 Thread 类或者调用 Runnable 接口来实现线程,问题是,创建线程哪种方式更好呢?什么情况下使用它?这个问题很容易回答, // 如果你知道Java不支持类的多重继承,但允许你调用多个接口。所以如果你要继承其他类,当然是调用Runnable接口更好了。 // 2)Runnable 和 Callable 有什么不同? // Runnable 和 Callable 都代表那些要在不同的线程中执行的任务。Runnable 从 JDK1.0 开始就有了,Callable 是在 JDK1.5 增加的。 // 它们的主要区别是 Callable 的 call() 方法可以返回值和抛出异常,而 Runnable 的 run() 方法没有这些功能。Callable 可以返回装载有计算结果的 Future 对象。 /** Java 常用的线程池有7种,他们分别是: (1)newCachedThreadPool :创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 (2)newFixedThreadPool:创建一个固定数目的、可重用的线程池。 (3)newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。 (4)newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 (5)newSingleThreadScheduledExcutor:创建一个单例线程池,定期或延时执行任务。 (6)newWorkStealingPool:创建持有足够线程的线程池来支持给定的并行级别,并通过使用多个队列,减少竞争,它需要穿一个并行级别的参数,如果不传,则被设定为默认的CPU数量。 (7) ForkJoinPool:支持大任务分解成小任务的线程池,这是Java8新增线程池,通常配合ForkJoinTask接口的子类RecursiveAction或RecursiveTask使用。 wait()、notify()、notifyAll() 这三个方法是 java.lang.Object 的 final native 方法,任何继承 java.lang.Object 的类都有这三个方法。它们是Java语言提供的实现线程间阻塞和控制进程内调度的底层机制,平时我们会很少用到的。 wait(): 导致线程进入等待状态,直到它被其他线程通过notify()或者notifyAll唤醒,该方法只能在同步方法中调用。 notify(): 随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态,该方法只能在同步方法或同步块内部调用。 notifyAll(): 解除所有那些在该对象上调用wait方法的线程的阻塞状态,同样该方法只能在同步方法或同步块内部调用。 调用这三个方法中任意一个,当前线程必须是锁的持有者,如果不是会抛出一个 IllegalMonitorStateException 异常。 wait() 与 Thread.sleep(long time) 的区别 sleep():在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),该线程不丢失任何监视器的所属权,sleep() 是 Thread 类专属的静态方法,针对一个特定的线程。 wait() 方法使实体所处线程暂停执行,从而使对象进入等待状态,直到被 notify() 方法通知或者 wait() 的等待的时间到。sleep() 方法使持有的线程暂停运行,从而使线程进入休眠状态,直到用 interrupt 方法来打断他的休眠或者 sleep 的休眠的时间到。 wait() 方法进入等待状态时会释放同步锁,而 sleep() 方法不会释放同步锁。所以,当一个线程无限 sleep 时又没有任何人去 interrupt 它的时候,程序就产生大麻烦了,notify() 是用来通知线程,但在 notify() 之前线程是需要获得 lock 的。另个意思就是必须写在 synchronized(lockobj) {...} 之中。wait() 也是这个样子,一个线程需要释放某个 lock,也是在其获得 lock 情况下才能够释放,所以 wait() 也需要放在 synchronized(lockobj) {...} 之中。 volatile 关键字 volatile 是一个特殊的修饰符,只有成员变量才能使用它。在Java并发程序缺少同步类的情况下,多线程对成员变量的操作对其它线程是透明的。volatile 变量可以保证下一个读取操作会在前一个写操作之后发生。线程都会直接从内存中读取该变量并且不缓存它。这就确保了线程读取到的变量是同内存中是一致的。 ThreadLocal 变量 ThreadLocal 是Java里一种特殊的变量。每个线程都有一个 ThreadLocal 就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了。如果为每个线程提供一个自己独有的变量拷贝,将大大提高效率。首先,通过复用减少了代价高昂的对象的创建个数。其次,你在没有使用高代价的同步或者不变性的情况下获得了线程安全。 join() 方法 join() 方法定义在 Thread 类中,所以调用者必须是一个线程,join() 方法主要是让调用该方法的 Thread 完成 run() 方法里面的东西后,再执行 join() 方法后面的代码,看下下面的"意思"代码: Thread t1 = new Thread(计数线程一); Thread t2 = new Thread(计数线程二); t1.start(); t1.join(); // 等待计数线程一执行完成,再执行计数线程二 t2.start(); 启动 t1 后,调用了 join() 方法,直到 t1 的计数任务结束,才轮到 t2 启动,然后 t2 才开始计数任务,两个线程是按着严格的顺序来执行的。如果 t2 的执行需要依赖于 t1 中的完整数据的时候,这种方法就可以很好的确保两个线程的同步性。 Thread.yield() 方法 Thread.sleep(long time):线程暂时终止执行(睡眠)一定的时间。 Thread.yield():线程放弃运行,将CPU的控制权让出。 这两个方法都会将当前运行线程的CPU控制权让出来,但 sleep() 方法在指定的睡眠时间内一定不会再得到运行机会,直到它的睡眠时间完成;而 yield() 方法让出控制权后,还有可能马上被系统的调度机制选中来运行,比如,执行yield()方法的线程优先级高于其他的线程,那么这个线程即使执行了 yield() 方法也可能不能起到让出CPU控制权的效果,因为它让出控制权后,进入排队队列,调度机制将从等待运行的线程队列中选出一个等级最高的线程来运行,那么它又(很可能)被选中来运行。 **/ }
public class MyTask implements Runnable{ private int id; public MyTask(int id) { this.id = id; } @Override public void run() { try { Thread.sleep(1000); System.out.println("线程:"+ Thread.currentThread().getName() + "正在执行"+id); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String toString() { return "MyTask{" + "id=" + id + '}'; } }