JUC : 并发编程工具类的使用

个人博客网:https://wushaopei.github.io/    (你想要这里多有)

一、JUC是什么

1、JUC定义

JUC,即java.util.concurrent 在并发编程中使用的工具类

              

2、进程、线程的定义

2.1 进程、线程是什么?

进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
 
线程:通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。

2.2 进程、线程例子

  •  使用QQ,查看进程一定有一个QQ.exe的进程,我可以用qq和A文字聊天,和B视频聊天,给C传文件,给D发一段语言,QQ支持录入信息的搜索。
  • 大四的时候写论文,用word写论文,同时用QQ音乐放音乐,同时用QQ聊天,多个进程。
  • word如没有保存,停电关机,再通电后打开word可以恢复之前未保存的文档,word也会检查你的拼写,两个线程:容灾备份,语法检查

二、Lock 接口

1、Lock 接口定义

Lock :Lock 是juc 下的一个接口类,用于对多线程的同步实现。

            

2、Lock接口的实现ReentrantLock可重入锁

         

2.1 Lock 的使用示例:

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...
 
   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

3、Thread 类 创建线程的方式

3.1 继承Thread 类

public class SaleTicket extends Thread

问题:java是单继承,资源宝贵,要用接口方式

如果使用继承Thread 类的方式来创建线程,对资源是很大的浪费。所以实际开发中不建议这么写

3.2  new Thread()

Thread t1 = new Thread();
   t1.start();

这里通过创建线程实例的方式来创建线程,但在实际开发中,这样是不好的,对资源的浪费依旧居高不下。

3.3 通过接口实现类为形参的方式注入创建 Thread 线程

       

Thread(Runnable target, String name) 

  这里是通过 Tread 有参构造的方式创建一个新的线程对象。

4、实现现成的三种方法

4.1 新建类实现 runnable接口

class MyThread implements Runnable//新建类实现runnable接口
 
new Thread(new MyThread,...)

这种方法会新增类,有更新更好的方法

4.2 匿名内部类

   new Thread(new Runnable() {
    @Override
    public void run() {
 
    }
   }, "your thread name").start();

这种方法不需要创建新的类,可以 new 接口

4.3 lambda 表达式

 new Thread(() -> {
 
 }, "your thread name").start();

  这种方法代码更简洁精炼

5、案例代码:

package com.atguigu.thread;
 
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
class Ticket //实例例eld +method
{
 private int number=30;
/* //1同步 public synchronized void sale() 
 {//2同步  synchronized(this) {}
  if(number > 0) {
    System.out.println(Thread.currentThread().getName()+"卖出"+(number--)+"\t 还剩number);
   }
 }*/
 
// Lock implementations provide more extensive locking operations
// than can be obtained using synchronized methods and statements. 
 private Lock lock = new ReentrantLock();//List list = new ArrayList()
 
 public void sale() {
   lock.lock();
   
   try {
    if(number > 0) {
     System.out.println(Thread.currentThread().getName()+"卖出"+(number--)+"\t 还剩number);
    }
   } catch (Exception e) {
    e.printStackTrace();
   } finally {
    lock.unlock();
   }
 }
}
 
/**
 * 
 * @Description:卖票程序个售票出  0张票
 @author xiale
 * 笔记:J里面如何 1 多线程编-上
    1.1 线程  (资里源类 *   1.2 高内聚 /
public class SaleTicket 
{
 public static void main(String[] args)//main所有程序
   Ticket ticket = new Ticket();
   //Thread(Runnable target, String name) Allocates a new Thread object.
 
 new Thread(() -> {for (int i = 1; i < 40; i++)ticket.sale();}, "AA").start();
 new Thread(() -> {for (int i = 1; i < 40; i++)ticket.sale();}, "BB").start();
 new Thread(() -> {for (int i = 1; i < 40; i++)ticket.sale();}, "CC").start();

/*  new Thread(new Runnable() {
    @Override
    public void run() 
    {
     for (int i = 1; i <=40; i++) 
     {
       
       ticket.sale();
     }
    }
   }, "AA").start();
   
   new Thread(new Runnable() {
    @Override
    public void run() 
    {
     for (int i = 1; i <=40; i++) 
     {
       ticket.sale();
     }
    }
   }, "BB").start();
   new Thread(new Runnable() {
    @Override
    public void run() 
    {
     for (int i = 1; i <=40; i++) 
     {
       ticket.sale();
     }
    }
   }, "CC").start();
   */
 }
}
//1 class MyThread implements Runnable
 
//2 匿名内部类
 
//3 laExpress

三、java 8 新特性

1、lambda 表达式

1.1 查看例子: LambdaDemo

1.2 要求:接口只有一个方法

lambda表达式,如果一个接口只有一个方法,我可以把方法名省略
Foo foo = () -> {System.out.println("****hello lambda");};

写法分析: 拷贝小括号(),写死右箭头->,落地大括号{...}

1.3 函数式接口

lambda表达式,必须是函数式接口,必须只有一个方法;如果接口只有一个方法java默认它为函数式接口。

为了正确使用Lambda表达式,需要给接口加个注解:

@FunctionalInterface

如有两个方法,立刻报错


          Runnable接口为什么可以用lambda表达式?

         


2、接口里是否能有实现方法?

2.1 default 方法

接口里在java8后容许有接口的实现,default方法默认实现

default int div(int x,int y) {
  return x/y;
 }

     接口里default方法可以有几个?

2.2 静态方法实现

静态方法实现:接口新增

public static int sub(int x,int y){
  return x-y;
}

可以有几个?
注意静态的叫类方法,能用foo去调吗?要改成Foo

3、代码

package com.atguigu.thread;
@FunctionalInterface
interface Foo{

// public void sayHello() ;
// public void say886() ;
 
 public int add(int x,int y);
 
 default int div(int x,int y) {
   return x/y;
 }
 
 public static int sub(int x,int y) {
   return x-y;
 }
}
 
/**
 * 
 * @Description: Lambda Express-----> 函数式编程
 * 1 拷贝小括号(形参列表),写死右箭头 ->,落地大括号 {方法实现}
 * 2 有且只有一个public方法@FunctionalInterface注解增强定义
 * 3 default方法默认实现
 * 4 静态方法实现
 */
public class LambdaDemo
{
 public static void main(String[] args)
 {
//  Foo foo = new Foo() {
//   @Override
//   public void sayHello() {
//     System.out.println("Hello!!");
//   }
//
//   @Override
//   public void say886() {
//     // TODO Auto-generated method stub
//     
//   }
//  };
//  foo.sayHello();
//  System.out.println("============");
//  foo = ()->{System.out.println("Hello!! lambda !!");};
//  foo.sayHello();
 
 Foo foo = (x,y)->{
   System.out.println("Hello!! lambda !!");
   return x+y;
   };
   
   int result = foo.add(3,5);
   System.out.println("******result="+result);
   System.out.println("******result div="+foo.div(10, 2));
   System.out.println("******result sub="+Foo.sub(10, 2));
   
 }
}

四、Callalbe 接口

1、Callable接口

1.1 是什么?

定义:Callable 是用来获得多线程的方法。


注意:(1)继承thread类(2)runnable接口
如果只回答这两个你连被问到juc的机会都没有


2、与Runnable 对比

创建新类MyThread实现runnable接口

class MyThread implements Runnable{
 @Override
 public void run() {
 
 }
}

新类MyThread2实现callable接口

class MyThread2 implements Callable<Integer>{
 @Override
 public Integer call() throws Exception {
  return 200;
 } 
}

 面试题: callable接口与runnable接口的区别?

答:(1)是否有返回值
       (2)是否抛异常
       (3)落地方法不一样,一个是run,一个是call

3、Callable 怎么用?

不能直接替换Runnable 来使用,原因: thread类的构造方法根本没有Callable

     

解决办法:java 多态,一个类可以实现多个接口

   

从上图可以知道,Runnable 实现类FutureTask 接口;Callable 的实例可以作为Future Task 的构造参数。

FutureTask<Integer> ft = new FutureTask<Integer>(new MyThread());
new Thread(ft, "AA").start();

如以上的函数一样,实现多线程的创建。

关于 运行成功后如何获得返回值?

 

ft.get();

4、Future Task

4.1 Future Task 是什么?

                            

在项目中,用它在执行线程时,做异步调用,类似于使用 main 将一个个方法串起来,从而解决: 正常调用挂起堵塞问题。

4.2 原理

在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。

一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。

仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
 

  • 只计算一次
  • get方法放到最后

4.3 代码

package com.atguigu.thread; 
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class MyThread implements Callable<Integer>
{
  @Override
  public Integer call() throws Exception
  {
     Thread.sleep(4000);
     System.out.println(Thread.currentThread().getName()+"  *****come in call");
     return 200;
  }
}
 
/**
 * 
 * @Description: Callable接口获得多线程
 * @author xialei
 * @date 
 * 笔记结论见最后
 */ 
public class CallableDemo
{
  public static void main(String[] args) throws InterruptedException, ExecutionException
  {
     
     FutureTask<Integer> ft = new FutureTask<Integer>(new MyThread());
     new Thread(ft, "AA").start();
     
     /*FutureTask<Integer> ft2 = new FutureTask<Integer>(new MyThread());
     new Thread(ft2, "BB").start();*/
     
     
     System.out.println(Thread.currentThread().getName()+"------main");
     
     
     Integer result = ft.get();
     //Integer result2 = ft2.get();
     System.out.println("**********result: "+result);
   } 
}
 
/**
 * 
 * 
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,
当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。
 
一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
 
仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,
就不能再重新开始或取消计算。get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,
然后会返回结果或者抛出异常。 
 
只计算一次
get方法放到最后
 */

五、线程间通信

1、面试题:两个线程打印

两个线程,一个线程打印1-52,另一个打印字母A-Z打印顺序为12A34B...5152Z,
要求用线程间通信

2、例子:NotifyWaitDemo

3、线程间通信:1、生产者+消费者2、通知等待唤醒机制

4、多线程编程模板下

  • 判断
  • 干活
  • 通知

5、synchronized实现

5.1 代码

package com.atguigu.thread; 
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.omg.IOP.Codec;
class ShareDataOne//资源类
{
  private int number = 0;//初始值为零的一个变量
 
  public synchronized void increment() throws InterruptedException 
  {
     //1判断
     if(number !=0 ) {
       this.wait();
     }
     //2干活
     ++number;
     System.out.println(Thread.currentThread().getName()+"\t"+number);
     //3通知
     this.notifyAll();
  }
  
  public synchronized void decrement() throws InterruptedException 
  {
     // 1判断
     if (number == 0) {
       this.wait();
     }
     // 2干活
     --number;
     System.out.println(Thread.currentThread().getName() + "\t" + number);
     // 3通知
     this.notifyAll();
  }
}
/**
 * 
 * @Description:
 *现在两个线程,
 * 可以操作初始值为零的一个变量,
 * 实现一个线程对该变量加1,一个线程对该变量减1,
 * 交替,来10轮。 
 * @author xialei
 *
 *  * 笔记:Java里面如何进行工程级别的多线程编写
 * 1 多线程变成模板(套路)-----上
 *     1.1  线程    操作    资源类  
 *     1.2  高内聚  低耦合
 * 2 多线程变成模板(套路)-----下
 *     2.1  判断
 *     2.2  干活
 *     2.3  通知
 */
public class NotifyWaitDemoOne
{
  public static void main(String[] args)
  {
     ShareDataOne sd = new ShareDataOne();
     new Thread(() -> {
       for (int i = 1; i < 10; i++) {
          try {
            sd.increment();
          } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }
       }
     }, "A").start();
     new Thread(() -> {
       for (int i = 1; i < 10; i++) {
          try {
            sd.decrement();
          } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }
       }
     }, "B").start();
  }
}

5.2 如果换成4个线程执行呢?

如果换成4个线程会导致错误,虚假唤醒

  • 原因:在java多线程判断时,不能用if,程序出事出在了判断上面
  • 突然有一天加的线程进到if了,突然中断了交出控制权
  • 没有进行验证,而是直接走下去了,加了两次,甚至多次

5.3 解决办法

解决虚假唤醒:查看API,java.lang.Object

    

中断和虚假唤醒是可能产生的,所以要用loop循环,if只判断一次,while是只要唤醒就要拉回来再判断一次。if换成while

(1)代码

package com.atguigu.thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.omg.IOP.Codec;
/*
 * * 
 * 2 多线程变成模板(套路)-----下
 *     2.1  判断
 *     2.2  干活
 *     2.3  通知
 * 3 防止虚假唤醒用while
 * */
class ShareData//资源类
{
  private int number = 0;//初始值为零的一个变量
 
  public synchronized void increment() throws InterruptedException 
  {
     //判断
     while(number!=0) {
       this.wait();
     }
     //干活
     ++number;
     System.out.println(Thread.currentThread().getName()+" \t "+number);
     //通知
     this.notifyAll();;
  }
  
  public synchronized void decrement() throws InterruptedException 
  {
     //判断
     while(number!=1) {
       this.wait();
     }
     //干活
     --number;
     System.out.println(Thread.currentThread().getName()+" \t "+number);
     //通知
     this.notifyAll();
  }
}
 
/**
 * 
 * @Description:
 *现在两个线程,
 * 可以操作初始值为零的一个变量,
 * 实现一个线程对该变量加1,一个线程对该变量减1,
 * 交替,来10轮。 
 *
 *  * 笔记:Java里面如何进行工程级别的多线程编写
 * 1 多线程变成模板(套路)-----上
 *     1.1  线程    操作    资源类  
 *     1.2  高内聚  低耦合
 * 2 多线程变成模板(套路)-----下
 *     2.1  判断
 *     2.2  干活
 *     2.3  通知
 */
public class NotifyWaitDemo
{
  public static void main(String[] args)
  {
     ShareData sd = new ShareData();
     new Thread(() -> {
 
       for (int i = 1; i <= 10; i++) {
          try {
            sd.increment();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
       }
     }, "A").start();
     
     new Thread(() -> {
 
       for (int i = 1; i <= 10; i++) {
          try {
            sd.decrement();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
       }
     }, "B").start();
     new Thread(() -> {
 
       for (int i = 1; i <= 10; i++) {
          try {
            sd.increment();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
       }
     }, "C").start();
     new Thread(() -> {
 
       for (int i = 1; i <= 10; i++) {
          try {
            sd.decrement();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
       }
     }, "D").start();
     
  }
} 

(2)原理图:

                         

6、java8新版实现

6.1 对标实现

              

6.2 Condition

        

class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 
 
   final Object[] items = new Object[100];
   int putptr, takeptr, count;
 
   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length)
         notFull.await();
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

代码:

package com.atguigu.thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.omg.IOP.Codec;
class ShareData{//资源类

  private int number = 0;//初始值为零的一个变量
 
  private Lock lock = new ReentrantLock();
  private Condition condition  = lock.newCondition(); 
   
  public  void increment() throws InterruptedException {
  
      lock.lock();
         try {
          //判断
          while(number!=0) {
            condition.await();
          }
          //干活
          ++number;
          System.out.println(Thread.currentThread().getName()+" \t "+number);
          //通知
          condition.signalAll();
     } catch (Exception e) {
       e.printStackTrace();
     } finally {
       lock.unlock();
     } 
  }
  
  
  public  void decrement() throws InterruptedException{
      
      lock.lock();
         try {
          //判断
          while(number!=1) {
            condition.await();
          }
          //干活
          --number;
          System.out.println(Thread.currentThread().getName()+" \t "+number);
          //通知
          condition.signalAll();
     } catch (Exception e) {
       e.printStackTrace();
     } finally {
       lock.unlock();
     }
  }
  
  /*public synchronized void increment() throws InterruptedException{
     //判断
     while(number!=0) {
       this.wait();
     }
     //干活
     ++number;
     System.out.println(Thread.currentThread().getName()+" \t "+number);
     //通知
     this.notifyAll();;
  }
  
  public synchronized void decrement() throws InterruptedException {
     //判断
     while(number!=1) {
       this.wait();
     }
     //干活
     --number;
     System.out.println(Thread.currentThread().getName()+" \t "+number);
     //通知
     this.notifyAll();
  }*/
}
 
/**
 * 
 * @Description:
 *现在两个线程,
 * 可以操作初始值为零的一个变量,
 * 实现一个线程对该变量加1,一个线程对该变量减1,
 * 交替,来10轮。 
 * @author xialei
 *
 *  * 笔记:Java里面如何进行工程级别的多线程编写
 * 1 多线程变成模板(套路)-----上
 *     1.1  线程    操作    资源类  
 *     1.2  高内聚  低耦合
 * 2 多线程变成模板(套路)-----下
 *     2.1  判断
 *     2.2  干活
 *     2.3  通知
 */
public class NotifyWaitDemo{
  public static void main(String[] args){

     ShareData sd = new ShareData();
     new Thread(() -> {
 
       for (int i = 1; i <= 10; i++) {
          try {
            sd.increment();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
       }
     }, "A").start();
     
     new Thread(() -> {
 
       for (int i = 1; i <= 10; i++) {
          try {
            sd.decrement();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
       }
     }, "B").start();
     new Thread(() -> {
 
       for (int i = 1; i <= 10; i++) {
          try {
            sd.increment();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
       }
     }, "C").start();
     new Thread(() -> {
 
       for (int i = 1; i <= 10; i++) {
          try {
            sd.decrement();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
       }
     }, "D").start();
  }
}

六、线程间定制化调用通信

1、例子:ThreadOrderAccess

2、线程-调用-资源类

3、判断-干活-通知

  1. 有顺序通知,需要有标识位
  2. 有一个锁Lock,3把钥匙Condition
  3. 判断标志位
  4. 输出线程名+第几次+第几轮
  5. 修改标志位,通知下一个

4、代码

package com.atguigu.thread;
 
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareResource {
  private int number = 1;//1:A 2:B 3:C 
  private Lock lock = new ReentrantLock();
  private Condition c1 = lock.newCondition();
  private Condition c2 = lock.newCondition();
  private Condition c3 = lock.newCondition();
 
  public void print5(int totalLoopNumber)  {
     lock.lock();
     try {
       //1 判断
       while(number != 1){
          //A 就要停止
          c1.await();
       }
       //2 干活
       for (int i = 1; i <=5; i++) {
          System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoopNumber: "+totalLoopNumber);
       }
       //3 通知
       number = 2;
       c2.signal();
     } catch (Exception e) {
       e.printStackTrace();
     } finally {
       lock.unlock();
     }
  }
  public void print10(int totalLoopNumber)  {
     lock.lock();
     try{
       //1 判断
       while(number != 2)
       {
          //A 就要停止
          c2.await();
       }
       //2 干活
       for (int i = 1; i <=10; i++) {
          System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoopNumber: "+totalLoopNumber);
       }
       //3 通知
       number = 3;
       c3.signal();
     } catch (Exception e) {
       e.printStackTrace();
     } finally {
       lock.unlock();
     }
  }  
  
  public void print15(int totalLoopNumber)  {
     lock.lock();
     try {
       //1 判断
       while(number != 3){
          //A 就要停止
          c3.await();
       }
       //2 干活
       for (int i = 1; i <=15; i++) {
          System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoopNumber: "+totalLoopNumber);
       }
       //3 通知
       number = 1;
       c1.signal();
     } catch (Exception e) {
       e.printStackTrace();
     } finally {
       lock.unlock();
     }
  }  
}
 
/**
 * 
 * @Description: 
 * 多线程之间按顺序调用,实现A->B->C
 * 三个线程启动,要求如下:
 * 
 * AA打印5次,BB打印10次,CC打印15次
 * 接着
 * AA打印5次,BB打印10次,CC打印15次
 * ......来10轮  
 * @author xialei
 *
 */
public class ThreadOrderAccess {
  public static void main(String[] args) {
     ShareResource sr = new ShareResource();
     
     new Thread(() -> {
       for (int i = 1; i <=10; i++) {
          sr.print5(i);
       }
     }, "AA").start();
     new Thread(() -> {
       for (int i = 1; i <=10; i++) {
          sr.print10(i);
       }
     }, "BB").start();
     new Thread(() -> {
       for (int i = 1; i <=10; i++) {
          sr.print15(i);
       }
     }, "CC").start();   
  }
}

七、多线程锁

1、例子:ThreadOrderAccess

2、锁的 8 个问题

  1. 标准访问,先打印短信还是邮件
  2. 停4秒在短信方法内,先打印短信还是邮件
  3. 普通的hello方法,是先打短信还是hello
  4. 现在有两部手机,先打印短信还是邮件
  5. 两个静态同步方法,1部手机,先打印短信还是邮件
  6. 两个静态同步方法,2部手机,先打印短信还是邮件
  7. 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
  8. 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件

 3、8 锁分析

A 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,
其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法
锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法

  • 加个普通方法后发现和同步锁无关
  • 换成两个对象后,不是同一把锁了,情况立刻变化。 

synchronized实现同步的基础Java中的每一个对象都可以作为锁。
具体表现为以下3种形式:

  1. 对于普通同步方法,锁是当前实例对象。
  2. 对于静态同步方法,锁是当前类的Class对象。
  3. 对于同步方法块,锁是Synchonized括号里配置的对象
当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。

也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。

所有的静态同步方法用的也是同一把锁——类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。

但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!

4、代码

package com.atguigu.thread;
import java.util.concurrent.TimeUnit;
 
class Phone{
 
 public  synchronized void sendSMS() throws Exception {
   
   System.out.println("------sendSMS");
 }
 public synchronized void sendEmail() throws Exception {
   System.out.println("------sendEmail");
 }
 
 public void getHello()  {
   System.out.println("------getHello");
 }
}
 
/**
 * @Description: 8锁
 * @author xialei
 * 
 1 标准访问,先打印短信还是邮件
 2 停4秒在短信方法内,先打印短信还是邮件
 3 新增普通的hello方法,是先打短信还是hello
 4 现在有两部手机,先打印短信还是邮件
 5 两个静态同步方法,1部手机,先打印短信还是邮件
 6 两个静态同步方法,2部手机,先打印短信还是邮件
 7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
 8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
 * --------------------------------- 
 */
public class Lock_8 {
 public static void main(String[] args) throws Exception {
 
   Phone phone = new Phone();
   Phone phone2 = new Phone();
   
   new Thread(() -> {
    try {
     phone.sendSMS();
    } catch (Exception e) {
     e.printStackTrace();
    }
   }, "AA").start();
   
   Thread.sleep(100);
   
   new Thread(() -> {
    try {
     phone.sendEmail();
     //phone.getHello();
     //phone2.sendEmail();
    } catch (Exception e) {
     e.printStackTrace();
    }
   }, "BB").start();
 }
}
 

八、JUC强大的辅助类讲解

1、ReentrantReadWriteLock 读写锁

该类用于解决 写写、读写互斥的场景下,当进行写操作时,不进行读操作和其他的写操作;进行读操作时不进行写操作;确保数据 的一致性和梯次读取到数据的一致性。

(1)例子:ReadWriteLockDemo

(2)类似软件: 红蜘蛛

(3)代码:

package com.atguigu.thread; 
import java.util.concurrent.locks.ReentrantReadWriteLock;
 
class MyQueue{ 
 
 private Object obj;
 private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
 
 public void readObj(){
   rwLock.readLock().lock();
   try {
    System.out.println(Thread.currentThread().getName()+"\t"+obj);
   } catch (Exception e) {
    e.printStackTrace();
   } finally {
    rwLock.readLock().unlock();
   }
 }
 
 public void writeObj(Object obj){
   rwLock.writeLock().lock();
   try {
    this.obj = obj;
    System.out.println(Thread.currentThread().getName()+"writeThread:\t"+obj);
   } catch (Exception e) {
    e.printStackTrace();
   } finally {
    rwLock.writeLock().unlock();
   }
 } 
}
 
/**
 * 
 * @Description: 一个线程写入,100个线程读取
 * @author xialei
 * 
 */
public class ReadWriteLockDemo{
 public static void main(String[] args) throws InterruptedException {
   MyQueue q = new MyQueue();
   
   new Thread(() -> {
    q.writeObj("ClassName1221");
   }, "AAAAA").start();
      
   for (int i = 1; i <=100; i++) {
    new Thread(() -> {
     q.readObj();
    },String.valueOf(i)).start();
   }   
 }
}

2、CountDownLatch

(1)例子:CountDownLatchDemo

(2)原理:

  •  CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞。
  •  其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),
  •  当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。

(3)代码:

package com.atguigu.thread;
import java.util.concurrent.CountDownLatch;
 
/**
 * @Description:
 *  *让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒。
 * 
 * CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞。
 * 其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),
 * 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。
 * 
 * 解释:6个同学陆续离开教室后值班同学才可以关门。
 * 
 * main主线程必须要等前面6个线程完成全部工作后,自己才能开干 
 */
public class CountDownLatchDemo{
   public static void main(String[] args) throws InterruptedException   {
         CountDownLatch countDownLatch = new CountDownLatch(6);
       
       for (int i = 1; i <=6; i++){ //6个上自习的同学,各自离开教室的时间不一致
       
          new Thread(() -> {
              System.out.println(Thread.currentThread().getName()+"\t 号同学离开教室");
              countDownLatch.countDown();
          }, String.valueOf(i)).start();
       }
       countDownLatch.await();
       System.out.println(Thread.currentThread().getName()+"\t****** 班长关门走人,main线程是班长");
          
   }
}

3、CyclicBarrier 循环栅栏

(1)例子: CountDownLatchDemo

(2)原理:

  •  CyclicBarrier
  •  的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,
  •  让一组线程到达一个屏障(也可以叫同步点)时被阻塞,
  •  直到最后一个线程到达屏障时,屏障才会开门,所有
  •  被屏障拦截的线程才会继续干活。
  •  线程进入屏障通过CyclicBarrier的await()方法。

(3)代码

package com.atguigu.thread;
 
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
 
/**
 * 
 * @Description: TODO(这里用一句话描述这个类的作用)  
 *
 * CyclicBarrier
 * 的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,
 * 让一组线程到达一个屏障(也可以叫同步点)时被阻塞,
 * 直到最后一个线程到达屏障时,屏障才会开门,所有
 * 被屏障拦截的线程才会继续干活。
 * 线程进入屏障通过CyclicBarrier的await()方法。
 * 
 * 集齐7颗龙珠就可以召唤神龙
 */
public class CyclicBarrierDemo {
  private static final int NUMBER = 7;
  
  public static void main(String[] args) {
     //CyclicBarrier(int parties, Runnable barrierAction) 
     
     CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, ()->{System.out.println("*****集齐7颗龙珠就可以召唤神龙");}) ;
     
     for (int i = 1; i <= 7; i++) {
       new Thread(() -> {
          try {
            System.out.println(Thread.currentThread().getName()+"\t 星龙珠被收集 ");
            cyclicBarrier.await();
          } catch (InterruptedException | BrokenBarrierException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }
       
       }, String.valueOf(i)).start();
     }
  }
}

4、Semaphore 信号灯

(1)例子:CountDownLatchDemo

(2)原理:

在信号量上我们定义两种操作:

  •  acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),
  •  要么一直等下去,直到有线程释放信号量,或超时。
  •  release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
  •  信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。

(3)代码

package com.atguigu.thread;
 
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
 
/**
 * 
 * @Description: TODO(这里用一句话描述这个类的作用)  
 * @author xialei
 * 
 * 在信号量上我们定义两种操作:
 * acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),
 *             要么一直等下去,直到有线程释放信号量,或超时。
 * release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
 * 
 * 信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
 */
public class SemaphoreDemo {
  public static void main(String[] args)  {
     Semaphore semaphore = new Semaphore(3);//模拟3个停车位
     
     for (int i = 1; i <=6; i++) { //模拟6部汽车
    
       new Thread(() -> {
          try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName()+"\t 抢到了车位");
            TimeUnit.SECONDS.sleep(new Random().nextInt(5));
            System.out.println(Thread.currentThread().getName()+"\t------- 离开");
          } catch (InterruptedException e) {
            e.printStackTrace();
          }finally {
            semaphore.release();
          }
       }, String.valueOf(i)).start();
     }     
  }
}
posted @ 2020-02-11 19:33  维宇空灵  阅读(988)  评论(0编辑  收藏  举报