搜索
您的当前位置:首页正文

JUC编程

来源:吉趣旅游网

1、什么是JUC

2、线程和进程

进程:一个程序,QQ.exe Music.exe 程序的集合;
一个进程往往可以包含多个线程,至少包含一个!
进程是cpu资源分配的最小单位!

Java默认有两个线程 main 和 GC

线程:开了一个进程 Typora,写字,自动保存(线程负责的)
线程是cpu调度的最小单位!
对于Java而言:Thread、Runnable、Callable
Java无法自己开启线程

1、线程有几个状态

public enum State {

    NEW,			//新建

    RUNNABLE,		//运行

    BLOCKED,		//阻塞

    WAITING,		//等待(死等)
    
    TIMED_WAITING,	//超时等待

    TERMINATED;		//终止
}

2、wait/sleep 区别

1、来自不同的类
wait => Object
sleep => Thread

2、关于锁的释放
wait 会释放锁
sleep不会释放!

3、是否需要被唤醒
wait需要被notify
sleep不需要

4、使用的范围
wait必须在同步代码块中
sleep可以在任何地方

3、Lock锁(重点)

1、传统的synchronized

/*
 * 真正的多线程开发,要降低耦合性
 * 每一个线程要执行的就是一个单独的资源类,没有任何附属的操作!
 * */
public class SaleTicketBySyn {
    public static void main(String[] args) {
        Ticket1 ticket1 = new Ticket1();
        //以前的写法,多线程操作同一个资源类,把资源类丢入线程即可
/*        new Thread(new Runnable() {
            @Override
            public void run() {
                ticket1.sale();
            }
        }).start();*/

        //JDK1.8以后 使用lambada表达式 (参数)->{代码}
        new Thread(() -> {
            for (int i = 0; i < 50; i++)
                ticket1.sale();
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                ticket1.sale();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                ticket1.sale();
            }
        }, "C").start();
    }
}

/*
 * 资源类 oop 就是一个类,不要实现任何接口或者抽象方法
 * */
class Ticket1 {
    //属性、方法
    private int number = 50;

    //卖票的方式
    public synchronized void sale() {
        if (number > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出第" + (number--) + "张票" + ",还剩余" + number + "张");
        }
    }
}

2、Lock接口

Lock形式的锁

public class SaleTicketByLock {
    public static void main(String[] args) {
        Ticket2 ticket2 = new Ticket2();

        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                ticket2.sale();
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                ticket2.sale();
            }
        }, "B").start();

        new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                ticket2.sale();
            }
        }, "C").start();
    }
}


/*
 1. 资源类 oop 就是一个类,不要实现任何接口或者抽象方法
 2. */
class Ticket2 {
    //属性、方法
    private int number = 50;
    //获得一把可重入锁
    ReentrantLock lock = new ReentrantLock();

    /*
     * Lock三部曲
     * 1、new ReentrantLock();   获得一把锁
     * 2、lock.lock();          加锁(锁住被争夺的资源)
     * 3、lock.unlock();         解锁
     * */

    //卖票的方式
    public void sale() {
        //加锁
        lock.lock();
        try {
            // 业务代码(锁住被争夺的资源)
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出第" + (number--) + "张票" + ",还剩余" + number + "张");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 解锁
            lock.unlock();
        }
    }
}

3、synchronized和Lock的区别

4、生产者和消费者问题

1、synchronized版本

public class FalseWakeBySyn {
    public static void main(String[] args) {
        Pro_Con res = new Pro_Con();

        //开启一个负责+1操作的线程
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    res.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();

        //开启一个负责+1操作的线程
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    res.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();

        //开启一个负责-1操作的线程
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    res.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();

        //开启一个负责-1操作的线程
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    res.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();
    }
}


/*
 * 一个线程+1后,等待,唤醒其他线程进行-1操作
 * 一个线程-1后,等待,唤醒其他线程进行+1操作
 * */
class Pro_Con {
    private int number = 0;

    public synchronized void increment() throws InterruptedException {
        while (number != 0) {
            //不等于0时,不进行操作,当前线程等待,其他线程运行
            //(使用while避免虚假唤醒问题)
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "-->" + number);
        //+1后,唤醒其他线程来-1
        notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        while (number == 0) {
            //等于0时,不进行操作,当前线程等待,其他线程运行
            //(使用while避免虚假唤醒问题)
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "-->" + number);
        //-1后,唤醒其他线程来+1
        notifyAll();
    }

}

1.1 虚假唤醒问题

虚假唤醒
当我们使用if作为判断等待的条件时
唤醒线程后,不会再判断一次if,而是直接执行之后的代码

当我们使用while作为判断等待的条件时
唤醒线程后,会再判断一次条件,再决定是否执行之后的代码

使用if时,若只有一个线程+1操作,一个线程-1操作,则无问题
若有多个线程进行相同操作,就会有线程问题
使用while作为判断即可

2、Lock版本

Condition 精准的通知和唤醒线程

package com.maki.lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/*
 * 使用Lock的方式,替换掉synchronized中的wait,notify操作
 * */
public class ProductConsumerByLock {
    public static void main(String[] args) {
        Product_Consumer pc = new Product_Consumer();

        //需求:让三个线程轮流执行
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                pc.printA();
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                pc.printB();
            }
        }, "B").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                pc.printC();
            }
        }, "C").start();
    }
}

/*
 *  需求,让三个线程轮流执行
 * */
class Product_Consumer {
    private int number = 1;

    //Lock三部曲   获得锁,加锁,释放锁
    Lock lock = new ReentrantLock();                  //获得锁
    Condition condition1 = lock.newCondition();      //condition对象,监视器,用来监视一个线程
    Condition condition2 = lock.newCondition();      //condition对象,监视器,用来监视一个线程
    Condition condition3 = lock.newCondition();      //condition对象,监视器,用来监视一个线程

    public void printA() {
        lock.lock();
        try {

            //当不是1时,A线程永久等待
            while (number != 1) {
                // 等待
                condition1.await();
            }
            number = 2;
            System.out.println(Thread.currentThread().getName());
            // 唤醒B线程
            condition2.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB() {
        lock.lock();
        try {

            //当不是2时,B线程永久等待
            while (number != 2) {
                // 等待
                condition2.await();
            }
            number = 3;
            System.out.println(Thread.currentThread().getName());
            // 唤醒C线程
            condition3.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC() {
        lock.lock();
        try {

            //当不是3时,C线程永久等待
            while (number != 3) {
                // 等待
                condition3.await();
            }
            number = 1;
            System.out.println(Thread.currentThread().getName());
            // 唤醒A线程
            condition1.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

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

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

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 Test6 {
    public static void main(String[] args) {
        //一个对象
        phone6 phone6 = new phone6();

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

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

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

class phone6 {

    //静态同步方法
    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模板、phone6对象
A线程拿到class模板的锁,睡眠后;B线程拿到phone6对象的锁,执行call方法,它们是两把锁

7、第七种问题

/*
 * 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方法,它们是两把锁

6、集合类不安全

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的功能一样,区别

  1. run()–>call()
  2. 无返回值–>有返回值(与泛型中定义的一致)
  3. 不抛出异常–>可以抛出异常

但是,开启线程的方式,只有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();
   }
 }
}

注意:匿名内部类要使用的变量,必须被声明为final

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队列

它并不是什么新东西

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

1、BlockingQueue的四组API

1.1 add remove 抛出异常

/*
 * 抛出异常,有返回值
 * */
@Test
public void testAddRemove() {
    //添加成功,返回true
    System.out.println(blockingQueue.add('a'));
    //添加失败,抛出IllegalStateException异常
    System.out.println(blockingQueue.add('b'));

    //取出成功,返回元素
    System.out.println(blockingQueue.remove());
    //取出失败,抛出NoSuchElementException异常
    System.out.println(blockingQueue.remove());
}

1.2 offer poll 有返回值,无异常

/*
 * 无异常,有返回值
 * */
@Test
public void testOfferPoll() {
    System.out.println(blockingQueue.offer('a'));
    //添加失败,返回false
    System.out.println(blockingQueue.offer('b'));

    System.out.println(blockingQueue.poll());
    //取出失败,返回null
    System.out.println(blockingQueue.poll());
}

1.3 put take 无返回值,永久等待

/*
 * 永久阻塞,无返回值,无异常
 * */
@Test
public void testPutTake() throws InterruptedException {
    blockingQueue.put('a');
    //添加失败,永久等待
    blockingQueue.put('b');

    System.out.println(blockingQueue.take());
    //获取失败,永久等待
    System.out.println(blockingQueue.take());
}

1.4 offer poll 超时等待

/*
 * 超时等待,无异常,有返回值
 * */
@Test
public void testOfferPollWait() throws InterruptedException {
    System.out.println(blockingQueue.offer('a', 2, TimeUnit.SECONDS));
    //添加失败,等待2s后,返回false
    System.out.println(blockingQueue.offer('a', 2, TimeUnit.SECONDS));

    System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
    //获取失败,等待2s后,返回null
    System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
}

11、线程池(重点)

线程池:3大方法、7大参数、4种拒绝策略

线程池的好处:线程复用、可以控制最大并发数、管理线程

1、三大方法

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

1.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();
}

1.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();
}

1.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();

2、七大参数

我们使用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大参数!

2.1 corePoolSize

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

for (int i = 1; i < 2; i++) {
  threadPool.execute(()->{
      System.out.println(Thread.currentThread().getName()+"--->ok");
  });
}
pool-1-thread-1--->ok
pool-1-thread-2--->ok

2.2 maximumPoolSize

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

for (int i = 1; i <=6; i++) {
  threadPool.execute(()->{
      System.out.println(Thread.currentThread().getName()+"--->ok");
  });
}
pool-1-thread-1--->ok
pool-1-thread-1--->ok
//i=3时,想要开启一个新线程,先进入了阻塞队列;功能由核心线程帮你完成
pool-1-thread-1--->ok
pool-1-thread-1--->ok
pool-1-thread-2--->ok
//i=6时,阻塞队列满了,开启一个新线程
pool-1-thread-3--->ok

2.3 keepAliveTime

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

2.4 TimeUnit

超时单位

2.5 BlockingQueue

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

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

2.6 ThreadFactory

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

Executors.defaultThreadFactory();

2.7 RejectedExecutionHandler

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

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

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

2.8 ThreadPoolExecutor手动创建线程池

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

2.9 简单理解


最大线程池大小,表示线程池中的最大线程数,即你可以开启多少个线程,并发执行的最大线程数!

当你要开启一段线程,执行一段业务时,在阻塞队列未满之前,优先使用核心线程来处理业务!

当阻塞队列满了,才会再次开启一个新的线程,但是该线程在指定的时间内,没有被调用,会自动释放!

开启的线程数达到了最大,阻塞队列也满了后,再要开启一段线程执行业务时,使用拒绝策略!

3、四种拒绝策略


当我们使用2.8中的方式:
2个核心线程,最大5个线程,阻塞队列为3时,再开启第9个线程后

3.1 AbortPolicy

抛出异常!

3.2 CallerRunsPolicy

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

3.3 DiscardPolicy

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

3.4 DiscardOldestPolicy

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

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

4.1 CPU密集型

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

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

4.2 IO密集型

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

12、四大函数式接口(重点)

什么是函数式接口

只有一个方法的接口,例如Runnable接口

@FunctionalInterface 
public interface Runnable { 
	public abstract void run(); 
}

任何的函数式接口,都可以使用lambada表达式 ( ) -> { }
( ) 中为方法的形参,他的数据类型默认为Object,若定义了泛型,则为泛型中的数据类型!
{ } 中写方法体的内容

1、Function函数式接口


接收参数1,返回参数2!

public static void main(String[] args) {
 //接收参数1,返回参数2
/* Function<String, Integer> function = new Function<String, Integer>() {
     @Override
     public Integer apply(String str) {
         return 1024;
     }
 };
 System.out.println(function.apply("接收参数1,返回参数2"));*/

 //lambada表达式写法!
 Function<String, Integer> function = (str) -> {
     return 1024;
 };
 System.out.println(function.apply("接收参数1,返回参数2"));
}

2、Predicate断定型接口


接收参数,返回boolean

public static void main(String[] args) {
/* Predicate<String> predicate=new Predicate<String>() {
     @Override
     public boolean test(String str) {
         return str.isEmpty();
     }
 };
 System.out.println(predicate.test("断定型接口,接收参数,返回boolean"));*/

 //lambada表达式写法!
 Predicate<String> predicate = (str) -> {
         return str.isEmpty();
 };
 System.out.println(predicate.test("断定型接口的lambada表达式"));
}

3、Consumer 消费型接口


接收一个参数,无返回值

public static void main(String[] args) {
/* Consumer<Integer> consumer=new Consumer<Integer>() {
     @Override
     public void accept(Integer integer) {
         System.out.println("integer = " + integer);
     }
 };
 consumer.accept(5);*/

 //lambada表达式写法
 Consumer<String> consumer = (str) -> {
     System.out.println("str = " + str);
 };
 consumer.accept("消费型接口");
}

4、Supplier 供给型接口

public static void main(String[] args) {
/* Supplier<String> supplier=new Supplier<String>() {
     @Override
     public String get() {
         return "Supplier接口";
     }
 };
 System.out.println(supplier.get());*/

 //lambada表达式
 Supplier<String> supplier = () -> {
     return "Supplier接口";
 };
 System.out.println(supplier.get());
}

13、Stream流式计算

其实就是用来筛选数据的;所有关于计算的操作,应该交给流来操作


题目要求:一分钟内完成此题,只能用一行代码实现!
现在有5个用户!筛选:

  • ID 必须是偶数
  • 年龄必须大于23岁
  • 用户名转为大写字母
  • 用户名字母倒着排序
  • 只输出一个用户
User u1 = new User(1, "a", 28);
User u2 = new User(2, "b", 13);
User u3 = new User(3, "c", 23);
User u4 = new User(4, "d", 12);
User u5 = new User(5, "e", 41);
//1、先将数据存储到集合中
List<User> users = Arrays.asList(u1, u2, u3, u4, u5);

//2、计算交给Stream流(lambada表达式,链式编程,函数式接口)
//2.1 将集合转为一个Stream对象;该方法是collection接口中的方法;
//    集合上添加了泛型,这样Stream流对象就知道操作的是什么数据了
users.stream()
  //2.2 filter方法进行过滤id为偶数的用户
  //它接收一个Predicate断定式接口,接收一个参数,返回boolean
  .filter((u)->{return u.getId()%2==0;})
  
  //2.3 过滤年龄大于10的用户
  .filter((u)->{return u.getAge()>10;})
  
  //2.4 用户名转为大写
  //它接收一个Function函数式接口,接收参数1,返回	参数2
  //这里的参数1(u)是集合中的每一个User对象,参数2返回的是一个String
  .map((u)->{return u.getName().toUpperCase();})
  
  //2.5 倒序排序
  //它接收一个Comparator接口,该接口也是函数式接口
  //接收两个参数,返回int
  .sorted((str1,str2)->{return str2.compareTo(str1);})
  
  //2.6 只输出1个用户
  .limit(1)
  .forEach(System.out::println);
}

因篇幅问题不能全部显示,请点此查看更多更全内容

Top