Java并发编程的艺术(上)

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() {
@Override
public void run() {
// TODO Auto-generated method stub
int 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 die
System.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() {
@Override
public 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();
}
 
}
这段代码就可能出现死锁。

如何有效的避免死锁呢?首先要看看死锁的四个必要条件:互斥、请求与保持、不可剥夺、循环等待。
所以避免死锁的方法有:
避免一个线程获取多个锁
尽量保证每个锁只占有一个资源
尝试使用定时锁,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());
}
}
 
}

2. 在不同的JVM和操作系统中,线程的规划存在差异,有些操作系统可能会忽略线程优先级的设定;
3. 线程的状态
六种:
状态名称 说明
NEW 初始状态,线程被构建,但是还没有调用start()方法
RUNNABLE 就绪状态和运行中状态统称runnable
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 等待状态
TIME_WAITING 超时等待状态,可在指定的时间自行返回
TERMINATED 终止状态,表示当前的线程已经执行完毕了
4. 查看线程的状态:
用jps命令查看线程id;然后用jstack命令+线程id查看;


5. 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
*/
@Override
public 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
*/
@Override
public 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
}
}
}
 
}

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
*/
@Override
public 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
*/
@Override
public 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();
}
}
 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值