网站开发哪家/免费引流人脉推广软件

网站开发哪家,免费引流人脉推广软件,网站客户运营,宁波妇科医院私立哪家医院好目录 前言 1. SimpleDateFormat线程不安全 2. 双重检查锁的漏洞 3. volatile的原子性 4. 死锁 4.1 缩小锁的范围 4.2 保证锁的顺序 5. 没释放锁 6. HashMap导致内存溢出 7. 使用默认线程池 8. Async注解的陷阱 9. 自旋锁浪费cpu资源 10. ThreadLocal用完没清空 前…

目录

前言

1. SimpleDateFormat线程不安全

2. 双重检查锁的漏洞

3. volatile的原子性

4. 死锁

4.1 缩小锁的范围

4.2 保证锁的顺序

5. 没释放锁

6. HashMap导致内存溢出

7. 使用默认线程池

8. @Async注解的陷阱

9. 自旋锁浪费cpu资源

10. ThreadLocal用完没清空


前言

对于从事后端开发的同学来说,并发编程肯定再熟悉不过了。

说实话,在java中并发编程是一大难点,至少我是这么认为的。不光理解起来比较费劲,使用起来更容易踩坑。

不信,让继续往下面看。

今天重点跟大家一起聊聊并发编程的10个坑,希望对你有帮助。

1. SimpleDateFormat线程不安全

在java8之前,我们对时间的格式化处理,一般都是用的SimpleDateFormat类实现的。例如:

@Service
public class SimpleDateFormatService {public Date time(String time) throws ParseException {SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return dateFormat.parse(time);}
}

如果你真的这样写,是没问题的。

就怕哪天抽风,你觉得dateFormat是一段固定的代码,应该要把它抽取成常量。

于是把代码改成下面的这样:

@Service
public class SimpleDateFormatService {private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public Date time(String time) throws ParseException {return dateFormat.parse(time);}
}

dateFormat对象被定义成了静态常量,这样就能被所有对象共用。

如果只有一个线程调用time方法,也不会出现问题。

但Serivce类的方法,往往是被Controller类调用的,而Controller类的接口方法,则会被tomcat线程池调用。换句话说,可能会出现多个线程调用同一个Controller类的同一个方法,也就是会出现多个线程会同时调用time方法的情况。

而time方法会调用SimpleDateFormat类的parse方法:

@Override
public Date parse(String text, ParsePosition pos) {...Date parsedDate;try {parsedDate = calb.establish(calendar).getTime();...} catch (IllegalArgumentException e) {pos.errorIndex = start;pos.index = oldStart;return null;}return parsedDate;
} 

该方法会调用establish方法:

Calendar establish(Calendar cal) {...//1.清空数据cal.clear();//2.设置时间cal.set(...);//3.返回return cal;
}

其中的步骤1、2、3是非原子操作。

但如果cal对象是局部变量还好,坏就坏在parse方法调用establish方法时,传入的calendar是SimpleDateFormat类的父类DateFormat的成员变量:

public abstract class DateFormat extends Forma {....protected Calendar calendar;...
}

这样就可能会出现多个线程,同时修改同一个对象即:dateFormat,他的同一个成员变量即:Calendar值的情况。

这样可能会出现,某个线程设置好了时间,又被其他的线程修改了,从而出现时间错误的情况。

那么,如何解决这个问题呢?

  1. SimpleDateFormat类的对象不要定义成静态的,可以改成方法的局部变量。

  2. 使用ThreadLocal保存SimpleDateFormat类的数据。

  3. 使用java8的DateTimeFormatter类。

2. 双重检查锁的漏洞

单例模式无论在实际工作,还是在面试中,都出现得比较多。

我们都知道,单例模式有:饿汉模式懒汉模式两种。

饿汉模式代码如下:

public class SimpleSingleton {//持有自己类的引用private static final SimpleSingleton INSTANCE = new SimpleSingleton();//私有的构造方法private SimpleSingleton() {}//对外提供获取实例的静态方法public static SimpleSingleton getInstance() {return INSTANCE;}
}

使用饿汉模式的好处是:没有线程安全的问题,但带来的坏处也很明显。

private static final SimpleSingleton INSTANCE = new SimpleSingleton();

一开始就实例化对象了,如果实例化过程非常耗时,并且最后这个对象没有被使用,不是白白造成资源浪费吗?

还真是啊。

这个时候你也许会想到,不用提前实例化对象,在真正使用的时候再实例化不就可以了?

这就是我接下来要介绍的:懒汉模式

具体代码如下:

public class SimpleSingleton2 {private static SimpleSingleton2 INSTANCE;private SimpleSingleton2() {}public static SimpleSingleton2 getInstance() {if (INSTANCE == null) {INSTANCE = new SimpleSingleton2();}return INSTANCE;}
}

示例中的INSTANCE对象一开始是空的,在调用getInstance方法才会真正实例化。

嗯,不错不错。但这段代码还是有问题。

假如有多个线程中都调用了getInstance方法,那么都走到 if (INSTANCE == null) 判断时,可能同时成立,因为INSTANCE初始化时默认值是null。这样会导致多个线程中同时创建INSTANCE对象,即INSTANCE对象被创建了多次,违背了只创建一个INSTANCE对象的初衷。

为了解决饿汉模式懒汉模式各自的问题,于是出现了:双重检查锁

具体代码如下:

public class SimpleSingleton4 {private static SimpleSingleton4 INSTANCE;private SimpleSingleton4() {}public static SimpleSingleton4 getInstance() {if (INSTANCE == null) {synchronized (SimpleSingleton4.class) {if (INSTANCE == null) {INSTANCE = new SimpleSingleton4();}}}return INSTANCE;}
}

需要在synchronized前后两次判空。

但我要告诉你的是:这段代码有漏洞的。

有什么问题?

public static SimpleSingleton4 getInstance() {if (INSTANCE == null) {//1synchronized (SimpleSingleton4.class) {//2if (INSTANCE == null) {//3INSTANCE = new SimpleSingleton4();//4}}}return INSTANCE;//5
}

getInstance方法的这段代码,我是按1、2、3、4、5这种顺序写的,希望也按这个顺序执行。

但是java虚拟机实际上会做一些优化,对一些代码指令进行重排。重排之后的顺序可能就变成了:1、3、2、4、5,这样在多线程的情况下同样会创建多次实例。重排之后的代码可能如下:

public static SimpleSingleton4 getInstance() {if (INSTANCE == null) {//1if (INSTANCE == null) {//3synchronized (SimpleSingleton4.class) {//2INSTANCE = new SimpleSingleton4();//4}}}return INSTANCE;//5
}

原来如此,那有什么办法可以解决呢?

答:可以在定义INSTANCE是加上volatile关键字。具体代码如下:

public class SimpleSingleton7 {private volatile static SimpleSingleton7 INSTANCE;private SimpleSingleton7() {}public static SimpleSingleton7 getInstance() {if (INSTANCE == null) {synchronized (SimpleSingleton7.class) {if (INSTANCE == null) {INSTANCE = new SimpleSingleton7();}}}return INSTANCE;}
}

volatile关键字可以保证多个线程的可见性,但是不能保证原子性。同时它也能禁止指令重排

双重检查锁的机制既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。

此外,如果你想了解更多单例模式的细节问题,可以看看我的另一篇文章《单例模式,真不简单》

3. volatile的原子性

从前面我们已经知道volatile,是一个非常不错的关键字,它能保证变量在多个线程中的可见性,它也能禁止指令重排,但是不能保证原子性

使用volatile关键字禁止指令重排,前面已经说过了,这里就不聊了。

可见性主要体现在:一个线程对某个变量修改了,另一个线程每次都能获取到该变量的最新值。

先一起看看反例:

public class VolatileTest extends Thread {private  boolean stopFlag = false;public boolean isStopFlag() {return stopFlag;}@Overridepublic void run() {try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}stopFlag = true;System.out.println(Thread.currentThread().getName() + " stopFlag = " + stopFlag);}public static void main(String[] args) {VolatileTest vt = new VolatileTest();vt.start();while (true) {if (vt.isStopFlag()) {System.out.println("stop");break;}}}
}

上面这段代码中,VolatileTest是一个Thread类的子类,它的成员变量stopFlag默认是false,在它的run方法中修改成了true。

然后在main方法的主线程中,用vt.isStopFlag()方法判断,如果它的值是true时,则打印stop关键字。

那么,如何才能让stopFlag的值修改了,在主线程中通过vt.isStopFlag()方法,能够获取最新的值呢?

正例如下:

public class VolatileTest extends Thread {private volatile boolean stopFlag = false;public boolean isStopFlag() {return stopFlag;}@Overridepublic void run() {try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}stopFlag = true;System.out.println(Thread.currentThread().getName() + " stopFlag = " + stopFlag);}public static void main(String[] args) {VolatileTest vt = new VolatileTest();vt.start();while (true) {if (vt.isStopFlag()) {System.out.println("stop");break;}}}
}

volatile关键字修饰stopFlag即可。

下面重点说说volatile的原子性问题。

使用多线程给count加1,代码如下:

public class VolatileTest {public volatile int count = 0;public void add() {count++;}public static void main(String[] args) {final VolatileTest test = new VolatileTest();for (int i = 0; i < 20; i++) {new Thread() {@Overridepublic void run() {for (int j = 0; j < 1000; j++) {test.add();}};}.start();}while (Thread.activeCount() > 2) {//保证前面的线程都执行完Thread.yield();}System.out.println(test.count);}
}

执行结果每次都不一样,但可以肯定的是count值每次都小于20000,比如:19999。

这个例子中count是成员变量,虽说被定义成了volatile的,但由于add方法中的count++是非原子操作。在多线程环境中,count++的数据可能会出现问题。

由此可见,volatile不能保证原子性

那么,如何解决这个问题呢?

答:使用synchronized关键字。

改造后的代码如下:

public class VolatileTest {public int count = 0;public synchronized void add() {count++;}public static void main(String[] args) {final VolatileTest test = new VolatileTest();for (int i = 0; i < 20; i++) {new Thread() {@Overridepublic void run() {for (int j = 0; j < 1000; j++) {test.add();}};}.start();}while (Thread.activeCount() > 2) {//保证前面的线程都执行完Thread.yield();}System.out.println(test.count);}
}

4. 死锁

死锁可能是大家都不希望遇到的问题,因为一旦程序出现了死锁,如果没有外力的作用,程序将会一直处于资源竞争的假死状态中。

死锁代码如下:

public class DeadLockTest {public static String OBJECT_1 = "OBJECT_1";public static String OBJECT_2 = "OBJECT_2";public static void main(String[] args) {LockA lockA = new LockA();new Thread(lockA).start();LockB lockB = new LockB();new Thread(lockB).start();}}class LockA implements Runnable {@Overridepublic void run() {synchronized (DeadLockTest.OBJECT_1) {try {Thread.sleep(500);synchronized (DeadLockTest.OBJECT_2) {System.out.println("LockA");}} catch (InterruptedException e) {e.printStackTrace();}}}
}class LockB implements Runnable {@Overridepublic void run() {synchronized (DeadLockTest.OBJECT_2) {try {Thread.sleep(500);synchronized (DeadLockTest.OBJECT_1) {System.out.println("LockB");}} catch (InterruptedException e) {e.printStackTrace();}}}
}

一个线程在获取OBJECT_1锁时,没有释放锁,又去申请OBJECT_2锁。而刚好此时,另一个线程获取到了OBJECT_2锁,也没有释放锁,去申请OBJECT_1锁。由于OBJECT_1和OBJECT_2锁都没有释放,两个线程将一起请求下去,陷入死循环,即出现死锁的情况。

那么如果避免死锁问题呢?

4.1 缩小锁的范围

出现死锁的情况,有可能是像上面那样,锁范围太大了导致的。

那么解决办法就是缩小锁的范围

具体代码如下:

class LockA implements Runnable {@Overridepublic void run() {synchronized (DeadLockTest.OBJECT_1) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}synchronized (DeadLockTest.OBJECT_2) {System.out.println("LockA");}}
}class LockB implements Runnable {@Overridepublic void run() {synchronized (DeadLockTest.OBJECT_2) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}synchronized (DeadLockTest.OBJECT_1) {System.out.println("LockB");}}
}

在获取OBJECT_1锁的代码块中,不包含获取OBJECT_2锁的代码。同时在获取OBJECT_2锁的代码块中,也不包含获取OBJECT_1锁的代码。

4.2 保证锁的顺序

出现死锁的情况说白了是,一个线程获取锁的顺序是:OBJECT_1和OBJECT_2。而另一个线程获取锁的顺序刚好相反为:OBJECT_2和OBJECT_1。

那么,如果我们能保证每次获取锁的顺序都相同,就不会出现死锁问题。

具体代码如下:

class LockA implements Runnable {@Overridepublic void run() {synchronized (DeadLockTest.OBJECT_1) {try {Thread.sleep(500);synchronized (DeadLockTest.OBJECT_2) {System.out.println("LockA");}} catch (InterruptedException e) {e.printStackTrace();}}}
}class LockB implements Runnable {@Overridepublic void run() {synchronized (DeadLockTest.OBJECT_1) {try {Thread.sleep(500);synchronized (DeadLockTest.OBJECT_2) {System.out.println("LockB");}} catch (InterruptedException e) {e.printStackTrace();}}}
}

两个线程,每个线程都是先获取OBJECT_1锁,再获取OBJECT_2锁。

5. 没释放锁

在java中除了使用synchronized关键字,给我们所需要的代码块加锁之外,还能通过Lock关键字加锁。

使用synchronized关键字加锁后,如果程序执行完毕,或者程序出现异常时,会自动释放锁。

但如果使用Lock关键字加锁后,需要开发人员在代码中手动释放锁。

例如:

public class LockTest {private final ReentrantLock rLock = new ReentrantLock();public void fun() {rLock.lock();try {System.out.println("fun");} finally {rLock.unlock();}}
}

代码中先创建一个ReentrantLock类的实例对象rLock,调用它的lock方法加锁。然后执行业务代码,最后再finally代码块中调用unlock方法。

但如果你没有在finally代码块中,调用unlock方法手动释放锁,线程持有的锁将不会得到释放。

6. HashMap导致内存溢出

HashMap在实际的工作场景中,使用频率还是挺高的,比如:接收参数,缓存数据,汇总数据等等。

但如果你在多线程的环境中使用HashMap,可能会导致非常严重的后果。

@Service
public class HashMapService {private Map<Long, Object> hashMap = new HashMap<>();public void add(User user) {hashMap.put(user.getId(), user.getName());}
}

在HashMapService类中定义了一个HashMap的成员变量,在add方法中往HashMap中添加数据。在controller层的接口中调用add方法,会使用tomcat的线程池去处理请求,就相当于在多线程的场景下调用add方法。

在jdk1.7中,HashMap使用的数据结构是:数组+链表。如果在多线程的情况下,不断往HashMap中添加数据,它会调用resize方法进行扩容。该方法在复制元素到新数组时,采用的头插法,在某些情况下,会导致链表会出现死循环。

死循环最终结果会导致:内存溢出

此外,如果HashMap中数据非常多,会导致链表很长。当查找某个元素时,需要遍历某个链表,查询效率不太高。

为此,jdk1.8之后,将HashMap的数据结构改成了:数组+链表+红黑树

如果同一个数组元素中的数据项小于8个,则还是用链表保存数据。如果大于8个,则自动转换成红黑树。

为什么要用红黑树?

答:链表的时间复杂度是O(n),而红黑树的时间复杂度是O(logn),红黑树的复杂度是优于链表的。

既然这样,为什么不直接使用红黑树?

答:树节点所占存储空间是链表节点的两倍,节点少的时候,尽管在时间复杂度上,红黑树比链表稍微好一些。但是由于红黑树所占空间比较大,HashMap综合考虑之后,认为节点数量少的时候用占存储空间更多的红黑树不划算。

jdk1.8中HashMap就不会出现死循环?

答:错,它在多线程环境中依然会出现死循环。在扩容的过程中,在链表转换为树的时候,for循环一直无法跳出,从而导致死循环。

那么,如果想多线程环境中使用HashMap该怎么办呢?

答:使用ConcurrentHashMap

7. 使用默认线程池

我们都知道jdk1.5之后,提供了ThreadPoolExecutor类,用它可以自定义线程池

线程池的好处有很多,比如:

  1. 降低资源消耗:避免了频繁的创建线程和销毁线程,可以直接复用已有线程。而我们都知道,创建线程是非常耗时的操作。

  2. 提供速度:任务过来之后,因为线程已存在,可以拿来直接使用。

  3. 提高线程的可管理性:线程是非常宝贵的资源,如果创建过多的线程,不仅会消耗系统资源,甚至会影响系统的稳定。使用线程池,可以非常方便的创建、管理和监控线程。

当然jdk为了我们使用更便捷,专门提供了:Executors类,给我们快速创建线程池。

该类中包含了很多静态方法:

  • newCachedThreadPool:创建一个可缓冲的线程,如果线程池大小超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

  • newFixedThreadPool:创建一个固定大小的线程池,如果任务数量超过线程池大小,则将多余的任务放到队列中。

  • newScheduledThreadPool:创建一个固定大小,并且能执行定时周期任务的线程池。

  • newSingleThreadExecutor:创建只有一个线程的线程池,保证所有的任务安装顺序执行。

在高并发的场景下,如果大家使用这些静态方法创建线程池,会有一些问题。

那么,我们一起看看有哪些问题?

  • newFixedThreadPool:允许请求的队列长度是Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。

  • newSingleThreadExecutor:允许请求的队列长度是Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。

  • newCachedThreadPool:允许创建的线程数是Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

那我们该怎办呢?

优先推荐使用ThreadPoolExecutor类,我们自定义线程池。

具体代码如下:

ExecutorService threadPool = new ThreadPoolExecutor(8, //corePoolSize线程池中核心线程数10, //maximumPoolSize 线程池中最大线程数60, //线程池中线程的最大空闲时间,超过这个时间空闲线程将被回收TimeUnit.SECONDS,//时间单位new ArrayBlockingQueue(500), //队列new ThreadPoolExecutor.CallerRunsPolicy()); //拒绝策略

顺便说一下,如果是一些低并发场景,使用Executors类创建线程池也未尝不可,也不能完全一棍子打死。在这些低并发场景下,很难出现OOM问题,所以我们需要根据实际业务场景选择。

8. @Async注解的陷阱

之前在java并发编程中实现异步功能,一般是需要使用线程或者线程池

线程池的底层也是用的线程。

而实现一个线程,要么继承Thread类,要么实现Runnable接口,然后在run方法中写具体的业务逻辑代码。

开发spring的大神们,为了简化这类异步操作,已经帮我们把异步功能封装好了。spring中提供了@Async注解,我们可以通过它即可开启异步功能,使用起来非常方便。

具体做法如下:

1.在springboot的启动类上面加上@EnableAsync注解。

@EnableAsync
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

2.在需要执行异步调用的业务方法加上@Async注解。

@Service
public class CategoryService {@Asyncpublic void add(Category category) {//添加分类}
}

3.在controller方法中调用这个业务方法。

@RestController
@RequestMapping("/category")
public class CategoryController {@Autowiredprivate CategoryService categoryService;@PostMapping("/add")public void add(@RequestBody category) {categoryService.add(category);}
}

这样就能开启异步功能了。

是不是很easy?

但有个坏消息是:用@Async注解开启的异步功能,会调用AsyncExecutionAspectSupport类的doSubmit方法。

默认情况会走else逻辑。

而else的逻辑最终会调用doExecute方法:

protected void doExecute(Runnable task) {Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));thread.start();
}

我去,这不是每次都会创建一个新线程吗?

没错,使用@Async注解开启的异步功能,默认情况下,每次都会创建一个新线程。

如果在高并发的场景下,可能会产生大量的线程,从而导致OOM问题。

建议大家在@Async注解开启的异步功能时,请别忘了定义一个线程池

9. 自旋锁浪费cpu资源

在并发编程中,自旋锁想必大家都已经耳熟能详了。

自旋锁有个非常经典的使用场景就是:CAS(即比较和交换),它是一种无锁化思想(说白了用了一个死循环),用来解决高并发场景下,更新数据的问题。

而atomic包下的很多类,比如:AtomicInteger、AtomicLong、AtomicBoolean等,都是用CAS实现的。

我们以AtomicInteger类为例,它的incrementAndGet没有每次都给变量加1。

public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

它的底层就是用的自旋锁实现的:

public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5;
}

在do...while死循环中,不停进行数据的比较和交换,如果一直失败,则一直循环重试。

如果在高并发的情况下,compareAndSwapInt会很大概率失败,因此导致了此处cpu不断的自旋,这样会严重浪费cpu资源。

那么,如果解决这个问题呢?

答:使用LockSupport类的parkNanos方法。

具体代码如下:

private boolean compareAndSwapInt2(Object var1, long var2, int var4, int var5) {if(this.compareAndSwapInt(var1,var2,var4, var5)) {return true;} else {LockSupport.parkNanos(10);return false;}}

当cas失败之后,调用LockSupport类的parkNanos方法休眠一下,相当于调用了Thread.Sleep方法。这样能够有效的减少频繁自旋导致cpu资源过度浪费的问题。

10. ThreadLocal用完没清空

在java中保证线程安全的技术有很多,可以使用synchroized、Lock等关键字给代码块加锁。

但是它们有个共同的特点,就是加锁会对代码的性能有一定的损耗。

其实,在jdk中还提供了另外一种思想即:用空间换时间

没错,使用ThreadLocal类就是对这种思想的一种具体体现。

ThreadLocal为每个使用变量的线程提供了一个独立的变量副本,这样每一个线程都能独立地改变自己的副本,而不会影响其它线程所对应的副本。

ThreadLocal的用法大致是这样的:

  1. 先创建一个CurrentUser类,其中包含了ThreadLocal的逻辑。

public class CurrentUser {private static final ThreadLocal<UserInfo> THREA_LOCAL = new ThreadLocal();public static void set(UserInfo userInfo) {THREA_LOCAL.set(userInfo);}public static UserInfo get() {THREA_LOCAL.get();}public static void remove() {THREA_LOCAL.remove();}
}
  1. 在业务代码中调用CurrentUser类。

public void doSamething(UserDto userDto) {UserInfo userInfo = convert(userDto);CurrentUser.set(userInfo);...//业务代码UserInfo userInfo = CurrentUser.get();...
}

在业务代码的第一行,将userInfo对象设置到CurrentUser,这样在业务代码中,就能通过CurrentUser.get()获取到刚刚设置的userInfo对象。特别是对业务代码调用层级比较深的情况,这种用法非常有用,可以减少很多不必要传参。

但在高并发的场景下,这段代码有问题,只往ThreadLocal存数据,数据用完之后并没有及时清理。

ThreadLocal即使使用了WeakReference(弱引用)也可能会存在内存泄露问题,因为 entry对象中只把key(即threadLocal对象)设置成了弱引用,但是value值没有。

那么,如何解决这个问题呢?

public void doSamething(UserDto userDto) {UserInfo userInfo = convert(userDto);try{CurrentUser.set(userInfo);...//业务代码UserInfo userInfo = CurrentUser.get();...} finally {CurrentUser.remove();}
}

需要在finally代码块中,调用remove方法清理没用的数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/283539.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CAP理论与MongoDB一致性、可用性的一些思考

大约在五六年前&#xff0c;第一次接触到了当时已经是hot topic的NoSql。不过那个时候学的用的都是mysql&#xff0c;Nosql对于我而言还是新事物&#xff0c;并没有真正使用&#xff0c;只是不明觉厉。但是印象深刻的是这么一张图片&#xff08;后来google到图片来自这里&#…

【开源】一个WPF开发的XML记事本

今天推荐一个WPF开源项目&#xff1a;XmlNotepad[1]。此仓库由 WPF开发者[2] 推荐&#xff0c;站长简单翻译分享。介绍XML Notepad[3] 是一个 Windows 程序&#xff0c;它为浏览和编辑 XML 文档提供了一个简单直观的用户界面。有四种安装方式&#xff1a;ClickOnce 安装程序[4]…

log4j2 mybatis 显示 sql 和 结果集

为什么80%的码农都做不了架构师&#xff1f;>>> 首先说明版本&#xff1a; log4j 版本是 第2版beta9&#xff0c;引用jar包 log4j-api-2.0-beta9.jar log4j-core-2.0-beta9.jar mybatis 是 mybatis-3.2.3 log4j2 配置 如下&#xff0c;命名为 log4j2.xml&#xff0…

深入解析volatile关键字

目录 1. 初步认识volatile 2. volatile的特性一&#xff1a;保证可见性 2.1 volatile关键字是如何保证可见性的&#xff1f; 2.2 从JMM来看可见性 2.3 从硬件层面了解可见性的本质 2.3.1 CPU高速缓存 2.3.2 缓存一致性 2.3.3 MESI 协议 2.3.4 缓存一致性小结 3. vola…

Nginx基础配置

一、主配置文件结构main block&#xff1b;#全局块配置全局生效event{#事件驱动相关配置 }http{#http/https协议相关配置段 server { ... }&#xff1a;#每个server用于定义一个虚拟主机&#xff1b; server { ... server_name root alias location [OPERATOR] URL { ... if CO…

上周面试回来后写的Java面试总结,想进BAT必看

上周陪同之前一起工作的同事去面试&#xff08;乔治&#xff0c;小袁&#xff0c;鹏飞&#xff08;面试人&#xff09;&#xff09;&#xff0c;第一站是去深圳&#xff0c;第二站上海&#xff0c;第三站杭州。面试什么公司我在这里就不多说了&#xff0c;你们知道是一线公司就…

从贝叶斯方法谈到贝叶斯网络

从贝叶斯方法谈到贝叶斯网络0 引言其实。介绍贝叶斯定理、贝叶斯方法、贝叶斯判断的资料、书籍不少&#xff0c;比方《数理统计学简史》&#xff0c;以及《统计决策论及贝叶斯分析 James O.Berger著》等等&#xff0c;然介绍贝叶斯网络的中文资料则非常少。中文书籍总共也没几本…

k8s 读书笔记 - kubectl 命令行工具用法详解

kubectl 在 k8s 集群中作为 客户端 CLI 工具&#xff0c;可以让用户使用 kubectl 工具执行命令行&#xff0c;并通过使用 k8s API 与 k8s 集群的控制面&#xff08;kube-controller-manager&#xff09;进行通信。kubectl 语法格式kubectl 命令行的语法格式如下&#xff1a;kub…

初级必备:单例模式的7个问题

故事 实话实说&#xff0c;关于单例模式&#xff0c;网上有N多个版本。你估计也看过很多版本。但看完了又能怎样&#xff1f;我技术群里的一位小伙伴&#xff0c;上周面试&#xff0c;就因为一个单例模式&#xff0c;然后叫他回去等通知了。 下面是这位同学被问到的问题&…

Spring AOP源码解析——AOP动态代理原理和实现方式

2019独角兽企业重金招聘Python工程师标准>>> Spring介绍 Spring(http://spring.io/)是一个轻量级的Java 开发框架&#xff0c;同时也是轻量级的IoC和AOP的容器框架&#xff0c;主要是针对JavaBean的生命周期进行管理的轻量级容器&#xff0c;可以单独使用&#xff0…

使用.NET构建简单的高性能Redis(三)

译者注该原文是Ayende Rahien大佬业余自己在使用C# 和 .NET构建一个简单、高性能兼容Redis协议的数据库的经历。首先这个"Redis"是非常简单的实现&#xff0c;但是他在优化这个简单"Redis"路程很有趣&#xff0c;也能给我们在从事性能优化工作时带来一些启…

海尔联手软银机器人,进军服务机器人领域

海尔此次将正式全面进入到服务机器人。 据悉&#xff0c;3月6日海尔公布了未来对于家用机器人的最新战略&#xff0c;同时与软银展开战略合作&#xff0c;将软银的人形机器人引入中国市场&#xff0c;正式进军服务机器人领域。 在本次发布会上&#xff0c;海尔与软银将联手从软…

.NET 7 中的 EndpointFilter

ASP.NET 7 中的 EndpointFilterIntro.NET 6 开始微软引入了 Minimal API&#xff0c;但是相比成熟的控制器模型&#xff0c;还是有很多的不足&#xff0c;.NET 7 针对于 Minimal API 也做了一些改进来让 Minimal API 功能更加丰富&#xff0c;其中 Filter 就是其中的一个更新从…

越来越火的图数据库到底能做什么?

作者 | 俞方桦 随着大数据时代的到来&#xff0c;传统的关系型数据库由于其在数据建模和存储方面的限制&#xff0c;变得越来越难以满足大量频繁变化的需求。关系型数据库&#xff0c;尽管其名称中有“关系”这个词&#xff0c;却并不擅长处理复杂关系的查询和分析。另外&…

piwik抓取用户交互行为

2019独角兽企业重金招聘Python工程师标准>>> https://github.com/matomo-org/matomo-sdk-ios/tree/version-3 http://piwik.org 首先下载demo&#xff0c;把文件拖到项目中去&#xff0c;在AppDelegate.m文件填写piwik服务器的URL和编号&#xff1b; 例如&#xff1…

k8s 读书笔记 - kubernetes 基本概念和术语(下)

DevOps前言上一篇文章 中&#xff0c;我们介绍了 k8s 中的 Master、Node、Pod、Label、RC & RS、Deployment、HPA & VPA、DaemonSet 这些资源对象信息&#xff0c;接下来我们继续介绍 k8s 中常用的资源对象。StatefulSet在 k8s 系统中&#xff0c;Pod 的管理对象 RC、D…

java数据类型后加三个点...

2019独角兽企业重金招聘Python工程师标准>>> 从Java5开始&#xff0c;Java语言对方法参数支持一种新写法&#xff0c;varargs&#xff08;可变长度参数列表&#xff09;&#xff0c;其语法就是类型后跟...&#xff0c;表示此处接受的参数为0到多个Object类型的对象&…

手把手教你用 Jenkins 自动部署 SpringBoot!

1. 什么是 CI/CD 1.1 CI&#xff08;Continuous Integration&#xff09; 1.2 CD&#xff08;Continuous Delivery/Continuous Deployment&#xff09; 2. 什么是 Jenkins 3. 准备工作 3.1 整体规划 3.2 准备代码 3.3 准备服务器 4. 搭建 Jenkins 5. 安装插件 6. 配置 …

bondat蠕虫传播与对抗

转载来自&#xff1a;http://www.mottoin.com/109730.html &#xff08;1&#xff09;可移动磁盘传播手段&#xff1a;隐藏U盘文件&#xff0c;创建快捷方式指向病毒bat文件。Bondat蠕虫主要通过可移动磁盘传播&#xff0c;并借助可移动磁盘中的文件隐蔽自身。Bondat蠕虫会检索…

vim 编译 Python 代码提示配置

2019独角兽企业重金招聘Python工程师标准>>> .vim 和.vimrc 拷贝到根目录 注意根目录下默认是没有.vim的&#xff0c;所以拷贝.vim 没问题&#xff0c;但是拷贝.vimrc 之前需要把原来的.vimrc备份 两个文件下载&#xff1a;http://pan.baidu.com/s/1eRRhakM 转载于:…