Java并发编程的艺术(上)

时间:2023-02-04 20:54:53
ChapterOne 并发编程的挑战  1. 并发编程的目的是为了让程序更快速的运行,但是并不是启用更多的线程就能让程序最大限度的并发执行。2. 进行多线程并发编程时,会遇到许多挑战,列举三个:上下文切换、死锁、其他资源限制。3. 是否并发一定就比串行快?  例子:
public class CurrencyTest {private static final long count = 1000000001; /** * @author PeterS * @date 2016年5月4日 * @param  * @description */public static void main(String[] args) throws InterruptedException{currency();serial();}public static void currency() throws InterruptedException {long start = System.currentTimeMillis();Thread thread = new Thread(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubint a= 0;for(long i =0;i<count;i++){a+=5;}}});thread.start();int b = 0;for(long i=0;i<count;i++) b--;long time = System.currentTimeMillis()-start;thread.join(); //wait for the thread to dieSystem.out.println("currency:"+time+"ms,b="+b);}public static void serial() {long start = System.currentTimeMillis();int a =0,b=0;for(long i = 0;i<count;i++) a+=5;for(long i = 0;i<count;i++) b--;long time = System.currentTimeMillis()-start;System.out.println("serial:"+time+"ms,b="+b);} }
    这个是比较结果:
count的值 currency(ms) serial(ms)
10001 1 0
1000001 3 5
100000001 38 73
1000000001 363 695
可见,在并发量小的时候,并行并不占优势,并发量大了,效果才明显。4. 上下文切换:减少上下文切换的方法:
①、无锁并发编程:避免使用锁,例如将数据ID按照hash算法取模分段,不同线程处理不同段的数据
②、CAS算法:Atom类,不需要加锁
③、使用最少线程
④、协程:在单线程中进行多任务的调度和切换
5、死锁:
public class DeadlockTest {private static String a = "A";private static String b = "B"; /** * @author PeterS * @date 2016年5月4日 * @param  * @description */public static void main(String[] args) {new DeadlockTest().deadlock();}public void deadlock() {Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized(a){try {Thread.currentThread().sleep(2000);} catch (Exception e) {e.printStackTrace();}synchronized(b){System.out.println("1");}}}});Thread thread2 = new Thread(new Runnable() {public void run() {synchronized(b){synchronized (a) {System.out.println("2");}}}});thread1.start();thread2.start();} }
这段代码就可能出现死锁。
Java并发编程的艺术(上)
如何有效的避免死锁呢?首先要看看死锁的四个必要条件:互斥、请求与保持、不可剥夺、循环等待。所以避免死锁的方法有:
避免一个线程获取多个锁
尽量保证每个锁只占有一个资源
尝试使用定时锁,lock.trylock(timeout)来代替使用内部锁机制
对于数据库锁,加锁和解锁必须放到一个数据库连接里,否则会出现解锁失败
6. 其他资源限制:硬件资源限制,采取分布式解决;软件资源限制,采取资源池复用解决。
ChapterTwo Java并发机制的底层实现原理
1. Java代码的生命流程:Java代码——>Java字节码——>被类加载器加载到JVM中——>JVM执行字节码——>转化成汇编语言——>在CPU上执行2. volatile关键字的应用①volatile关键字保证所有线程看到这个变量是数据一致的,②实现原理:volatile变量会触发两个操作
将当前处理器的缓存行数据写入系统内存
这个写回内存的操作会使其他的CPu缓存了该地址的数据无效
3. synchronized的实现原理和应用具体表现形式:
对于普通同步方法,锁是当前实例对象;
对于静态同步方法,锁是当前类的Class 对象;
对于同步代码块,锁是括号()里配置的内容;
锁一共四种状态,从低到高依次是:无锁、偏向锁、轻量级锁、重量级锁。这四种状态的锁,只能升级,不能降级。自旋锁:一直自旋,不调用睡眠的锁,会消耗CPU。ChapterThree Java内存模型ChapterFour Java并发编程基础
1. 一个不同的Java程序包含哪些线程?
如下代码:
public class MutiThread { /** * @author PeterS * @date 2016年5月4日 * @param  * @description */public static void main(String[] args) {ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);for (ThreadInfo threadInfo : threadInfos) {System.out.println("["+threadInfo.getThreadId()+"]"+threadInfo.getThreadName());}} }
Java并发编程的艺术(上)
2. 在不同的JVM和操作系统中,线程的规划存在差异,有些操作系统可能会忽略线程优先级的设定;3. 线程的状态六种:
状态名称 说明
NEW 初始状态,线程被构建,但是还没有调用start()方法
RUNNABLE 就绪状态和运行中状态统称runnable
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 等待状态
TIME_WAITING 超时等待状态,可在指定的时间自行返回
TERMINATED 终止状态,表示当前的线程已经执行完毕了
4. 查看线程的状态:
用jps命令查看线程id;然后用jstack命令+线程id查看;Java并发编程的艺术(上)
Java并发编程的艺术(上)
5. Java线程状态变迁图Java并发编程的艺术(上)
6.Daemon线程(守护线程)主要作用于后台调度以及支持性工作,当Java虚拟机中不存在一个Daemon线程时,虚拟机将退出。可以用Thread.setDaemon(true)设置为Daemon线程。7. 优雅的中断线程:通过中断操作和标志位的方式。
public class Shutdown { public static void main(String[] args) throws Exception{Runner one = new Runner(); Thread countThread = new Thread(one,"CountThread"); countThread.start(); TimeUnit.SECONDS.sleep(1); countThread.interrupt(); //用中断操作来终止进程 Runner two = new Runner(); countThread = new Thread(two,"CountThread"); countThread.start(); TimeUnit.SECONDS.sleep(1); two.cancel(); //设置标志位来中断进程 }private static class Runner implements Runnable{private long i;private volatile boolean on = true;/** * @author PeterS * @date 2016年5月5日 * @param  * @description */@Overridepublic void run() {while(on&&!Thread.currentThread().isInterrupted()){ //判断终止的条件i++;}System.out.println("count i="+i);}public void cancel() {on=false;} } }
8. 等待/通知机制(生产/消费者模型)
该范式分为两个部分:等待者(消费者)和通知者(生产者);等待者(消费者):
synchronized(object){ while(condition不满足){ object.wait(); } //to do}
通知者(生产者):
synchronized(object){ 改变condition; object.notifyAll();}
直接上代码:
public class WaitNotify {static boolean flag = true;static Object lock = new Object(); /** * @author PeterS * @date 2016年5月5日 * @param  * @description */public static void main(String[] args) {Thread thread1 = new Thread(new Wait(),"wait");thread1.start();SleepUtils.second(1);Thread thread2 = new Thread(new Notify(),"notify");thread2.start();}static class Wait implements Runnable {public void run() {synchronized (lock) {if (flag) {try {System.out.println(Thread.currentThread()+"flag is true.wait@ "+new SimpleDateFormat("HH:mm:ss").format(new Date()));lock.wait();} catch (Exception e) {// TODO: handle exception}}System.out.println(Thread.currentThread()+"flag is false.running@ "+new SimpleDateFormat("HH:mm:ss").format(new Date())); }}}static class Notify implements Runnable{ /** * @author PeterS * @date 2016年5月5日 * @param  * @description */@Overridepublic void run() {synchronized (lock) {System.out.println(Thread.currentThread()+"hold lock.notify@ "+new SimpleDateFormat("HH:mm:ss").format(new Date()));lock.notifyAll();flag = false;SleepUtils.second(5);}synchronized (lock) {System.out.println(Thread.currentThread()+"hold lock again.sleep@ "+new SimpleDateFormat("HH:mm:ss").format(new Date()));SleepUtils.second(5);}}}public static class SleepUtils{public static final void second(long sec) {try {TimeUnit.SECONDS.sleep(sec);} catch (Exception e) {// TODO: handle exception}}} }
Java并发编程的艺术(上)
9. 管道传输管道输入输出流主要用于线程之间的数据传输,传输媒介为内存。分为4种表现形式:PipedOutputStream、PipedInputStream、PipedReader、PipedWriter。10. join()含义:如果线程a执行了thread.join(),表示当前线程A等待thread线程终止之后才从thread.join()返回。join(long millis)和join(long millis,int nanos)提供了超时特性,如果线程没有在给定的时间内结束,将返回。11. ThreadLocal使用ThreadLocal是一个线程变量,是一个以ThreadLocal为key,任意对象为value的存储结构。建议深入学习一下。这里不做细致讲解。12. 线程池的实例:ConnectionPool.java
public class ConnectionPool {private LinkedList<Connection> pool = new LinkedList<>();/** * @author PeterS * @date 2016年5月5日 * @param  * @description */public ConnectionPool(int initialSize ) {if (initialSize>0) {for(int i=0;i<initialSize;i++){pool.addLast(ConnectionDriver.createConnection());}}}public void releaseConnection(Connection connection) {if (connection!=null) {synchronized (pool) {//连接释放后需要通知,这样其他消费者可以感知到连接池中已经归还了一个连接pool.addLast(connection);pool.notifyAll();}}}public Connection fetchConnection(long millis) throws InterruptedException{synchronized (pool) {//完全超时if (millis<=0) {while (pool.isEmpty()) {pool.wait();}return pool.removeFirst();}else{ //这是一个超时等待模型,当等待超过一定的时常millis,就直接返回long future = System.currentTimeMillis()+millis;long remaining = millis;while (pool.isEmpty()&&remaining>0) {pool.wait(remaining);remaining = future-System.currentTimeMillis();}Connection result = null;if (!pool.isEmpty()) {result=pool.removeFirst();}return result;}}} }
ConnectionDriver.java
public class ConnectionDriver {static class ConnectionHandler implements InvocationHandler{ /** * @author PeterS * @date 2016年5月5日 * @param  * @description */@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (method.getName().equals("commit")) {TimeUnit.SECONDS.sleep(100);}return null;}}public static final Connection createConnection() {return (Connection)Proxy.newProxyInstance(ConnectionDriver.class.getClassLoader(),new Class<?>[]{Connection.class}, new ConnectionHandler());} }
ConnectionPoolTest.java
public class ConnectionPoolTest {static ConnectionPool pool = new ConnectionPool(10);static CountDownLatch start = new CountDownLatch(1);static CountDownLatch end; /** * @author PeterS * @date 2016年5月5日 * @param  * @description */public static void main(String[] args) throws Exception{int threadCount = 10;end = new CountDownLatch(threadCount);int count = 20;AtomicInteger got = new AtomicInteger();AtomicInteger noGot = new AtomicInteger();for(int i = 0;i<threadCount;i++){Thread thread = new Thread(new ConnectionRunner(count, got, noGot),"ConnectionRunnerThread");thread.start();}start.countDown();end.await();System.out.println("total invoke:"+(threadCount*count));System.out.println("got connection:"+got);System.out.println("not got connection:"+noGot);}static class ConnectionRunner implements Runnable{int count;AtomicInteger got;AtomicInteger noGot;/** * @author PeterS * @date 2016年5月5日 * @param  * @description */public ConnectionRunner(int count,AtomicInteger got,AtomicInteger noGot) {this.count=count;this.got=got;this.noGot=noGot;} /** * @author PeterS * @date 2016年5月5日 * @param  * @description */@Overridepublic void run() {try {start.await();} catch (Exception e) {}while (count>0) {try {Connection connection = pool.fetchConnection(1000);if (connection!=null) {try {connection.createStatement();connection.commit();} finally{pool.releaseConnection(connection);got.incrementAndGet();}}else {noGot.incrementAndGet();}} catch (Exception e) {}finally {count--;}}end.countDown();}} }