JUC并发编程(四)-Synchronized、ReenTrantLock、线程池

Synchronized原理

使用

对于普通方法: 锁住的是当前实例对象
对于静态方法: 锁住的是当前类的class对象
对于静态代码块: 锁住的是括号里面的配置对象

原理

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

    }
    public void test(){
    
    
        synchronized (Test.class){
    
    

        }
    }
}

执行javap -c class文件

public class com.example.demothreadpool.Test {
    
    
  public com.example.demothreadpool.Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: return

  public void test();
    Code:
       0: ldc           #2                  // class com/example/demothreadpool/Test
       2: dup
       3: astore_1
       4: monitorenter
       5: aload_1
       6: monitorexit
       7: goto          15
      10: astore_2
      11: aload_1
      12: monitorexit
      13: aload_2
      14: athrow
      15: return
    Exception table:
       from    to  target type
           5     7    10   any
          10    13    10   any
}

通过反编译我们看到了两个字节码:monitorenter、monitorexit,代码块的同步是利用monitorenter和monitorexit这两个字节码指令。它们分别位于同步代码块的开始和结束位置。当jvm执行到monitorenter指令时,当前线程试图获取monitor对象的所有权,如果未加锁或者已经被当前线程所持有,就把锁的计数器+1;当执行monitorexit指令时,锁计数器-1;当锁计数器为0时,该锁就被释放了。如果获取monitor对象失败,该线程则会进入阻塞状态,直到其他线程释放锁。

ReenTrantLock使用

ReenTrantLock(boolean fair)
ReenTrantLock有两个构造方法,一个空参,另一个ReenTrantLock(boolean fair)当参数fair=true时,表示构造一个公平锁。synchronized是非公平锁,公平锁会按时间顺序,先唤醒等待队列中等待的线程。公平锁的一大特点就是不会产生饥饿现象。但是,由于实现公平锁要求系统维护一个有序队列,因此实现公平锁的成本很高,性能也相对低下。因此,默认情况锁是非公平的。

lock()
获得锁,如果锁已经被占用,则等待

unlock()
释放锁

tryLock()(获取不败不会等待)
尝试获得锁,如果成功放回true,失败返回false,该方法不等待,立即返回,获取失败,等待指定时间后也获取失败,则不会等待通过lock.tryLock()方法来实现。如果其他线程占有锁,则当前线程不会等待,立即返回false。tryLock(long ,TimeUnit),tryLock方法既可以接受无参调用,还可以接受参数,第一个参数等待时长,第二个表示计时单位。如果超过等待时长还没有获取到锁,会停止等待并返回false。

lockInterruptibly()
获得锁,但优先响应中断。如果当前线程未被中断,则获取锁

newCondition()
返回与lock实例一起使用的Condition对象
使用ReentrantLock类的newCondition()方法可以获取Condition对象
需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了,而synchronized是随机唤醒线程的
Condition接口:此接口中封装了await(),signal(),signalAll(),方法。代替了原来锁中的wait(),notify(),notifyAll()监视器方法

Synchronized、ReenTrantLock区别

第一
synchronized 它是jvm层面实现的(jdk1.6进行了优化)
ReentrantLock是jdk层面实现的(jdk1.5引入)
Synchronized释放锁,被动释放: 方法执行结束、异常
ReentrantLock 可以手动灵活释放和获得锁

第二
Synchronized可以修饰在方法层面、或者代码块层面
Lock只能写在代码块中

第三
Synchronized只能实现非公平锁
Lock可以实现非公平和公平锁

线程池Executors

相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。

Java通过Executors提供四种线程池,分别为
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

案例

/**
* 创建固定长度的线程池
*/
public class FixedThreadPool implements Runnable{
    
    
    public static void main(String[] args) {
    
    
        ExecutorService service = Executors.newFixedThreadPool(3);
        for(int i=0;i<10;i++){
    
    
            service.execute(new FixedThreadPool());
        }
    }


    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName()+"ok");
    }
}

在这里插入图片描述
newSingleThreadExecutor

/**
* 创建单一线程池
*/
public class SingleThreadExecutor implements Runnable{
    
    
    public static void main(String[] args) {
    
    
        ExecutorService service = Executors.newSingleThreadExecutor();
        for(int i=0;i<10;i++){
    
    
            service.execute(new SingleThreadExecutor());
        }
    }


    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName()+"执行了");
    }
}

在这里插入图片描述
newCachedThreadPool

/**
* 缓存线程池,可伸展
*/
public class CachedThreadPool implements Runnable{
    
    
    public static void main(String[] args) {
    
    
        ExecutorService service = Executors.newCachedThreadPool();
        for(int i=0;i<10;i++){
    
    
            service.execute(new CachedThreadPool());
            if(i==5){
    
    
                try {
    
    
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }


    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName()+"执行");
    }
}

在这里插入图片描述
newScheduledThreadPool

public class ScheduledThreadPool implements Runnable{
    
        
    public static void main(String[] args) {
    
     
       ScheduledExecutorService service = Executors.newScheduledThreadPool(5);       
       for(int i=0;i<10;i++){
    
              
         //service.schedule(new  ScheduledThreadPool(),2,TimeUnit.SECONDS);延迟两秒执行            
         //延迟两秒后,每3秒执行一次            
         service.scheduleAtFixedRate(new ScheduledThreadPool(),2,3,TimeUnit.SECONDS);      
   }   
}    
 @Override    public void run() {
    
        
     System.out.println(Thread.currentThread().getName()+"执行");    
  }
}

在这里插入图片描述

自定义线程池

Executors线程池弊端

Executors返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致大量资源被占用。
2)CachedThreadPool 和 ScheduledThreadPool :
允许创建的线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致大量资源被占用。

自定义线程池

newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
    
    
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
    
    
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

通过上面的两个源码可以看出:java给我们提供的线程池底层就是new ThreadPoolExecutor的实现,因此我们可以自己去实现new ThreadPoolExecutor来自定义我们的线程池

构造一个线程池
参数:
corePoolSize 核心线程池大小
maximumPoolSize 线程池最大容量大小
keepAliveTime 线程池空闲时,线程存活的时间
TimeUnit 时间单位
最后一位 阻塞队列

构建一个核心线程池大小为5,线程池最大容量大小为6, 线程池空闲时,线程存活的时间3秒,阻塞队列长度为3的线程池

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 6, 3,
       TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3)
);

JDK 7 提供了7个阻塞队列,如下
1、ArrayBlockingQueue 数组结构组成的有界阻塞队列。
此队列按照先进先出(FIFO)的原则对元素进行排序,但是默认情况下不保证线程公平的访问队列,即如果队列满了,那么被阻塞在外面的线程对队列访问的顺序是不能保证线程公平(即先阻塞,先插入)的。
2、LinkedBlockingQueue一个由链表结构组成的有界阻塞队列
此队列按照先出先进的原则对元素进行排序
3、PriorityBlockingQueue支持优先级的无界阻塞队列
4、DelayQueue支持延时获取元素的无界阻塞队列,即可以指定多久才能从队列中获取当前元素
5、SynchronousQueue不存储元素的阻塞队列,每一个put必须等待一个take操作,否则不能继续添加元素。并且他支持公平访问队列。
6、LinkedTransferQueue由链表结构组成的无界阻塞TransferQueue队列。
7、LinkedBlockingDeque链表结构的双向阻塞队列,优势在于多线程入队时,减少一半的竞争。

猜你喜欢

转载自blog.csdn.net/weixin_42371621/article/details/110189578
今日推荐