JUC并发编程(上)

1、什么是JUC

  • 并发编程目的是:充分利用CPU资源提高程序性能
  • java.util.concurrent(java并发编程的工具类)

2、线程

  • 进程:例如我们电脑开启的每个应用程序

  • 线程:一个进程包含至少一个线程,java中最少有两个线程:main、gc

  • 并发:多个线程快速交替操作一个资源类的过程

  • 并行:多核CPU情况下,每个CPU可同时执行一个线程,互不抢占CUP资源

  • 线程状态

  • NEW:尚未启动的线程的线程状态

  • RUNNABLE:可运行线程的线程状态

  • BLOCKED:等待监视器锁的阻塞线程的线程状态

  • WAITING:等待线程的线程状态

  • TIMED_WAITING:具有指定等待时间的等待线程的线程状态

  • TERMINATED:终止线程的线程状态。线程已经完成执行

  • wait、sleep的区别?

      • wait–>Object的方法
      • sleep–>Thread的方法(谁调用谁睡觉,A调用B的sleep方法,则A在睡觉)
    • 是否释放锁

      • sleep:抱着锁睡觉不释放
      • wait:会释放锁
    • 使用范围

      • wait/notify/notifyAll只能用在同步方法或者同步块中
      • sleep可以在任意地方使用
    • 异常

      • 都需要捕获InterruptedException异常

3、synchronized(自动挡)与Lock(手动挡)的区别

  • Synchronized是关键字,java内置,Lock是一个java类
  • Synchronized无法判断是否获得锁,Lock可以判断
  • Synchronized会自动释放锁,Lock需要在finally释放锁(不释放会死锁,多个lock()就要有多个unlock())
  • Synchronized一个线程阻塞,其他线程就要永久等下去,Lock可以通过tryLock()尝试获得锁,获取不到就结束等待
  • Synchronized可重入(进了大门就能进卧室或者厕所)、不可中断,非公平的,Lock可重入,可中断,可公平非公平(构造支持手动设置公平或者不公平)
    • 公平:需要排队,必须先来后到
    • 不公平:可插队(好处:提高效率,例如不需要等待执行时间长的线程)

synchronized代码

/**
 * 属性   方法    高内聚(不要直接在资源类实现runnable接口)
 */
public class SaleTicket {
    private int number =30;

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

/**
 * 多线程编程套路:
 * 1.高内聚  低耦合   (前提)
 * 2.线程   操作(资源类对外暴露的方法)  资源类  (要点)
 *
 *
 * 3个售票员售30张票
 */
class Test{
    public static void main(String[] args) {
        SaleTicket saleTicket = new SaleTicket();

        new Thread(()->{
            for (int i = 1; i <= 30; i++) {
                saleTicket.sale();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 1; i <= 30; i++) {
                saleTicket.sale();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 1; i <= 30; i++) {
                saleTicket.sale();
            }
        },"C").start();
    }
}

Lock 代码

/**
 * 属性   方法    高内聚(不要直接在资源类实现runnable接口)
 */
public class SaleTicket2 {
    private int number =40;
    private Lock lock = new ReentrantLock();
    public void sale(){
        lock.lock();
        try {
            if(number>0){
                System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票,还剩"+number+"张票");
            }
        } finally {
            lock.unlock();
        }
    }
}

/**
 * 多线程编程套路:
 * 1.高内聚  低耦合   (前提)
 * 2.线程   操作(资源类对外暴露的方法)  资源类  (要点)
 *
 * 3个售票员售40张票
 */
class Test2{
    public static void main(String[] args) {
        SaleTicket2 saleTicket = new SaleTicket2();

        new Thread(()->{
            for (int i = 1; i <= 40; i++) {
                saleTicket.sale();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 1; i <= 40; i++) {
                saleTicket.sale();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 1; i <= 40; i++) {
                saleTicket.sale();
            }
        },"C").start();
    }
}
  • 生产者、消费者:判断 干活 通知(线程之间是不能通信,所以需要调度线程)

Synchronized版生产者消费者代码(含虚假唤醒处理)

/**
 * 题目: 两个线程,操作一个初始值为0的变量
 *      一个线程+1   一个线程-1     判断什么时候+1  什么时候-1
 *      交替10次
 *
 *  多线程编程套路:
 *  * 1.高内聚  低耦合   (前提)
 *  * 2.线程   操作(资源类对外暴露的方法)  资源类  (要点)
 *
 *  生产者消费者模型:判断  干活  通知
 */
public class Test {

    public static void main(String[] args) {
        Data data = new Data();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    data.incrementNum();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    data.decrementNum();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    data.decrementNum();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    data.decrementNum();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
class Data{ 
	   private int num =0 ; 
       //+1
       public synchronized void incrementNum() throws InterruptedException{
           //判断
           /*if(num!=0){//会产生虚假唤醒
               this.wait();
           }*/
           while(num!=0){ //while防止虚假唤醒
               this.wait();
           }
           //干活
           num++;
           System.out.println(Thread.currentThread().getName()+"\t"+num);
   
           //通知
           this.notifyAll();
       }
   
       //-1
       public synchronized void decrementNum() throws InterruptedException{
           //判断
           /*if(num==0){ 会产生虚假唤醒
               this.wait();
           }*/
           while(num==0){//while防止虚假唤醒
               this.wait();
           }
           //干活
           num--;
           System.out.println(Thread.currentThread().getName()+"\t"+num);
   
           //通知
           this.notifyAll();
       }
   }

Lock版生产者消费者(含精准唤醒)

/**
 * 三个线程  A B C
  * 三个线程依次打印
  * A 5次
  * B 10次
  * C 15次
  * 依次循环
  *
  * 精准唤醒   通过num标识
  */
 public class C {
 
     public static void main(String[] args) {
         Data3 data3 = new Data3();
 
         new Thread(()->{
             data3.print5();
         },"A").start();
         new Thread(()->{
             data3.print10();
         },"B").start();
         new Thread(()->{
             data3.print15();
         },"C").start();
     }
 
 }
 class Data3{
     private int num =1 ;
     private Lock lock = new ReentrantLock();
     private Condition condition1 = lock.newCondition();
     private Condition condition2 = lock.newCondition();
     private Condition condition3 = lock.newCondition();
 
     public  void print5(){
         lock.lock();
         try {
             //判断
             while(num!=1){ //while防止虚假唤醒
                 condition1.await();
             }
             //干活
             for (int i = 1; i <= 5; i++) {
                 System.out.println(Thread.currentThread().getName()+"\t"+i);
             }
             num=2;
 
             //通知第二个线程干活(指定线程干活)
             condition2.signal();
         }catch (InterruptedException e){
             e.printStackTrace();
         }finally {
             lock.unlock();
         }
     }
     public  void print10(){
         lock.lock();
         try {
             //判断
             while(num!=2){ //while防止虚假唤醒
           condition2.await();
             }
          //干活
             for (int i = 1; i <= 10; i++) {
              System.out.println(Thread.currentThread().getName()+"\t"+i);
             }
          num=3;
 
             //通知第三个线程干活(指定线程干活)
             condition3.signal();
         }catch (InterruptedException e){
             e.printStackTrace();
         } finally {
             lock.unlock();
         }
     }
     public  void print15(){
         lock.lock();
         try {
             //判断
             while(num!=3){ //while防止虚假唤醒
                 condition3.await();
             }
             //干活
             for (int i = 1; i <= 15; i++) {
                 System.out.println(Thread.currentThread().getName()+"\t"+i);
             }
             num=1;
 
             //通知第一个线程干活(指定线程干活)
             condition1.signal();
         }catch (InterruptedException e){
             e.printStackTrace();
         } finally {
             lock.unlock();
         }
     }
 }

3、8锁现象透彻理解

1、标准访问,请问先打印邮件还是短信?

/**
 * 标准访问:先打印邮件还是短信
 * 
 *  邮件
 */
public class Test {
    public static void main(String[] args) {
        Phone phone = new Phone();
        //两个线程使用的是同一个对象,两个线程一把锁,所以先调用的先执行
        new Thread(()->{
            phone.sendEmail();
        },"A").start();
        new Thread(()->{
            phone.sendSm();
        },"B").start();
    }
}
class Phone{

    //synchronized  锁的是方法的调用者
    public synchronized void sendEmail(){
        System.out.println("sendEmail");
    }

    public synchronized void sendSm(){
        System.out.println("sendSm");
    }
}

2、邮件方法暂停4秒钟,请问先打印邮件还是短信?

/**
 * **2、邮件方法暂停4秒钟,请问先打印邮件还是短信?**
 *
 *  邮件
 */
public class Test2 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        //两个线程使用的是同一个对象,两个线程一把锁,所以先调用的先执行
        new Thread(()->{
            phone.sendEmail();
        },"A").start();
        new Thread(()->{
            phone.sendSm();
        },"B").start();
    }
}
class Phone2{

    //synchronized  锁的是方法的调用者
    public synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(2);
            System.out.println("sendEmail");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void sendSm(){
        System.out.println("sendSm");
    }
}

3、新增一个普通方法hello()没有同步,请问先打印邮件还是hello?

/**
 * 新增一个普通方法hello()没有同步,请问先打印邮件还是hello?
 *
 *  hello
 */
public class Test3 {
    public static void main(String[] args) throws InterruptedException {
     Phone3 phone = new Phone3();
        //两个线程使用的是同一个对象,两个线程一把锁,所以先调用的先执行
     new Thread(()->{  //先执行
            phone.sendEmail();
        },"A").start();

        //干扰
        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{  //一秒后执行
            phone.hello();
        },"B").start();
    }
}

//锁:竞争关系
class Phone3{

    //synchronized  锁的是方法的调用者
    public synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(2);
            System.out.println("sendEmail");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void sendMS() {
        System.out.println("sendMS");
    }

    //hello没有被Synchronized修饰,不是同步方法,所以不需要等待,其他线程用了一把锁
    public void hello(){
        System.out.println("hello");
    }
}

4、两部手机、请问先打印邮件还是短信?

/**
 * 两部手机、请问先打印邮件还是短信?
 * 
 * 短信
 *
 */
public class Test4 {

    //像:回家  分别进卧室(锁)  卫生间
   public static void main(String[] args) throws InterruptedException {
        //两个对象,互不干扰
     Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();

        new Thread(()->{  //一开始就执行
            phone1.sendEmail();
        },"A").start();

        //干扰
        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{  //一秒后执行
            phone2.sendMS();
        },"B").start();
    }
}

//锁:竞争关系
class Phone4{

    //被synchronized修饰的方法,锁的对象是方法的调用者,
    // 调用者不同,两者就没有关系,两个方法用的就不是同一个锁
    public synchronized void sendEmail(){
        //善意的延迟  这个说法真可爱
        try {
            TimeUnit.SECONDS.sleep(2);
            System.out.println("sendEmail");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void sendMS() {
        System.out.println("sendMS");
    }
}

5、两个静态同步方法,同一部手机,请问先打印邮件还是短信?

/**
 * 两个静态同步方法,同一部手机,请问先打印邮件还是短信?
 *
 * 邮件
 *
 */
public class Test5 {

    //像:回家  分别进卧室(锁)  卫生间
    public static void main(String[] args) throws InterruptedException {
     //两个对象,互不干扰
        Phone5 phone1 = new Phone5();

        new Thread(()->{  //一开始就执行
            phone1.sendEmail();
        },"A").start();

        //干扰
        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{  //一秒后执行
            phone1.sendMS();
        },"B").start();
    }
}

class Phone5{

    //对象  类模板可以new很多个对象
    //class  类模板  只有一个

    //被synchronized 和 static 修饰的方法,锁的对象是 类的class对象  唯一的
    //同一把锁
    public static synchronized void sendEmail(){
        //善意的延迟  这个说法真可爱
        try {
            TimeUnit.SECONDS.sleep(2);
            System.out.println("sendEmail");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static synchronized void sendMS() {
        System.out.println("sendMS");
    }
}

6、两个静态同步方法,2部手机,请问先打印邮件还是短信?

/**
 * 两个静态同步方法,2部手机,请问先打印邮件还是短信?
 *
 * 邮件
 *
 */
public class Test6 {

    //像:回家  分别进卧室(锁)  卫生间
    public static void main(String[] args) throws InterruptedException {
        //两个对象,同一个class模板,同一把锁  所以先调用的先执行
     Phone6 phone1 = new Phone6();
        Phone6 phone2 = new Phone6();

        new Thread(()->{  //一开始就执行
            phone1.sendEmail();
        },"A").start();

        //干扰
        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{  //一秒后执行
            phone2.sendMS();
        },"B").start();
    }
}


class Phone6{

    //对象  类模板可以new很多个对象
    //class  类模板  只有一个

    //被synchronized 和 static 修饰的方法,锁的对象是 类的class对象  唯一的
    //同一把锁
    public static synchronized void sendEmail(){
        //善意的延迟  这个说法真可爱
        try {
            TimeUnit.SECONDS.sleep(2);
            System.out.println("sendEmail");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static synchronized void sendMS() {
        System.out.println("sendMS");
    }
}

7、一个普通同步方法,一个静态同步方法,同一部手机,请问先打印邮件还是短信?

/**
 * 一个普通同步方法,一个静态同步方法,同一部手机,请问先打印邮件还是短信?
 *
 * 短信  (对象不一致、class锁跟对象锁互不干预)
 *
 */
public class Test7 {

    //像:回家  分别进卧室(锁)  卫生间
 public static void main(String[] args) throws InterruptedException {
        //两个对象,互不干扰
        Phone7 phone1 = new Phone7();

        new Thread(()->{  //一开始就执行
            phone1.sendEmail();
        },"A").start();

        //干扰
        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{  //一秒后执行
            phone1.sendMS();
        },"B").start();
    }
}


class Phone7{

   //CLASS
    public static synchronized void sendEmail(){
        //善意的延迟  这个说法真可爱
        try {
            TimeUnit.SECONDS.sleep(2);
            System.out.println("sendEmail");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //对象
    public synchronized void sendMS() {
        System.out.println("sendMS");
    }
}

8、一个普通同步方法,一个静态同步方法,2部手机,请问先打印邮件还是短信?

/**
 * 一个普通同步方法,一个静态同步方法,2部手机,请问先打印邮件还是短信?
 *
 * 短信  (对象不一致、class锁跟对象锁互不干预)
 *
 */
public class Test8 {

 //像:回家  分别进卧室(锁)  卫生间
    public static void main(String[] args) throws InterruptedException {
     //两个对象,互不干扰
        Phone7 phone1 = new Phone7();
     Phone7 phone2 = new Phone7();

     new Thread(()->{  //一开始就执行
            phone1.sendEmail();
     },"A").start();

     //干扰
        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{  //一秒后执行
            phone1.sendMS();
        },"B").start();
    }
}


class Phone8{

   //CLASS
    public static synchronized void sendEmail(){
        //善意的延迟  这个说法真可爱
        try {
            TimeUnit.SECONDS.sleep(2);
            System.out.println("sendEmail");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //对象
    public synchronized void sendMS() {
        System.out.println("sendMS");
    }
}

小结

  1. new this 本身的这个对象,调用者
  2. static class 类模板,保证唯一!
  3. 一个对象中有多个 synchronized 方法,某个时刻内只要有一个线程去访问 synchronized 方法了就会被加锁,独立公共厕所!其他线程就会阻塞!
  4. 加了一个普通方法后 ,两个对象,无关先后,一个有锁,一个没锁!情况会变化!
  5. 换成静态同步方法,情况会变化! CLASS ,所有静态同步方法的锁唯一,锁的是对象实例class 本身!

4、集合类不安全

List:单线程安全,多线程不安全

  • 单线程
package com.coding.collunsafe;

import java.util.Arrays;
import java.util.List;

public class UnsafeList1 {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "b", "c");
        list.forEach(System.out::println);
    }
}
  • 多线程
package com.coding.unsafe;

import java.util.*;

/**
 * 善于总结:
 *  1.故障现象
 *  2.导致原因
 *  3.解决方法
 *
 */
public class UnsafeList2 {

    public static void main(String[] args) {

        //代码实现
        效率高不支持并发(add 方法不加锁)
        ArrayList<String> list = new ArrayList<>();

        // 测试list多线程下是否安全
        //不安全  多线程下会产生一个异常:ConcurrentModificationException


        for (int i = 1; i <= 300; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,4));
                System.out.println(list);
            }).start();
        }
    }
}
  • List多线程不安全解决办法
package com.coding.unsafe;

import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 善于总结:
 *  1.故障现象
 *  2.导致原因
 *  3.解决方法
 *
 */
public class UnsafeList3 {

    public static void main(String[] args) {

        //代码实现
        效率高不支持并发(add 方法不加锁)
        //ArrayList<String> list = new ArrayList<>();
        // 线程安全的类  效率低(add方法使用锁)
       // List<String> list = new Vector<>();  50
       // List<String> list = Collections.synchronizedList(new ArrayList());  60

        //写入时复制  在多线程高并发编程中,一致性最重要
        //如果是读  不加锁
        //如果是写  就要拷贝一份到自己那里,修改完再放回去
        List<String> list = new CopyOnWriteArrayList<>();


        // 测试list多线程下是否安全
        //不安全  多线程下会产生一个异常:ConcurrentModificationException


        for (int i = 1; i <= 300; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,4));
                System.out.println(list);
            }).start();
        }
    }
}

Set

package com.coding.unsafe;

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

public class UnsafeSet {

    public static void main(String[] args) {
        Set<String> hashSet = new HashSet<>();//底层是什么?new HashMap
        for (int i = 1; i <= 30; i++) {//多线程会报 ConcurrentModificationException
            new Thread(()->{
                hashSet.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(hashSet);
            },String.valueOf(i)).start();
        }
    }
}
  • Set多线程不安全解决办法
package com.coding.unsafe;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

public class SafeSet {

    public static void main(String[] args) {
        //第一种方法  60分
        //Set<Object> hashSet = Collections.synchronizedSet(new HashSet<>());

        //第二种方法  100分
        Set<String> hashSet = new CopyOnWriteArraySet<>();
        for (int i = 1; i <= 300; i++) {//多线程会报 ConcurrentModificationException
            new Thread(()->{
                hashSet.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(hashSet);
            },String.valueOf(i)).start();
        }
    }
}

Map

package com.coding.unsafe;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class UnsafeMap {
    public static void main(String[] args) {
        Map<String, Object> hashMap = new HashMap<>();
        //初始容量  加载因子  上面等同于下面
        //Map<String, Object> hashMap = new HashMap<>(16, 0.75F);
        for (int i = 1; i <= 300; i++) {//多线程会报 ConcurrentModificationException
            new Thread(()->{
                hashMap.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(hashMap);
            },String.valueOf(i)).start();
        }

    }
}

  • Map多线程不安全解决办法
package com.coding.unsafe;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class SafeMap {
    public static void main(String[] args) {

        //第一种方法  coding老师jdk1.8没有
        //Map<String, Object> hashMap = Collections.synchronizedMap(new HashMap<>());

        //第二种方法
        Map<Object, Object> hashMap = new ConcurrentHashMap<>();


        for (int i = 1; i <= 300; i++) {
            new Thread(()->{
                hashMap.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(hashMap);
            },String.valueOf(i)).start();
        }

    }
}

5、Callable

  • 线程创建方式:Thread、Runnable、Callable区别
    • 是否有返回值
    • 是否抛出异常(Callable task.get()的时候需要抛出:ExecutionException、InterruptedException)
    • 方法不同:run()、call()
package com.coding.callableDemo;

import com.coding.demo02.A;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

public class Test1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //new Thread(Runnable)
        //new Thread(RunnableFuture)
        //new Thread(FutureTask)

        //new Thread(new A(),"A").start();
        FutureTask<Integer> task = new FutureTask<>(new B());

        new Thread(task,"A").start();//两个线程仍然输出一个结果
        new Thread(task,"B").start();//细节1:结果缓存,提高N倍效率

        System.out.println(task.get());//获取结果
        //细节2:get()一般放在最后,保证结果平稳运行的效率,因为它会阻塞等待结果产生
        //用在于线程耗时且结果返回不重要的情况下

    }
}

/*class A implements Runnable{
    @Override
    public void run() {
        System.out.println("Runnable");
    }
}*/

class B implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        System.out.println("Callable");

        TimeUnit.SECONDS.sleep(10);
        return 1024;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值