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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010498753/article/details/85047105
1. 【强制】 获取单例对象需要保证线程安全,其中的方法也要保证线程安全。
说明: 资源驱动类、工具类、 单例工厂类都需要注意。

解析:使用单例模式时经常会被同时读与同时写,因为使用的是同一个变量,而内部的内容是大家共享的,经常会被多人读写

由于静态内部类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. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
正例:
public class TimerTaskThread extends Thread {
public TimerTaskThread() {
super.setName("TimerTaskThread");
...
}
}

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

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");
    }
 
}

ThreadPool创建的线程池【推荐】,可以监控


 
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。

例子,摘自: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)启动线程
        }
    }
}

使用ThreadLocal解决问题

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://zzhonghe.iteye.com/blog/826162


import static java.lang.System.out;

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;

public class TestSyncMethods {

	public static void test(int round, int threadNum, CyclicBarrier cyclicBarrier) {
		new SyncTest("Sync", round, threadNum, cyclicBarrier).testTime();
		new LockTest("Lock", round, threadNum, cyclicBarrier).testTime();
		new AtomicTest("Atom", round, threadNum, cyclicBarrier).testTime();
	}

	public static void main(String args[]) {

		for (int i = 0; i < 5; i++) {
			int round = 100000 * (i + 1);
			int threadNum = 5 * (i + 1);
			CyclicBarrier cb = new CyclicBarrier(threadNum * 2 + 1);
			out.println("==========================");
			out.println("round:" + round + " thread:" + threadNum);
			test(round, threadNum, cb);

		}
	}
}

class SyncTest extends TestTemplate {
	public SyncTest(String _id, int _round, int _threadNum, CyclicBarrier _cb) {
		super(_id, _round, _threadNum, _cb);
	}

	@Override
	/** 
	 * synchronized关键字不在方法签名里面,所以不涉及重载问题 
	 */
	synchronized long getValue() {
		return super.countValue;
	}

	@Override
	synchronized void sumValue() {
		super.countValue += preInit[index++ % round];
	}
}

class LockTest extends TestTemplate {
	ReentrantLock lock = new ReentrantLock();

	public LockTest(String _id, int _round, int _threadNum, CyclicBarrier _cb) {
		super(_id, _round, _threadNum, _cb);
	}

	/**
	 * synchronized关键字不在方法签名里面,所以不涉及重载问题
	 */
	@Override
	long getValue() {
		try {
			lock.lock();
			return super.countValue;
		} finally {
			lock.unlock();
		}
	}

	@Override
	void sumValue() {
		try {
			lock.lock();
			super.countValue += preInit[index++ % round];
		} finally {
			lock.unlock();
		}
	}
}

class AtomicTest extends TestTemplate {
	public AtomicTest(String _id, int _round, int _threadNum, CyclicBarrier _cb) {
		super(_id, _round, _threadNum, _cb);
	}

	@Override
	/** 
	 * synchronized关键字不在方法签名里面,所以不涉及重载问题 
	 */
	long getValue() {
		return super.countValueAtmoic.get();
	}

	@Override
	void sumValue() {
		super.countValueAtmoic.addAndGet(super.preInit[indexAtomic.get() % round]);
	}
}

abstract class TestTemplate {
	private String id;
	protected int round;
	private int threadNum;
	protected long countValue;
	protected AtomicLong countValueAtmoic = new AtomicLong(0);
	protected int[] preInit;
	protected int index;
	protected AtomicInteger indexAtomic = new AtomicInteger(0);
	Random r = new Random(47);
	// 任务栅栏,同批任务,先到达wait的任务挂起,一直等到全部任务到达制定的wait地点后,才能全部唤醒,继续执行
	private CyclicBarrier cb;

	public TestTemplate(String _id, int _round, int _threadNum, CyclicBarrier _cb) {
		this.id = _id;
		this.round = _round;
		this.threadNum = _threadNum;
		cb = _cb;
		preInit = new int[round];
		for (int i = 0; i < preInit.length; i++) {
			preInit[i] = r.nextInt(100);
		}
	}

	abstract void sumValue();

	/*
	 * 对long的操作是非原子的,原子操作只针对32位 long是64位,底层操作的时候分2个32位读写,因此不是线程安全
	 */
	abstract long getValue();

	public void testTime() {
		ExecutorService se = Executors.newCachedThreadPool();
		long start = System.nanoTime();
		// 同时开启2*ThreadNum个数的读写线程
		for (int i = 0; i < threadNum; i++) {
			se.execute(new Runnable() {
				public void run() {
					for (int i = 0; i < round; i++) {
						sumValue();
					}

					// 每个线程执行完同步方法后就等待
					try {
						cb.await();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (BrokenBarrierException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

				}
			});
			se.execute(new Runnable() {
				public void run() {

					getValue();
					try {
						// 每个线程执行完同步方法后就等待
						cb.await();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (BrokenBarrierException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

				}
			});
		}

		try {
			// 当前统计线程也wait,所以CyclicBarrier的初始值是threadNum*2+1
			cb.await();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BrokenBarrierException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// 所有线程执行完成之后,才会跑到这一步
		long duration = System.nanoTime() - start;
		out.println(id + " = " + duration);

	}

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

/**
 * @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);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (B) {
                        System.out.println("thread1...");
                    }
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (B) {
                    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)操作实现线程安全的自增。
  1. 【强制】多线程并行处理定时任务时, Timer 运行多个 TimeTask 时,只要其中之一没有捕获
    抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。

10.【推荐】使用 CountDownLatch 进行异步转同步操作,每个线程退出前必须调用 countDown
方法,线程执行代码注意 catch 异常,确保 countDown 方法被执行到,避免主线程无法执行
至 await 方法,直到超时才返回结果。
说明: 注意,子线程抛出异常堆栈,不能在主线程 try-catch 到。

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

  1. 【推荐】 在并发场景下, 通过双重检查锁(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...
}

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

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

在共享变量过程中,

15.【参考】 ThreadLocal 无法解决共享对象的更新问题, ThreadLocal 对象建议使用 static
修饰。这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享
此静态变量 ,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只
要是这个线程内定义的)都可以操控这个变量

猜你喜欢

转载自blog.csdn.net/u010498753/article/details/85047105