JUC编程

一、什么是JUC

什么是JUC:java.util.concurrent包名的简写,是关于并发编程的API。

与JUC相关的有三个包:java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks。

也查看了一些前辈的博客,这里使用了一下别人画的知识体系图,后续根据以下方面进行记录总结。

 二、线程基础知识

并发:多个线程操作同一个资源,交替执行的过程!

并行:多个线程同时执行!只有在多核CPU下才能完成!

线程六种状态:新建(new) ,运行(RUNNABLE),阻塞(BLOCKED),等待(WAITING),延迟等待(TIMED_WAITING),终止(TERMINATED)

wait/Sleep 区别

1.类不同

s.wait(1000);//object 类
​
TimeUnit.SECONDS.sleep(1);//Thread 类

2.释放资源不同

sleep:抱着锁睡得,不会释放锁,会自己醒!wait 会释放锁,不会自己醒,需要唤醒!

3.使用范围不同

wait 和 notify 是一组,一般在线程通信的时候使用!notify是为了唤醒wait资源的

sleep 就是一个单独的方法,在那里都可以用

 4.关于异常

sleep 需要捕获异常!

两者都可以暂停线程的执行。

对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。

Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。

sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。

在调用sleep()方法的过程中,线程不会释放对象锁。

而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。线程不会自动苏醒

三、线程锁

1、synchronized 传统方式

java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。

java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,知道线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。

java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的(后面会在8锁现象里具体说明)

先看传统的synchronized写法:

/*
 *线程操作资源类,资源类是单独的
 */
public class Demo1 {
​
    public static void main(String[] args) {
          Ticket ticket = new Ticket();
​
        //线程操控资源类
        new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i <30 ; i++) {
                    ticket.saleTicket();
                }
            }
        },"A").start();
​
        new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i <30 ; i++) {
                    ticket.saleTicket();
                }
            }
        },"B").start();
​
​
        new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i <30 ; i++) {
                    ticket.saleTicket();
                }
            }
        },"C").start();
​
    }
​
}
 //资源类 假设我们在卖票
 class Ticket{
​
    private int num = 100;
​
    public synchronized void saleTicket(){
        if (num>0){
            System.out.println(Thread.currentThread().getName()
                    +"卖出第"+(num--)+"票,还剩"+num);
        }
    }
​
​
}

2、Lock锁

锁是用于通过多个线程控制对共享资源的访问的工具,通常锁提供对共享资源的独占访问,一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。 但是,一些锁可能允许并发访问共享资源,如ReadWriteLock的读写锁。在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的。JDK1.5之后并发包中新增了Lock接口以及相关实现类来实现锁功能。synchronized方法和语句的范围机制使得使用监视器锁更容易编程,并且有助于避免涉及锁的许多常见编程错误,但是有时您需要以更灵活的方式处理锁。

如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况: 1.获取锁的线程执行完了该代码块,然后线程释放对锁的占有 2.线程执行发生异常,此时JVM会让线程自动释放锁

那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能地等待,这多么影响程序执行效率。因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到

public class Demo2 {
​
    public static void main(String[] args) {
        Ticket2 ticket = new Ticket2();
​
        new Thread(()->{
            for (int i = 0; i < 30; i++)ticket.saleTicket(); },"A").start();
​
        new Thread(()-> {
            for (int i = 0; i <30 ; i++) ticket.saleTicket(); },"B").start();
​
        new Thread(()->{
            for (int i = 0; i < 30; i++) ticket.saleTicket(); },"C").start();
    }
​
}
​
//资源类 假设我们在卖票
class Ticket2{
    // 使用Lock,它是一个对象
    // ReentrantLock 可重入锁:回家:大门 (卧室门,厕所门...)
    // ReentrantLock 默认是非公平锁!
    // 非公平锁: 不公平 (插队,后面的线程可以插队)
    // 公平锁: 公平(只能排队,后面的线程无法插队)
    // ReentrantLock(轻量级锁)也可以叫对象锁,可重入锁,互斥锁
    private Lock lock = new ReentrantLock();
​
   private int num = 100;
​
   public void saleTicket(){
       lock.lock();
​
       try {
           if (num>0){
               System.out.println(Thread.currentThread().getName()
                       +"卖出第"+(num--)+"票,还剩"+num);
           }
       } catch (Exception e) {
           e.printStackTrace();
       } finally {
           lock.unlock();
       }
   }
​
​}

注意lock.lock()不能放在try里面

说明:因为在 try-finally 外加锁的话,如果因为发生异常导致加锁失败,try-finally 块中的代码不会执行。相反,如果在 try{ } 代码块中加锁失败,finally 中的代码无论如何都会执行,但是由于当前线程加锁失败并没有持有 lock 对象锁,所以程序会抛出异常。

3、Synchronized 和 Lock 区别

1、Synchronized 是一个关键字、Lock 是一个对象

2、Synchronized 无法尝试获取锁,Lock 可以尝试获取锁,判断;

3、Synchronized 会自动释放锁(a线程执行完毕,b如果异常了,也会释放锁),lock锁是手动释放锁!如果你不释放就会死锁。

4、Synchronized (线程A(获得锁,如果阻塞),线程B(等待,一直等待);)lock,可以尝试获取锁,失败了之后就放弃

5、Synchronized 一定是非公平的,但是 Lock 锁可以是公平的,通过参数设置;

6、代码量特别大的时候,我们一般使用Lock实现精准控制,Synchronized 适合代码量比较小的同步问题;

4、关于锁的问题,彻底理解锁

考虑问题:有几把锁,锁的是谁?

1、锁的是具体的对象 new
2、锁的是唯一的模板 static

synchronized锁的对象是方法的调用者!
staitc synchronized锁的对象是唯一的class模板

1、第一种问题

/*
 * 1、两个同步方法情况下,两个线程先打印 发短信?打电话?
 * 2、线程A睡眠2s,两个线程先打印 发短信?打电话?
 * */
public class Test1 {
    public static void main(String[] args) {
        //一个对象
        phone1 phone1 = new phone1();

        //A线程
        new Thread(() -> {
            phone1.sendSms();
        }, "A").start();

        //线程睡眠1s
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //B线程
        new Thread(() -> {
            phone1.call();
        }, "B").start();
    }
}

class phone1 {

    public synchronized void sendSms() {
        //调用该方法的线程先睡眠2s
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--->发短信!");
    }

    public synchronized void call() {
        System.out.println(Thread.currentThread().getName() + "--->打电话!");
    }
}

永远都是先执行发短信,再执行打电话!
该问题中,有一把锁,锁的是phone1具体的对象
A线程先拿到锁(操作phone1对象),即便睡眠了2s,在没有释放锁之前,B线程无法操作phone1对象(在同步的情况下)

2、第二种问题

/*
 * 一个同步,一个普通方法情况下,先执行 发短信?打电话?
 * */
public class Test2 {
    public static void main(String[] args) {
        //一个对象
        phone2 phone2 = new phone2();

        //A线程
        new Thread(() -> {
            phone2.sendSms();
        }, "A").start();

        //线程睡眠1s
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //B线程
        new Thread(() -> {
            phone2.call();
        }, "B").start();
    }
}

class phone2 {

    //同步的
    public synchronized void sendSms() {
        //调用该方法的线程先睡眠2s
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--->发短信!");
    }

    //非同步的
    public void call() {
        System.out.println(Thread.currentThread().getName() + "--->打电话!");
    }
}

永远都是先打电话,再发短信!
该问题中,有一把锁,锁的是phone2对象
虽然A线程先拿到锁,但是B线程要执行的方法是非同步的,不用判断锁的存在,可直接执行!

3、第三种问题

/*
 * 两个对象,两个同步方法;先执行发短信?打电话?
 * */
public class Test3 {
    public static void main(String[] args) {
        //两个对象
        phone3 phone31 = new phone3();
        phone3 phone32 = new phone3();

        //A线程
        new Thread(() -> {
            phone31.sendSms();
        }, "A").start();

        //线程睡眠1s
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //B线程
        new Thread(() -> {
            phone32.call();
        }, "B").start();
    }
}

class phone3 {

    public synchronized void sendSms() {
        //调用该方法的线程先睡眠2s
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--->发短信!");
    }

    public synchronized void call() {
        System.out.println(Thread.currentThread().getName() + "--->打电话!");
    }
}

永远先执行打电话,再执行发短信!
该问题中,有两个锁,分别锁的是phone31,phone32对象
A线程拿到锁(phone31的)后睡眠,但是B线程拿的是(phone32的锁),操作的不是phone31的对象;所以B线程在A线程睡眠时,先执行!

4、第四种问题

/*
 * 两个静态同步方法,只有一个对象;先执行 发短信?打电话?
 * */
public class Test4 {
    public static void main(String[] args) {
        //一个对象
        phone4 phone4 = new phone4();

        //A线程
        new Thread(() -> {
            phone4.sendSms();
        }, "A").start();

        //线程睡眠1s
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //B线程
        new Thread(() -> {
            phone4.call();
        }, "B").start();
    }
}

class phone4 {

    //静态同步方法
    public static synchronized void sendSms() {
        //调用该方法的线程先睡眠2s
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--->发短信!");
    }

    //静态同步方法
    public static synchronized void call() {
        System.out.println(Thread.currentThread().getName() + "--->打电话!");
    }
}

永远先执行发短信,再执行打电话!
该问题中,有一把锁,锁的是class模板
A线程先拿到模板的锁,即便睡眠,在不释放锁之前,B线程无法操作这个class模板(同步的前提下)

5、第五种问题

/*
* 两个对象,两个静态同步方法;先执行发短信?打电话?
* */
public class Test5 {
    public static void main(String[] args) {
        //两个对象
        phone5 phone51 = new phone5();
        phone5 phone52 = new phone5();

        //A线程
        new Thread(() -> {
            phone51.sendSms();
        }, "A").start();

        //线程睡眠1s
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //B线程
        new Thread(() -> {
            phone52.call();
        }, "B").start();
    }
}

class phone5 {

    //静态同步方法
    public static synchronized void sendSms() {
        //调用该方法的线程先睡眠2s
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--->发短信!");
    }

    //静态同步方法
    public static synchronized void call() {
        System.out.println(Thread.currentThread().getName() + "--->打电话!");
    }
}

永远先执行发短信,再执行打电话!
该问题中,还是一把锁,锁的是唯一的class模板
sendSms和call方法,他们都是静态的,存在于class模板中;而不是具体的对象中
即便A线程使用了phone51对象,但是操作的方法在class模板中,是class锁,在不释放锁的前提下,B线程无法操作class模板(同步的前提下)

6、第六种问题

/*
 * 1个静态的同步方法,一个普通的同步方法,两个对象;先打印 发短信?打电话?
 * */
public class Test7 {
    public static void main(String[] args) {
        //两个对象
        phone7 phone71 = new phone7();
        phone7 phone72 = new phone7();

        //A线程
        new Thread(() -> {
            phone71.sendSms();
        }, "A").start();

        //线程睡眠1s
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //B线程
        new Thread(() -> {
            phone72.call();
        }, "B").start();
    }
}

class phone7 {

    //静态同步方法
    public static synchronized void sendSms() {
        //调用该方法的线程先睡眠2s
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--->发短信!");
    }

    //普通同步方法
    public synchronized void call() {
        System.out.println(Thread.currentThread().getName() + "--->打电话!");
    }
}

永远先执行打电话,再执行发短信!
该问题中,有两把锁;锁的是唯一的class模板、phone72对象
A线程拿到class模板的锁,睡眠后;B线程拿到phone72对象的锁,执行call方法,它们是两把锁

四、集合类不安全

1、List 不安全

我们通常使用的集合类都是非同步的,除了vector

当多个线程,向同一个集合写入数据时
ConcurrentModificationException 并发修改异常!

public static void main(String[] args) {
  //我们通常使用的List集合都是非同步的 除了vector
  List<String> list=new ArrayList<>();

  /*
  * 解决方案
  *   1、vector集合(效率低)
  *   2、Collections.synchronized(list)  (使用集合工具类,将非同步的集合变为同步的)
  *   3、CopyOnWrite   写入时复制
  *       当多个线程同时向list中写入数据时,存在写入覆盖问题
  *       在写入的时候复制,避免另一个线程将某一线程未写完的数据覆盖
  * */

  for (int i = 1; i < 10; i++) {
      new Thread(()->{
          //多个线程向同一个集合中写入数据
          list.add(UUID.randomUUID().toString().substring(0,5));
          System.out.println(list);
      },String.valueOf(i)).start();
  }
}

2、Set 不安全

与上同理,CopyOnWriteArraySet

HashSet底层是什么?

public HashSet() { 
  map = new HashMap<>(); 
}

private static final Object PRESENT = new Object(); // 不变值!
// add set 本质就是 map key是无法重复的! 
public boolean add(E e) { 
  return map.put(e, PRESENT)==null;
}

3、Map 不安全

回顾Map基本操作

// ConcurrentModificationException 
public static void main(String[] args) { 
  // map 是这样用的吗? 不是 
  // 默认等价于什么? 
  new HashMap<>(16,0.75); 
  //并发下的map
  Map<String, String> map = new ConcurrentHashMap<>();
  for (int i = 1; i <=30; i++) { 
      new Thread(()->{         
         map.put(Thread.currentThread().getName(),
         UUID.randomUUID().toString().substring( 0,5)); 
     
         System.out.println(map); 
   },String.valueOf(i)).start();
}

7、Callable接口

实际上,和Runnable的功能一样,区别

run()–>call()
无返回值–>有返回值(与泛型中定义的一致)
不抛出异常–>可以抛出异常
但是,开启线程的方式,只有new Thread(Runnable接口).start !
所以,我们需要找到一个类,该类与Runnable、Callable都有关系!

FutureTask类:该类是Runnable接口的实现类,可以接收Callable对象

public static void main(String[] args) throws ExecutionException, InterruptedException {
  /*
   * 开启线程,只能用Runnable接口才行
   * FutureTask是Runnable的实现类,并且可以接收一个Callable对象
   * */
  MyThread myThread = new MyThread();
  FutureTask futureTask = new FutureTask(myThread);
  new Thread(futureTask).start();
  String str = (String) futureTask.get();
  System.out.println(str);
}
}


class MyThread implements Callable<String> {
  @Override
  public String call() throws Exception {
    System.out.println("call方法执行了!");
    return "call()";
}

8、常用的辅助类

1、CountDownLatch

减法计数器,当线程数量不减为0时,不往后执行!

/*
* 线程的计数器!
* 当线程数量不减为0时,不向下继续执行!
*
* 注意:匿名内部类要使用的变量,作用域为final
* */
public class CountDownLatchTest {
 public static void main(String[] args) throws InterruptedException {
     //总数是7,必须在线程执行后,让该数-1
     CountDownLatch countDownLatch = new CountDownLatch(7);

     for (int i = 1; i <= 7; i++) {
         //lambada表达式,实际上就是一个匿名内部类,无法直接使用i变量
         //需要final域的变量
         final int temp=i;
         new Thread(()->{
             System.out.println(Thread.currentThread().getName()+"--->Go out");
            
             //告诉CountDownLatch,线程数量-1
             countDownLatch.countDown();      
         },String.valueOf(i)).start();
     }

     //进行等待操作,当线程数量不减为0,永久等待,之后的代码不执行!
     countDownLatch.await();
     System.out.println("线程数量为0了!!!");
 }
}

2、CyclicBarrier

加法计数器,当线程数量达到某个值后,开启一个新线程,执行一段代码

/*
 * 加法计数器
 * 当线程的数量达到某个值时,开启一个新线程,执行一段代码
 * */
public class CyclicBarrierTest {
 public static void main(String[] args) {
   //加法计数器,线程数量达到7时,执行一段新的线程
   CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
       System.out.println("召唤神龙成功!");
   });

   for (int i = 1; i <= 7; i++) {
       new Thread(() -> {
           System.out.println(Thread.currentThread().getName() + "--->个龙珠");

           // 每开启一个线程,cyclicBarrier对象中的值就会自动-1
           // 减为0时,表示开启了这么多个线程,并去执行一段新的线程!
           try {
               cyclicBarrier.await();
           } catch (InterruptedException e) {
               e.printStackTrace();
           } catch (BrokenBarrierException e) {
               e.printStackTrace();
           }
       }, String.valueOf(i)).start();
   }
 }
}

3、Semaphore

信号量机制,为了让多个共享资源,进行互斥的使用!
可以做限流功能,控制最大的线程数!

/*
* 信号量,让多个共享资源,进行互斥的使用
* */
public class SemaphoreTest {
 public static void main(String[] args) {
     //表示有3个信号量
     //每个线程开启时,拿走一个信号量
     //每个线程关闭后,释放一个信号量
     Semaphore semaphore = new Semaphore(3);

     for (int i = 1; i <= 6; i++) {
         new Thread(()->{
             try {
                 //开启一个线程后,拿走一个信号量  -1
                 semaphore.acquire();
                 System.out.println(Thread.currentThread().getName()+"--->线程开启了!");
                 TimeUnit.SECONDS.sleep(2);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }finally {
                 //线程运行结束后,释放一个信号量  +1
                 semaphore.release();
                 System.out.println("--------------------------------------");
                 System.out.println(Thread.currentThread().getName()+"--->线程结束了!");
             }
         },String.valueOf(i)).start();
     }
 }
}

结果:在同一时刻,只有三个线程能进行操作!

9、读写锁

也叫独占锁,共享锁
即,可以有多个线程读,但是只允许一个线程写!

public static void main(String[] args) {
   MyThread myThread = new MyThread();

   //开启写线程
   for (int i = 1; i <= 5; i++) {
       final String temp = String.valueOf(i);
       //注意:匿名内部类,只能使用final作用域的变量
       new Thread(() -> {
           myThread.put(temp, temp);
       }, String.valueOf(i)).start();
   }

   //开启读线程
   for (int i = 1; i <= 5; i++) {
       final String temp=i+"";
       new Thread(()->{
           myThread.get(temp);
       },String.valueOf(i)).start();
   }
}
}

class MyThread {
private volatile Map<String, Object> map = new HashMap<>();

//获得一把读写锁
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();

//写操作,只允许一个线程写
public  void put(String key, Object value) {
   //写操作,使用独占锁
   readWriteLock.writeLock().lock();
   try {
       System.out.println(Thread.currentThread().getName() + "开始写入...");
       map.put(key, value);
       System.out.println(Thread.currentThread().getName() + "写入完成!");
   } catch (Exception e) {
       e.printStackTrace();
   } finally {
       readWriteLock.writeLock().unlock();
   }
}

//读操作,可以有多个线程读
public void get(String key) {
   //读操作,使用共享锁
   readWriteLock.readLock().lock();
   try {
       System.out.println(Thread.currentThread().getName() + "开始读取...");
       Object value = map.get(key);
       System.out.println(Thread.currentThread().getName() + "读取完成!值为:" + value);
   } catch (Exception e) {
       e.printStackTrace();
   } finally {
       readWriteLock.readLock().unlock();
   }
}

10、阻塞队列

 BlockingQueue阻塞队列

 Queue队列

public static void main(String[] args) {
  //1、创建线程池
  //线程池中只有一个线程!
  ExecutorService pool1 = Executors.newSingleThreadExecutor();
  
  //2、使用线程池来开启线程
  for (int i = 1; i < 5; i++) {
      //executor开启线程池中的一个线程,并执行runnable中的run方法
      pool1.execute(()->{
          System.out.println(Thread.currentThread().getName()+" ok ");
      });
  }
  
  //3、程序运行结束,关闭线程池
  pool1.shutdown();
}

2、创建固定大小的线程池

public static void main(String[] args) {
  //1、创建线程池
  //创建一个固定的线程池大小
  ExecutorService pool2 = Executors.newFixedThreadPool(5);
  
  //2、使用线程池来开启线程
  for (int i = 1; i < 5; i++) {
      //executor开启线程池中的一个线程,并执行runnable中的run方法
      pool2.execute(()->{
          System.out.println(Thread.currentThread().getName()+" ok ");
      });
  }
  
  //3、程序运行结束,关闭线程池
  pool2.shutdown();
}

3、创建可伸缩大小的线程池

 什么情况下我们会使用 阻塞队列:多线程并发处理,线程池!

1、BlockingQueue的四组API

add remove 抛出异常

offer poll 有返回值,无异常

put take 无返回值,永久等待

offer poll 超时等待

11、线程池

 Executors工具类,类似于Collections,Arrays;用来创建线程池的!

1、创建单个线程的线程池

public static void main(String[] args) {
  //1、创建线程池
  //线程池中只有一个线程!
  ExecutorService pool1 = Executors.newSingleThreadExecutor();
  
  //2、使用线程池来开启线程
  for (int i = 1; i < 5; i++) {
      //executor开启线程池中的一个线程,并执行runnable中的run方法
      pool1.execute(()->{
          System.out.println(Thread.currentThread().getName()+" ok ");
      });
  }
  
  //3、程序运行结束,关闭线程池
  pool1.shutdown();
}

2、创建固定大小的线程池

public static void main(String[] args) {
  //1、创建线程池
  //创建一个固定的线程池大小
  ExecutorService pool2 = Executors.newFixedThreadPool(5);
  
  //2、使用线程池来开启线程
  for (int i = 1; i < 5; i++) {
      //executor开启线程池中的一个线程,并执行runnable中的run方法
      pool2.execute(()->{
          System.out.println(Thread.currentThread().getName()+" ok ");
      });
  }
  
  //3、程序运行结束,关闭线程池
  pool2.shutdown();
}

3、创建可伸缩大小的线程池

public static void main(String[] args) {
  //1、创建线程池
  //创建一个可伸缩大小的线程池
  ExecutorService pool3 = Executors.newCachedThreadPool();
  
  //2、使用线程池来开启线程
  for (int i = 1; i < 5; i++) {
      //executor开启线程池中的一个线程,并执行runnable中的run方法
      pool3.execute(()->{
          System.out.println(Thread.currentThread().getName()+" ok ");
      });
  }  
  
  //3、程序运行结束,关闭线程池
  pool3.shutdown();
}

注意:线程池用完,程序结束,记得关闭线程池!shutdown();

4、七大参数

我们使用Executors工具类创建线程池时发现
不论创建哪一种线程池,都使用了ThreadPoolExecutor对象来创建线程!

//Executors.newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
 return new FinalizableDelegatedExecutorService
     (new ThreadPoolExecutor
         (1, 1,
          0L, TimeUnit.MILLISECONDS,
         new LinkedBlockingQueue<Runnable>()));
}
//Executors.newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
 return new ThreadPoolExecutor
     (nThreads, nThreads,
    0L, TimeUnit.MILLISECONDS,
     new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
 return new ThreadPoolExecutor
     (0, Integer.MAX_VALUE,                //该值为21亿!
     60L, TimeUnit.SECONDS,
     new SynchronousQueue<Runnable>());
}

ThreadPoolExecutor对象有7大参数!

1、corePoolSize

核心线程池大小;线程池一被创建,池中就有X个核心线程可以直接运行,不会被释放!

2、maximumPoolSize

最大线程池大小;线程池中可以并发执行的全部线程数量;
当要开启一个新线程时,会先进入阻塞队列中;先由核心线程帮你完成功能
当阻塞队列满了,才会再开启一个线程!

3、keepAliveTime

超时了没有人调用,就会释放,只保留核心线程!

4 TimeUnit

超时单位

5、BlockingQueue

阻塞队列(阻塞队列也有四组API)!

除了核心线程外,当你想要再开启一个新的线程时,会先进入阻塞队列!

6、ThreadFactory

线程工厂:创建线程的,默认的即可!

Executors.defaultThreadFactory();

7、RejectedExecutionHandler

拒绝策略(4种拒绝策略)!

当达到了最大线程池数,阻塞队列也满了之后

再要开启线程时,使用拒绝策略!

8、ThreadPoolExecutor手动创建线程池

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
  2,                      //核心线程池大小
  5,                      //最大线程池大小
  2,                      //超时了没有人调用,就会释放
  TimeUnit.SECONDS,                       //超时单位
  new LinkedBlockingDeque<>(3),           //阻塞队列
  Executors.defaultThreadFactory(),       //线程工厂:用来创建线程,默认即可
  new ThreadPoolExecutor.AbortPolicy()    //拒绝策略
);

9 简单理解

 四种拒绝策略

1 AbortPolicy

抛出异常!

2 CallerRunsPolicy

谁开启的这个线程,谁来执行!(例如:main线程)

3 DiscardPolicy

丢掉任务,不会抛出异常!

4 DiscardOldestPolicy

尝试去和最早的竞争,也不会抛出异常!

5 自定义拒绝策略

private class CustomRejectedExecutionHandler implements RejectedExecutionHandler {  
  
        @Override  
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {  
            // 记录异常  
            // 报警处理等  
            System.out.println("error.............");  
            try {
                executor.getQueue().put(r);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }  
    }

13、池的最大的大小如何去设置

1 CPU密集型

CPU为几核,就将最大线程数设置为几,这样这些线程之间可以并行执行!保持CPU的效率最高!

//获取当前的CPU核数
System.out.println(Runtime.getRuntime().availableProcessors());

2 IO密集型

将最大线程数设置为:大于程序中十分耗IO的线程数
因为IO操作的效率很低,使用多线程来操作可以提高效率!将这些线程都存放在线程池中,使用线程池来开启多线程,执行IO操作!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值