java-多线程

多线程

synchronized

1、synchronized关键字

  1. 简介

    解决多个线程之间访问资源的同步性。保证被他修饰的方法或者是代码块在任意时刻只能有一个线程执行。

    1.6 之前依赖底层的操作系统的来实现的,java线程映射到操作系统的原生线程之上的。原生切换线程,需要从用户态转成内核态要花费挺长的时间。

    1.6之后,JVM层进行优化,所得效率也优化了很多。关于锁的实现也引入了大量的优化,降低了锁操作的开销。

  2. 如何使用?项目什么地方用

    1. 三种使用方式:

      1. 修饰实例方法,作用于当前对象实例,加锁,进入同步代码前要获得当前对象实例的锁

      2. 修饰静态方法,作用域当前类对象加锁,计入同步代码前获得当前类对象的锁。

      • 访问静态synchronized方法,占用的锁是类的锁,
      • 访问非静态 synchronized方法占用个的锁是对象的锁,
      • 这两个不会互斥
      1. 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的

2.原理

  1. 同步语句块的情况
    public class SynchronizedDemo{
        public void method(){
            synchronized(this){
                System.out.println("Ssynchroized 代码块");
            }
        }

    }

javac SynchronizedDemo.java
产生SynchronizedDemo.class

javap -c -s -v- l SynchronizedDemo.class

      public void method();
         descriptor: ()V
         flags: ACC_PUBLIC
         Code:
           stack=2, locals=3, args_size=1
              0: aload_0
              1: dup
              2: astore_1
              3: monitorenter
              4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
              7: ldc           #3                  // String Ssynchroized 代码块
              9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
             12: aload_1
             13: monitorexit
             14: goto          22
             17: astore_2
             18: aload_1
             19: monitorexit
             20: aload_2
             21: athrow
             22: return

  • 注意上边的monitorenter 这个就是为了获取monitor,本来计数器是0 然后获取后就会变成1,后续的继要获取的时候就会获取失败,直到另一个线程释放为止。
  • 注意上边的monitorexit 释放线程,计数器重新变成0 然后就可以有后续的操作
  1. synchrronized修饰方法

    public class SynchronizedDemo2{
      public synchronized void method(){
        System.out.println("synchronized 方法");
      }
    }
    

    操作步骤同上

      {
        public SynchronizedDemo2();
          descriptor: ()V
          flags: ACC_PUBLIC
          Code:
            stack=1, locals=1, args_size=1
               0: aload_0
               1: invokespecial #1                  // Method java/lang/Object."<init>":()V
               4: return
            LineNumberTable:
              line 1: 0
      
        public synchronized void method();
          descriptor: ()V
          flags: ACC_PUBLIC, ACC_SYNCHRONIZED
          Code:
            stack=2, locals=1, args_size=1
               0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
               3: ldc           #3                  // String synchronized 方法
               5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
               8: return
            LineNumberTable:
              line 3: 0
              line 4: 8
      }
      

见第15行,falges多个ACC_SUNCHRONIZED

3、概念

  1. 对象的锁,

    1. 如果 Synchronized 明确指定了锁对象,比如 Synchronized(变量

      名)、Synchronized(this) 等,说明加解锁对象为该对象。

    2. 如果没有明确指定:

      1. Synchronized 修饰的方法为非静态方法,表示此方法对应的对象为 锁对象
      2. Synchronized 修饰的方法为静态方法,则表示此方法对应的类对象 为锁对象。
  2. 1.6的优化

    1. 自旋锁:把线程进行阻塞操作之前先让线程自旋等待一段时间
    2. 三种不同的Monitor实现:(也就是不同的Monitor
      1. 偏向锁:没有竞争出现时,默认会使用偏向锁。也就是在对象头上的MarkWord部分设置线程ID,标示这个对象偏向于当前线程,这个时候没有互斥
      2. 轻量级锁:如果另一个线程试图锁定被偏斜的对象,JVM就撤销偏斜锁,切换到轻量级锁实现。
      3. 重量级锁:轻量级锁依赖CAS 操作 Mark Word 来试图获取锁,如果重试成功,
        就使用普通的轻量级锁;否则,进一步升级为重量级锁。
  3. Synchronized 非公平锁:

    ​ 获取锁的行为上,并不是先到先得,而是任何一个线程都有机会领到锁,所以叫非公平锁。

  4. 锁消除和锁粗化

    1. 锁消除:指虚拟机即时编译器在运行时,对一些代码上要求同步,但被
      检测到不可能存在共享数据竞争的锁进行消除。
    2. 锁粗化:就是增大锁的作用域。原则上应该同步块范围尽量的小,但是如果对同一块频繁的由同一个对象频繁的加锁解锁,也是浪费资源。
  5. 悲观锁/乐观锁/CAS

    1. 悲观锁:并发策略是悲观的:不管是否会被竞争,所有的数据操作都要求被加锁,用户态核心态转换,维护锁计数器,检查是否有被阻塞线程需要被唤醒灯操作。
    2. 乐观锁:基于冲突检测的乐观并发策略。先进行操作,如没有其他线征用数据就没问题。如果产生了冲突,进行其他补偿措施,也叫做非阻塞同步。
    3. 乐观锁的核心算法是 CAS:内存值、预期值、新值。当且仅当预期值和内存值相
      等时才将内存值修改为新值。
  6. 乐观锁的优劣

    1. 只能保证一个共享变量的原子操作
    2. 长时间自旋可能导致开销大
    3. ABA 问题
  7. ReentrantLock可重入锁/Synchronized 对比

    1. 锁的实现原理基本是为了达到一个目的:让所有的线程都能看到某种标记
    2. Synchronized:对象头中设置标记
    3. ReentrantLock/所有的基于 Lock 接口的实现类:用一个 volitile 修饰的 int 型变量,并保证每个线
      程都能拥有对该 int 的可见性和原子修改
    4. ReentrantLock有比synchorized多的功能
      1. 等待可中断:长期不释放锁的时候,正在等待的线程可以选择放弃等待 lock.lockInterruptibly()
      2. 带超时的获取锁尝试:指定的时间范围内获取锁
      3. 可以判断是否有线程在排队等待获取锁。
      4. 可以响应中断请求:与 Synchronized 不同,当获取到锁的线程被中 断时,能够响应中断,中断异常将会被抛出,同时锁会被释放。
      5. 可以实现公平锁。(ReentrantLock 既能够实现公平锁,也能够实现非公平锁,通过构造方里的参数来进行控制的)
      6. 在竞争不激烈时,Synchronized 的性能要优于 ReetrantLock;在高竞争情况下,Synchronized 的性能会下降几十倍

4、ReentrantLock使用

  1. 普通使用
package com.synchronizedDemo;

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest extends Thread {
    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;

    public ReentrantLockTest(String name) {
        super.setName(name);
    }

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            lock.lock();
            try {
                System.out.println(this.getName() + " " + i);
                i++;
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReentrantLockTest test1 = new ReentrantLockTest("thread1");
        ReentrantLockTest test2 = new ReentrantLockTest("thread2");
        test1.start();
        test2.start();
        test1.join();
        test2.join();
        System.out.println(i);
    }
}

2、可中断

package com.synchronizedDemo;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.locks.ReentrantLock;

public class LockInterrupt extends Thread{
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    int lock;
    public LockInterrupt(int lock, String name) {
        super(name);
        this.lock = lock;
    }
    @Override
    public void run() {
        try {
            if(lock ==1) {
                lock1.lockInterruptibly();
                Thread.sleep(500);
                lock2.lockInterruptibly();
            }else if (lock ==2) {
                lock2.lockInterruptibly();
                Thread.sleep(500);
                lock1.lockInterruptibly();
            }
            
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if(lock1.isHeldByCurrentThread()) {
                lock1.unlock();
            }
            if(lock2.isHeldByCurrentThread()) {
                lock2.unlock();
            }
            System.out.println(Thread.currentThread().getId() + ":线程退出");
        }
        
    }
    public static void main(String[] args) throws InterruptedException {
        LockInterrupt t1 = new LockInterrupt(1, "LockInterrupt1");
        LockInterrupt t2 = new LockInterrupt(2, "LockInterrupt2");
        t1.start();
        t2.start();
        Thread.sleep(1000);

        DeadlockChecker.check();
    }
    static class DeadlockChecker{
         private final static ThreadMXBean mbean = ManagementFactory
                    .getThreadMXBean();
         public static void check() {
             Thread tt = new Thread(()->{
                 while(true) {
                     long[] deadlockedThreadIds = mbean.findDeadlockedThreads();
                     if (deadlockedThreadIds != null) {
                         ThreadInfo[] threadInfos = mbean.getThreadInfo(deadlockedThreadIds);
                         for (Thread t : Thread.getAllStackTraces().keySet()) {
                             for (int i = 0; i < threadInfos.length; i++) {
                                 if (t.getId() == threadInfos[i].getThreadId()) {
                                     System.out.println(t.getName());
                                     t.interrupt();//中断
                                 }
                             }
                         }
                     }
                 }
             });
             tt.setDaemon(true);
             tt.start();
         }
    }
}
  • 锁定的时候用lock1.lockInterruptibly();
  • 中断时候,找到这个线程,然后直接 t.interrupt();

3、超时中断(代码太多,和上边比较类似,就是锁定的时候显示)

lock.tryLock(5, TimeUnit.SECONDS)

4、公平锁,初始化锁的时候,

public static ReentrantLock fairLock = new ReentrantLock(true);

5、JUC/AQS

  1. AQS 框架
    1. 一个用来构建锁和同步器的框架
    2. 内部定义了一个 volatile int state 变量,表示同步状态;state =0的时候表示没有线程占用,可以获得锁,同时将state=1 .如过state=1表示其他锁正在用,加入同步队列
    3. 通过 Node 内部类构成的一个双向链表结构的同步队列
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值