阿里巴巴规约手册个人笔记编程规约(一)六、并发处理

1. 【强制】 获取单例对象需要保证线程安全,其中的方法也要保证线程安全。
说明: 资源驱动类、工具类、 单例工厂类都需要注意。

解析:使用单例模式时因为 并发问题,经常会在

 if ( a==null){
      a = new A();
  }
   return a;

产生并发问题,可能会创建很多个新的单例对象

参考:https://blog.csdn.net/cselmu9/article/details/51366946

个人比较推荐的写法:
由于静态内部类SingletonHolder只有在getInstance()方法第一次被调用时,才会被加载,而且构造函数为private,因此该种方式实现了懒汉式的单例模式。不仅如此,根据JVM本身机制,静态内部类的加载已经实现了线程安全。所以给大家推荐这种写法。

public class Singleton {
    public static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton() {}

    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

下一个

2. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
10.【推荐】使用 CountDownLatch 进行异步转同步操作,每个线程退出前必须调用 countDown
方法,线程执行代码注意 catch 异常,确保 countDown 方法被执行到,避免主线程无法执行
至 await 方法,直到超时才返回结果。
说明: 注意,子线程抛出异常堆栈,不能在主线程 try-catch 到。

线程名称是调试的关键问题,虽然每个线程都有一个线程id,但为了区分好他的作用,最好添加好名称

package ClassFiveDotFive;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import javax.management.RuntimeErrorException;

/**
 * https://zk1878.iteye.com/blog/1002652
 * 
 * CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
 * 
 * 主要方法
 * 
 * public CountDownLatch(int count);
 * 
 * public void countDown();
 * 
 * public void await() throws InterruptedException
 * 
 * 
 * 构造方法参数指定了计数的次数
 * 
 * countDown方法,当前线程调用此方法,则计数减一
 * 
 * awaint方法,调用此方法会一直阻塞当前线程,直到计时器的值为0
 * 
 * @author david
 *
 */
public class CountDownLatchDemo {
	final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

	public static void main(String[] args) throws InterruptedException {
		CountDownLatch latch = new CountDownLatch(2);// 两个工人的协作
		Worker worker1 = new Worker("张三工人", 5000, latch);
		Worker worker2 = new Worker("李四工人", 8000, latch);
		Worker worker3 = new Worker("赵五工人", 8000, latch);
		worker1.start();//
		worker2.start();//
		worker3.start();
		latch.await(10, TimeUnit.SECONDS);// 等待所有工人完成工作
		System.out.println("all work done at " + sdf.format(new Date()));
	}

	static class Worker extends Thread {
		String workerName;
		int workTime;
		CountDownLatch latch;

		public Worker(String workerName, int workTime, CountDownLatch latch) {
			super(workerName); //添加Thread线程名称
			this.workerName = workerName;
			this.workTime = workTime;
			this.latch = latch;

		}

		public void run() {
			System.out.println("工人: " + workerName + " 工作开始在 " + sdf.format(new Date()));
			doWork();// 工作了
			System.out.println("工人: " + workerName + " 工作结束在 " + sdf.format(new Date()));
			latch.countDown();// 工人完成工作,计数器减一

		}

		private void doWork() {
			/*
			 * try { Thread.sleep(workTime); } catch (InterruptedException e) {
			 * e.printStackTrace(); } finally{ latch.countDown(); }
			 */
			// 李四工作时抛出了异常
			if (workTime == 8000) {
				throw new RuntimeErrorException(null, "工作被中途调出去了");
			}
			latch.countDown();
		}
	}

}

3. 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明: 使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决
资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或
者“过度切换”的问题。
4. 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明: Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE, 可能会创建大量的线程,从而导致 OOM。

以下代码提供两种方式创建线程池供以后调试用
Executors 创建


 
public class WorkerThread implements Runnable {
 
    private String command;
 
    public WorkerThread(String s){
        this.command=s;
    }
 
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+" Start. Command = "+command);
        processCommand();
        System.out.println(Thread.currentThread().getName()+" End.");
    }
 
    private void processCommand() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 
    @Override
    public String toString(){
        return this.command;
    }
}
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class SimpleThreadPool {
 
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            Runnable worker = new WorkerThread("" + i);
            executor.execute(worker);
          }
        executor.shutdown();
        while (!executor.isTerminated()) {
        }
        System.out.println("Finished all threads");
    }
 
}

我们来看看他的构造方法

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

public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }
 public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

BlockEdBlockingQueue 初始化长度为Int.maxValue个Runnable对象,极容易OOM。

我们可以看到构造方法里LinkedblockingQueue创建了Integer.MAX_VALUE个节点,我们缩小java程序里可申请的内存来观察出这个现象
在这里插入图片描述

-Xmx8m -Xms8m

创建堆在java内存是创建对象,,栈是创建临时变量
在这里插入图片描述
更多的JVM参数:https://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html

使用ThreadPoolExecutor 创建线程 例子


import java.util.concurrent.ThreadPoolExecutor;
 
public class MyMonitorThread implements Runnable
{
    private ThreadPoolExecutor executor;
 
    private int seconds;
 
    private boolean run=true;
 
    public MyMonitorThread(ThreadPoolExecutor executor, int delay)
    {
        this.executor = executor;
        this.seconds=delay;
    }
 
    public void shutdown(){
        this.run=false;
    }
 
    @Override
    public void run()
    {
        while(run){
                System.out.println(
                    String.format("[monitor] [%d/%d] Active: %d, Completed: %d, Task: %d, isShutdown: %s, isTerminated: %s",
                        this.executor.getPoolSize(),
                        this.executor.getCorePoolSize(),
                        this.executor.getActiveCount(),
                        this.executor.getCompletedTaskCount(),
                        this.executor.getTaskCount(),
                        this.executor.isShutdown(),
                        this.executor.isTerminated()));
                try {
                    Thread.sleep(seconds*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        }
 
    }
}
 
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
 
public class RejectedExecutionHandlerImpl implements RejectedExecutionHandler {
 
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println(r.toString() + " is rejected");
    }
 
}

这样子我们全部参数都得指定,保证了正确使用线程


import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
public class WorkerPool {
 
    public static void main(String args[]) throws InterruptedException{
        //RejectedExecutionHandler implementation
        RejectedExecutionHandlerImpl rejectionHandler = new RejectedExecutionHandlerImpl();
        //Get the ThreadFactory implementation to use
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        //creating the ThreadPoolExecutor
        ThreadPoolExecutor executorPool = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2), threadFactory, rejectionHandler);
        //start the monitoring thread
        MyMonitorThread monitor = new MyMonitorThread(executorPool, 3);
        Thread monitorThread = new Thread(monitor);
        monitorThread.start();
        //submit work to the thread pool
        for(int i=0; i<10; i++){
            executorPool.execute(new WorkerThread("cmd"+i));
        }
 
        Thread.sleep(30000);
        //shut down the pool
        executorPool.shutdown();
        //shut down the monitor thread
        Thread.sleep(5000);
        monitor.shutdown();
 
    }
}

下一个

5. 【强制】 SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为
static,必须加锁,或者使用 DateUtils 工具类。
正例: 注意线程安全,使用 DateUtils。亦推荐如下处理:
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
说明: 如果是 JDK8 的应用,可以使用 Instant 代替 Date, LocalDateTime 代替 Calendar,
DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释: simple beautiful strong
immutable thread-safe。

我们引申一个话题是,如何判断线程安全与线程不安全?

第一 我们首先判断这个代码里有没有全局共享变量
第二 我们判断这个全局共享变量读写操作,如果只读,那不会产生线程不安全问题
第三 如果这个共享变量同时可读可写,要考虑每个操作的原子性
注意每一步写的操作的原子性并注意 i++与i--不是原子操作(根据编译优化会产生不一样的结果)
第四步,考虑多写的情况下的线程安全。

例子,摘自:https://www.jianshu.com/p/d9977a048dab

public class TestSimpleDateFormat {
    //(1)创建单例实例
    static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        //(2)创建多个线程,并启动
        for (int i = 0; i <100 ; ++i) {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    try {//(3)使用单例日期实例解析文本
                        System.out.println(sdf.parse("2017-12-13 15:17:27"));
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.start();//(4)启动线程
        }
    }
}

解析来自:https://www.jianshu.com/p/d9977a048dab
源码解析

public Date parse(String source) throws ParseException
    {
        ParsePosition pos = new ParsePosition(0);
        Date result = parse(source, pos); //使用了SimpleDateFormat方法
        if (pos.index == 0)
            throw new ParseException("Unparseable date: \"" + source + "\"" ,
                pos.errorIndex);
        return result;
    }

SimpleDateFormat 会创建一个统一的calendar 对象

 public SimpleDateFormat(String pattern, Locale locale)
    {
        if (pattern == null || locale == null) {
            throw new NullPointerException();
        }

        initializeCalendar(locale);
        this.pattern = pattern;
        this.formatData = DateFormatSymbols.getInstanceRef(locale);
        this.locale = locale;
        initialize(locale);
    }
 private void initializeCalendar(Locale loc) {
        if (calendar == null) {
            assert loc != null;
            // The format object must be constructed using the symbols for this zone.
            // However, the calendar should use the current default TimeZone.
            // If this is not contained in the locale zone strings, then the zone
            // will be formatted using generic GMT+/-H:MM nomenclature.
            calendar = Calendar.getInstance(TimeZone.getDefault(), loc);
        }
    }

我们只关心全局共享变量的读写操作的原子性
来考察他是否线程安全,要注意临时变量是线程安全的,因为都存在于临时栈区中

public Date parse(String text, ParsePosition pos)
    {
       
        //(1)解析日期字符串放入CalendarBuilder的实例calb中
        .....

        Date parsedDate;
        try {//(2)使用calb中解析好的日期数据设置calendar
            parsedDate = calb.establish(calendar).getTime();
            ...
        }
       
        catch (IllegalArgumentException e) {
           ...
            return null;
        }

        return parsedDate;
    }

Calendar establish(Calendar cal) {
   ...
   //(3)重置日期对象cal的属性值
   cal.clear();
   //(4) 使用calb中中属性设置cal
   ...
   //(5)返回设置好的cal对象
   return cal;
}

从上面步骤可知步骤(3)(4)(5)操作不是原子性操作,当多个线程调用parse
方法时候比如线程A执行了步骤(3)(4)也就是设置好了cal对象,在执行步骤(5)前线程B执行了步骤(3)清空了cal对象,由于多个线程使用的是一个cal对象,所以线程A执行步骤(5)返回的就可能是被线程B清空后的对象,当然也有可能线程B执行了步骤(4)被线程B修改后的cal对象。从而导致程序错误。

public class TestSimpleDateFormat2 {
    // (1)创建threadlocal实例
    static ThreadLocal<DateFormat> safeSdf = new ThreadLocal<DateFormat>(){
        @Override 
        protected SimpleDateFormat initialValue(){
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
    
    public static void main(String[] args) {
        // (2)创建多个线程,并启动
        for (int i = 0; i < 10; ++i) {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    try {// (3)使用单例日期实例解析文本
                            System.out.println(safeSdf.get().parse("2017-12-13 15:17:27"));
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.start();// (4)启动线程
        }
    }
}

6. 【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁; 能
锁区块,就不要锁整个方法体; 能用对象锁,就不要用类锁。
说明: 尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法。

例子转载:https://864343928.iteye.com/blog/2125368
还有一篇 类锁,对象锁的比较http://www.voidcn.com/article/p-rthbixjo-bao.html

package threadcost;

public class Test {

	private static final int num = 500000000;

	public void cal() {
		long start = System.currentTimeMillis();
		int result = 0;

		for (int i = 0; i < num; i++) {
			result += 1;
		}
		System.out.println("cal计算结果为: " + result + ". 计算的时间为:" + (System.currentTimeMillis() - start));
	}

	public void cal2() {
		long start = System.currentTimeMillis();
		int result = 0;

		for (int i = 0; i < num; i++) {
			synchronized (this) {
				result += 1;
			}
		}
		System.out.println("cal2有锁计算结果为: " + result + ". 计算的时间为:" + (System.currentTimeMillis() - start) + "\r");
	}

	public static void main(String[] args) {

		for (int i = 0; i < 10; i++) {
			Test test = new Test();

			test.cal();
			test.cal2();
		}
	}
}
  1. 【强制】对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造
    成死锁。
    说明: 线程一需要对表 A、 B、 C 依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序
    也必须是 A、 B、 C,否则可能出现死锁。
    死锁的例程
Thread 1  locks A, waits for B
Thread 2  locks B, waits for A
package thread;

/**
 * @Description  死锁的例子
 * @author gaowenming
 */
public class DeadLock {

    /** A锁 */
    private static String A = "A";

    /** B锁 */
    private static String B = "B";

    public static void main(String[] args) {
        new DeadLock().deadLock();
    }

    public void deadLock() {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (A) {
                    try {
                        Thread.sleep(2000); //注意一定要大家同时sleep
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (B) {
                        System.out.println("thread1...");
                    }
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (B) {
                	  try {
                          Thread.sleep(2000);//注意一定要大家同时sleep
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                    synchronized (A) {
                        System.out.println("thread2...");
                    }
                }
            }
        });

        t1.start();
        t2.start();

    }

}
8. 【强制】并发修改同一记录时,避免更新丢失, 需要加锁。 要么在应用层加锁,要么在缓存加
锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。
说明: 如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次
数不得小于 3 次。
悲观锁
悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
Java synchronized 就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被block。
乐观锁
乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
乐观锁一般来说有以下2种方式:

使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
使用时间戳(timestamp)。乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

Java JUC中的atomic包就是乐观锁的一种实现,AtomicInteger 通过CAS(Compare And Set)操作实现线程安全的自增。
9. 【强制】多线程并行处理定时任务时, Timer 运行多个 TimeTask 时,只要其中之一没有捕获
抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。

实例摘自:https://blog.csdn.net/qqq911/article/details/2975908 并改写了一下

import java.util.Timr;
import java.util.TimerTask;

/**
 * Timer同时执行多个定时任务的例子。
 * 
 * @author 赵学庆,Java世纪网(java2000.net)
 * 
 */
public class TimeTestMulti {
  public static void main(String[] args) {
    final Timer timer = new Timer();

    // 任务1, 每一秒执行一次
    timer.schedule(new TimerTask() {
      public void run() {
       throw new NullPointerException("呵呵我报错了");
      }
    }, 0, 1000);
    // 任务2,每0.5秒执行一次
    timer.schedule(new TimerTask() {
      public void run() {
        System.out.println("I am running 2 ...");
      }
    }, 0, 500);
  }
}

为什么?正向分析代码很多,我们看的有点头晕,还好我们能复现他的报错,我们研究一下他报错的代码,他一旦报错,就会清空所有的队列

 public void run() {
        try {
            mainLoop();
        } finally {
            // Someone killed this Thread, behave as if Timer cancelled
            synchronized(queue) {
                newTasksMayBeScheduled = false;
                queue.clear();  // Eliminate obsolete references
            }
        }
    }

使用另一个则没有,例子摘自:https://www.jianshu.com/p/19da4371c4a0

package task;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Test  {

	private static LocalDateTime ldt = LocalDateTime.now();

	private static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");

	public static void main(String[] args) {

		ScheduledExecutorService ses = new ScheduledThreadPoolExecutor(2);

		Test1 test1 = new Test1();
		Test2 test2 = new Test2();

		// 第一个参数:需要执行的任务,第二个参数:第一次执行延迟的时间,
		// 第三个参数:间隔时间,第四个参数:计量单位,这里选择秒
		ses.scheduleAtFixedRate(test1, 1, 2, TimeUnit.SECONDS);
		ses.scheduleAtFixedRate(test2, 1, 2, TimeUnit.SECONDS);
		System.out.println(ldt.format(dtf));
	}

}

class Test1 extends Thread {
	@Override
	public void run() {
		try {
			LocalDateTime ldt = LocalDateTime.now();
			DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
			throw new NullPointerException();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}
}

class Test2 extends Thread {
	@Override
	public void run() {
		try {
			LocalDateTime ldt = LocalDateTime.now();
			DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
			System.out.println("我是Test2," + ldt.format(dtf) + "执行");
		} catch (Exception e) {
			e.printStackTrace();
		}

	}
}


11.【推荐】避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一
seed 导致的性能下降。
说明: Random 实例包括 java.util.Random 的实例或者 Math.random()的方式。
正例: 在 JDK7 之后,可以直接使用 API ThreadLocalRandom, 而在 JDK7 之前, 需要编码保
证每个线程持有一个实例。

实例程序

package random;

import java.util.Random;

public class Main {
	public static void main(String[] args) {
		Random 	r = new Random(23);
		
		System.out.println(r.nextInt());
	}
}

灵感来自:摘自:https://www.cnblogs.com/xrq730/p/4865416.html

查看代码发现
seed是一个全局变量

  private final AtomicLong seed;

AtomicLong是存在多线程竞争问题会损耗部分性能
而Math.random()是一个静态内部类,静态内部类是JVM优化级别,不存在多线程性能争抢问题

return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
12. 【推荐】 在并发场景下, 通过双重检查锁(double-checked locking) 实现延迟初始化的优
化问题隐患(可参考 The "Double-Checked Locking is Broken" Declaration), 推荐解
决方案中较为简单一种(适用于 JDK5 及以上版本) ,将目标属性声明为 volatile 型。

反例:

class LazyInitDemo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) synchronized(this) {
if (helper == null)
helper = new Helper();
}
return helper;
}
// other methods and fields...
}

解析参考来自:https://www.cnblogs.com/oreo/p/8505005.html
正例

public class Singleton {
    private static volatile Singleton instance=null;    //添加volatile修饰符
    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {//1
            synchronized (Singleton.class) {//2
                if (instance == null) {//3
                    instance = new Singleton();//4
                }
            }
        }
        return instance;
    }
}
13.【参考】 volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,
但是如果多写,同样无法解决线程安全问题。如果是 count++操作,使用如下类实现:
AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推
荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数) 。

注意,AtomicInteger 只能保证 i++的原子性
判断是无法解决的

  if(count.get()>10{
   count.addAndGet(1); 
   }

count.get()依然会存在并发问题

14.【参考】 HashMap 在容量不够进行 resize 时由于高并发可能出现死链,导致 CPU 飙升,在
开发过程中可以使用其它数据结构或加锁来规避此风险。

明天看看这个
https://blog.csdn.net/zhuqiuhui/article/details/51849692

/**
     * Transfers all entries from current table to newTable.
     */
    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) { //复制链表
 
            while(null != e) {
                Entry<K,V> next = e.next;        
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);//寻找插入的位置
                e.next = newTable[i]; //链表的插入操作,先缓存
                newTable[i] = e;  //
                e = next;
            } // while
 
        }
    }
15.【参考】 ThreadLocal 无法解决共享对象的更新问题, ThreadLocal 对象建议使用 static
修饰。这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享
此静态变量 ,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只
要是这个线程内定义的)都可以操控这个变量

摘自:https://sanii.cn/article/248
例子程序
使用synoiized

package threadlocal;

/**
 * 线程同步的运用
 *
 * @author sani
 *
 */
public class SynchronizedThreadLocal {


   

    /**
     * 建立线程,调用内部类
     */
    public void useThread() {
        BankThreadLocal bank = new BankThreadLocal();
        BankThreadLocal bank2 = new BankThreadLocal();
        NewThreadLocal new_thread = new NewThreadLocal(bank,"张三");
        NewThreadLocal new_thread2 = new NewThreadLocal(bank2,"李四");
        System.out.println("线程1");
        Thread thread1 = new Thread(new_thread);
        thread1.start();
        /* System.out.println("线程2");
        Thread thread2 = new Thread(new_thread);
        thread2.start();*/
        System.out.println("线程2");
        Thread thread2 = new Thread(new_thread2);
        thread2.start();
    }

    public static void main(String[] args) {
        SynchronizedThreadLocal st = new SynchronizedThreadLocal();
        st.useThread();
    }

}


class NewThreadLocal extends Thread {
    private BankThreadLocal bankThreadLocal;

    public NewThreadLocal(BankThreadLocal bankThreadLocal,String threadName) {
    	super(threadName);
        this.bankThreadLocal = bankThreadLocal;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            // bank.save1(10);
        	bankThreadLocal.save(10);
            System.out.println(this.getName()+" : "+ i + "账户余额为:" + bankThreadLocal.getAccount()+" hashcode: "+bankThreadLocal.hashCode());
        }
    }

}

class Bank {

   private int account = 100;


   public int getAccount() {
       return account;
   }

   /**
    * 用同步方法实现
    *
    * @param money
    */
   public synchronized void save(int money) {
       account += money;
   }
}
package threadlocal;  
  
public class Test {  
     
	Long longNotLocal;
	String stringNotLocal ;
	
    ThreadLocal<Long> longLocal = new ThreadLocal<Long>();  
    ThreadLocal<String> stringLocal = new ThreadLocal<String>();  
  
    public void set() {  
        longLocal.set(Thread.currentThread().getId());  
        stringLocal.set(Thread.currentThread().getName());  
    } 
    public void setNotLocal(){
    	longNotLocal = new Long(3);
    	stringNotLocal = new String("abcdefg");
    }
    public long getNotLocalLong() {  
        return longNotLocal;
    }  
    public String getNotLocalString() {  
        return stringNotLocal ;
    }  
    public long getLong() {  
        return longLocal.get();  
    }  
  
    public String getString() {  
        return stringLocal.get();  
    }  
  
    public static void main(String[] args) throws InterruptedException {  
        final Test test = new Test();  
  
        test.set();  
        System.out.println(test.getLong());  
        System.out.println(test.getString());  
        test.setNotLocal();
        System.out.println(test.getNotLocalLong());
        System.out.println(test.getNotLocalString());
        
        Thread thread1 = new Thread() {  
            public void run() {  
                test.set();  
                System.out.println(test.getLong());  
                System.out.println(test.getString());  
                test.setNotLocal();
                System.out.println(test.getNotLocalLong());
                System.out.println(test.getNotLocalString());
            };  
        };  
        thread1.start();  
        thread1.join();  
  
        System.out.println(test.getLong());  
        System.out.println(test.getString());  
    }  
}  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值