多线程和锁

synchronized与Lock的区别

1,首先synchronized是java内置关键字,在jvm层面,Lock是个java类

2,synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;

3,synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;

4,Lock可以让等待锁的线程发生响应中断,而synchronized却不行,它会让等待线程一直等待下去。

5,synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可中断、公平。

6,Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

synchronized和volatile的区别

1.volatile仅能使用在变量级别,synchronized则可以使用在变量、方法、类级别上。

2.volatile仅仅能实现变量修改可见性,并不能保证原子性,synchronized可以实现变量的修改可见性和原子性。

3.volatile不会造成线程阻塞,synchronized可能会造成线程阻塞。

4.volatile标记的变量不会被编译器优化,synchronized标记的变量可以被编译器优化。

为什么进程的切换比线程的代价大?

1、最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出

2,另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说,一旦去切换上下文,处理器中所有已经缓存的内存地址一瞬间都作废了。还有一个显著的区别是当你改变虚拟内存空间的时候,处理的页表缓冲(processor's Translation Lookaside Buffer (TLB))或者相当的神马东西会被全部刷新,这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中,不会出现这个问题。

为什么ConcurrentHashmap(1.8)的读操作不需要加锁?

在jdk1.7中是采用Segment + HashEntry + ReentrantLock的方式进行实现的,而1.8中放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现。

  • JDK1.8的实现降低锁的粒度,JDK1.7版本锁的粒度是基于Segment的,包含多个HashEntry,而JDK1.8锁的粒度就是HashEntry(首节点)
  • JDK1.8版本的数据结构变得更加简单,使得操作也更加清晰流畅,因为已经使用synchronized来进行同步,所以不需要分段锁的概念,也就不需要Segment这种数据结构了,由于粒度的降低,实现的复杂度也增加了
  • JDK1.8使用红黑树来优化链表,基于长度很长的链表的遍历是一个很漫长的过程,而红黑树的遍历效率是很快的,代替一定阈值的链表,这样形成一个最佳拍档。

那么,在源码中,get操作是没有加锁的,那么是如何保障读到的数据不是脏数据呢?

  get操作可以无锁是由于Node的元素val和指针next是用volatile修饰的,在多线程环境下线程A修改结点的val或者新增节点的时候是对线程B可见的

死锁产生的原因

1,系统资源不足时,竞争不可剥夺资源引起进程死锁

2,竞争临时资源引起死锁

3,进程推进顺序不当引起死锁

死锁的预防

破坏四个条件之一即可

死锁的避免

死锁避免的基本思想:系统对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配,这是一种保证系统不进入死锁状态的动态策略。 

1,银行家算法

2、避免一个线程同时获取多个锁;

3、避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。

4、尝试使用定时锁,使用Lock.tryLock(timeout)来替代使用内部锁机制。

5、对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

死锁的解除

1,释放所有锁,回退,并且等待一段随机的时间后重试。

2,给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级。

线程的优先级

1,在java中线程优先级分为1~10,默认的优先级是5,如果小于1或者大于10,则jdk报illegalArgumentException()异常。

2,设置线程优先级使用setPriority()方法

3,线程优先级具有继承性。a线程启动b线程,b线程的优先级和a线程的优先级是一样的

4,java 中有两种线程:用户线程和守护线程。可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。

5,优先级高的线程并不一定先执行完,而是优先获得系统资源。

在优先级调度环境下,线程优先级的改变有三种方式:

1. 用户指定优先级;

2. 根据进入等待状态的频繁程度提升或降低优先级(由操作系统完成);

3. 长时间得不到执行而被提升优先级。

线程通信的方式

wait / notify  volatile  CountDownLatch  CyclicBarrier  while轮询  synchronized  管道(java.io.PipedInputStream/java.io.PipedOutputStream)

进程通信的方式

1,管道/命名管道

  管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。

2,信号(java中的 wait / notify)

  信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的.

3,消息队列

4,共享内存

5,socket

6,信号量

 线程同步的方法

1,synchronized同步方法

2,(代码块同步)同步是一种高开销的操作,因此应该尽量减少同步的内容,通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

3,使用重入锁实现线程同步,ReentrantLock类是可重入、互斥、实现了Lock接口的锁

4,wait / notify 方法

5,使用volatile关键字。

6,使用局部变量实现线程同步。如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本。

7,使用原子变量实现线程同步,比如AutomicInteger(乐观锁)。

8,使用阻塞队列实现线程同步(BlockingQueue)。

java中线程的中断

在以前的jdk版本中,我们使用stop方法中断线程,但是现在的jdk版本中已经不再推荐使用该方法了,反而由以下三个方法完成对线程中断的支持。

public boolean isInterrupted()
public void interrupt()
public static boolean interrupted()

每个线程都一个状态位用于标识当前线程对象是否是中断状态。

isInterrupted 是一个实例方法,主要用于判断当前线程对象的中断标志位是否被标记了,如果被标记了则返回true表示当前已经被中断,否则返回false。

interrupt是一个实例方法,该方法用于设置当前线程对象的中断标识位。

interrupted是一个静态的方法,用于返回当前线程是否被中断。

线程一共6种状态,分别是NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED。下面我们看看,中断操作对不同状态下线程的影响。

1,new和terminated状态的线程对于中断是屏蔽的,也就是说中断操作对这两种状态下的线程是无效的。

2,RUNNBALE 状态下的线程遇到中断操作,也只会设置中断标志位并不会实际中断线程运行。

3,BLOCKED状态,执行中断操作之后,该线程仍然处于BLOCKED状态,但是中断标志位却已被修改。

4,WAITING/TIMED_WAITING 状态下,线程遇到中断操作的时候,会抛出一个InterruptedException异常,并清空中断标志位。

虽然有人质疑Java没有提供抢占式的中断机制,但是开发人员通过处理中断异常的方法,可以定制更为灵活的中断策略,从而在响应性和健壮性之间做出合理的平衡。

Java反射机制及原理

1 Class<?> clazz = Class.forName("java.util.ArrayList");
2 ArrayList object = (ArrayList) clazz.newInstance();
3 Method method = clazz.getMethod("add",Object.class);
4 method.invoke(list , "sss");

上面就是我们最常见的反射的例子,前两行实现了类的装载、链接(验证、准备、解析)、初始化(newInstance其实也是通过反射调用类的<init>方法),后面两行实现了从class对象中获取对象然后执行反射调用。

设想,假如我们要实现Invoke方法,是不是只要实现如下类即可。

1 public class Method {
2     public void invoke(Object obj , Object... args) {
3         ArrayList list = (ArrayList)obj;
4         list.add(args);
5     }
6 }

原理 1,就是动态的生成类似于上述的字节码,加载到JVM中运行

   2,获取Method对象流程。

上面的Class对象是在加载类时由JVM构造的,JVM为每个类管理一个独一无二的CLASS对象,这份CLASS对象里维护着该类的所有Method,Field,Constructor的cache,这份cache也可以称作为根对象。每次getMethod获取到的Method对象都持有对根对象的引用,因为一些重量级的Method的成员变量(主要是MethodAccessor),我们不希望每次创建Method对象都要重新初始化,于是所有代表同一个方法的Method对象都共享着根对象的MethodAccessor,每一次创建都会调用根对象的copy方法复制一份。
1 Method copy() { 
2         Method res = new Method(clazz, name, parameterTypes, returnType,
3                                 exceptionTypes, modifiers, slot, signature,
4                                 annotations, parameterAnnotations, annotationDefault);
5         res.root = this;
6         res.methodAccessor = methodAccessor;
7         return res;
8     }

  3,调用invoke方法流程。method.invoke方法会首先获取一个MethodAccessor,首先会从Method的根对象中获取MethodAccessor,如果为空,reflectionFactory.newMethodAccessor返回DelegatingMethodAccessorImpl实例,然后将MethodAccessor赋值给Method的root对象中(因为MethodAccessor是所有Method共用的),然后调用DelegatingMethodAccessorImpl中的invoke方法,当调用invoke的次数大于15次以后,MethodAccessor将由java代码生成。

原文地址:https://www.cnblogs.com/xueyunqing/p/11070770.html