java多线程编程核心技术
一、java多线程基础
1.1 进程和线程
进程是一次程序的执行,线程是进程中一个个独立运行的子任务。一个进程包含多个线程。
1.2 线程的实现方式
(1)继承Thread类
(2)实现Runnable接口
1.3 成员变量和局部变量
成员变量,存在于堆中,多线程共享,存在安全问题。局部变量,存在于工作空间也可理解为栈中,栈是私有的,每个线程执行时将会把局部变量放在各自栈帧的工作内存中,线程间不共享,故不存在线程安全问题。
1.4 常见方法
isAlive() : 判断当前线程是否处于活动状态
sleep(long ms) : 使线程处于休眠状态(暂停执行),线程不会释放对象锁
(对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的,wait会释放锁)
1.5 停止正在运行的线程
1)、 使用退出标志,使线程运行完run方法,正常结束
2)、 使用stop方法强制终止线程(不推荐,已标记为废弃的方法,可能会产生不可预料的结果)
3)、使用interrupt方法终止线程
Interrupt(),线程不会马上停止,只是给线程打了一个停止的标记,并不是真的停止线程。
Interrupted():测试线程是否已经中断,具有清除功能,第二次调用返回false
isInterrupted():测试线程是否已经中断
Interrupted+return也可中断线程(建议使用异常的方式)
另参考----尊重别人的劳动成果:http://www.cnblogs.com/w-wfy/p/6415005.html
1.6 暂停线程
1)、Suspend与resume
Suspend暂停线程与resume方法恢复线程
注:因为暂停时未释放锁,容易造成公共的同步对象的独占,使其他线程无法访问公共同步对象,也容易因暂停而导致数据不同步
2)、yield
它的作用是放弃cpu资源,让其他任务去占用cpu资源。但放弃的时间是不确定的,有可能刚刚放弃,又马上又获得了cpu资源
1.7 线程优先级
1)setpriority();优先级较高线程获得cpu资源较多,也就是cpu优先执行优先级较高的线程中的任务(规则性),但优先级较高的线程不一定每次都先执行完(随机性)
2)线程优先级具有继承性,比如A线程中调用B线程,则AB的优先级是一样的
二、对象与变量的并发访问
2.1、synchronized
1)、锁对象
1. synchronized加在方法上,锁为对象实例,同一个实例就是同一把锁。不同对象,不同的锁,Synchronized(this),this就代表对象实例。注意,Synchronized(Class)是以字节码作为锁,一个类只存在一份class字节码,另外,synchronized加载static修饰的方法上,锁也是字节码class,分析同步问题时,重点分析锁对象是什么。线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新获取最新的值,可以看到synchronized能够实现可见性。同时,由于synchronized具有同步锁,所以它也具有原子性
2. 锁也可为任意对象,比如定义一个成员变量string,因为成员变量,同一个对象共享,所以Synchronized(string)可以作为同步锁
3. synchronized锁重入,当线程获取到锁后,再次请求此对象的锁可以再次获得,即同步方法中(加了Synchronized修饰的方法),可以调用其他的同步方法
4. 出现异常,锁自动释放
5. 同步块,可以只同步主要的代码块,可以提高效率
6. 一半同步,一半异步,即一个线程获取到锁后访问同步方法时,其他线程因无法获取锁,但可以访问其他不是同步的方法(未加Synchronized的方法)
7. 内部类,或静态内部类,可以理解为成员变量即可。
2.1、volatile
1. 关键字volatile的作用是,强制从公共堆中获取变量,而不是私有栈中,可实现变量在多线程间可见
线程中的三个概念,原子性问题,可见性问题,有序性问题,volatile保证了可见性,但是非原子性,有一定的顺序性
当且仅当满足以下所有条件时,才应该使用 volatile 变量:
- 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
- 该变量没有包含在具有其他变量的不变式中。
2. 关键子synchronized和volatile进行一下比较:
1)关键字volatile是线程同步的轻量级实现,所以volatile性能背定比synchronized要好,并且volatile只能修饰于变量, isynchronizedi以修饰方法,以及代码块。随着JDK新版本的发布, synchronized关键7在执行效率上得到很大提升,在开发中使用synchronized关键子的比率还是比较大的。
2)多线程访问volatile不会发生阻塞, synchronized会出现阻塞。
3) volatile能保证数据的可见性,但不能保原子性; synchronizedi以保原子性.也可以同接保证可见性,因为它会将松有内存和公共内存中的数据做同步。
4)重申一下,关键子volatile解决的是变量在多个线程之间的可见性;synchronized解决是多线程访问资源的同步性
参考:内存模型是理解volatile的关键
https://www.cnblogs.com/dolphin0520/p/3920373.html#!comments
3. 例子
package TestItems.test.core; public class PrintString implements Runnable{ /*volatile*/ private Boolean isContinue=true; public Boolean getIsContinue() { return isContinue; } public void setIsContinue(Boolean isContinue) { this.isContinue = isContinue; } public void printMethod() { try { while(isContinue) { System.out.println(Thread.currentThread().getName()); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } public void run() { printMethod(); } } package TestItems.test.core; public class VolatileTest { public static void main(String[] args) throws Exception { PrintString p=new PrintString(); new Thread(p).start(); //p.printMethod(); Thread.sleep(3000); p.setIsContinue(false); } }
在win7的jdk64位环境中,运行正常,但是在JVM设置为Server服务器中会出现死循环(一直没有测试成功),解决方法是加volatle关键字
三、线程间的通信
3.1 等待/通知机制
1)、Wait()和notify()都是Object中的方法。wait使线程停止运行,会释放掉锁,notify()使停止的线程继续运行,等notify所在的同步块执行完,才会释放掉锁。但两个方法的使用前提是,都必须先获得对象级别的锁,否则会抛出IllegalMonitorStateException异常,它是RuntimeException的子类。当线程呈wait状态时,调用interrupt()时会出现InterruptedException遗传。
2)、等待通机制的实现
package TestItems.test.core; import java.text.SimpleDateFormat; import java.util.Date; public class MyThreadWait extends Thread { private Object lock; public MyThreadWait(Object lock) { super(); this.lock = lock; } @Override public void run() { try { synchronized (lock) { SimpleDateFormat smft = new SimpleDateFormat("YYYY年MM月dd日 HH:mm:ss"); System.out.println("等待开始:" + smft.format(new Date())); lock.wait(); System.out.println("等待结束:" + smft.format(new Date())); } } catch (Exception e) { e.printStackTrace(); } } } package TestItems.test.core; import java.text.SimpleDateFormat; import java.util.Date; public class MyThreadNotify extends Thread { private Object lock; public MyThreadNotify(Object lock) { super(); this.lock = lock; } @Override public void run() { try { synchronized(lock) { SimpleDateFormat smft = new SimpleDateFormat("YYYY年MM月dd日 HH:mm:ss"); System.out.println("唤醒开始:" + smft.format(new Date())); lock.notify(); System.out.println("唤醒结束:" + smft.format(new Date())); } } catch (Exception e) { e.printStackTrace(); } } } package TestItems.test.core; public class OneToOneWaitNotify { public static void main(String[] args) { Object lock = new Object(); try { MyThreadWait wait = new MyThreadWait(lock); MyThreadNotify notify = new MyThreadNotify(lock); wait.start(); Thread.sleep(3000); notify.start(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); /* 等待开始:2018年08月02日 14:49:41 唤醒开始:2018年08月02日 14:49:44 唤醒结束:2018年08月02日 14:49:44 等待结束:2018年08月02日 14:49:44*/ } } }
3)、生产消费者模式:多生产和多消费时,容易出现“假死”现象,即notify唤醒的可能是同类比如生产者唤醒的不是消费者而是生产者,导致所有线程都处于等待状态。解决方式使用notifall唤醒所有线程。
4)、通过管道进行线程间通信
JDK中提供了退格类来是线程间可以通信
PipedInputStream和PipedOutputStream
PipedReader和PipedWriter
5)、实战:等待通知交叉备份
package TestItems.test.core; public class CommonResoure { /* volatile */ private int comm = 10; volatile public boolean flag = true; public void add() { try { synchronized (CommonResoure.class) { //如果是if,经过单次判断后不会再进行判断,但是在线程唤醒后需要再次判断的 while (!flag) { CommonResoure.class.wait(); } comm++; System.out.println("comm++:" + comm); flag = false; CommonResoure.class.notifyAll(); } } catch (InterruptedException e) { e.printStackTrace(); } } public void decrease() { try { synchronized (CommonResoure.class) { while (flag) { CommonResoure.class.wait(); } comm--; System.out.println("comm--:" + comm); flag = true; CommonResoure.class.notifyAll(); } } catch (InterruptedException e) { e.printStackTrace(); } } public int getComm() { return comm; } public void setComm(int comm) { this.comm = comm; } } package TestItems.test.core; public class Comsume implements Runnable{ private CommonResoure res; public Comsume(CommonResoure res) { this.res=res; } public void run() { res.decrease(); } } package TestItems.test.core; public class Produce implements Runnable{ private CommonResoure res; public Produce(CommonResoure res) { this.res=res; } public void run() { res.add(); } } package TestItems.test.core; public class ProduceComsumeTest { public static void main(String[] args) throws InterruptedException { CommonResoure comm=new CommonResoure(); Produce p=new Produce(comm); Comsume c=new Comsume(comm); for(int i=0;i<50;i++) { new Thread(p,"name"+i).start(); new Thread(c,"name"+i).start(); } Thread.sleep(2000); } }
3.2 join()
在主线程中启用子线线程,有时子线程中需要进行长时间的运算,而通常主线程在子线程运行完之前就就是,但有时需要等到子线程中的结果,那么可以使用join方法,它的作用就是等待线程对象销毁。Join与interrupt相遇也会出现InterruptedException异常。Join内部是使用wait来实现的,所有具有释放锁的特点
3.3 ThreadLocal
变量共享一般使用public static的形式,如果想实现每个线程都有自己的共享变量,就可以使用ThreadLocal。它主要就是来解决每个线程绑定自己的值。
1)、 示例(后续在写)
2)、解决get获取为null的问题(示例后续写)
继承threadLocal重写initialValue方法,返回默认值
3)、InheritableThreadLocal可以在子线程中取到从父线程中继承的值(示例后续在写)
四、Lock的使用
ReentrantLock
ReentrantReadWriteLock
4.1 ReentrantLock
1)、ReentrantLock跟synchronizd一样可以实现线程间的同步互斥,但是更加强大。
Lock lockins=new ReentrantLock();
// 获取对象锁
lockins.lock();
// 释放锁
lockins.unlock();
2)、关键字synchronized与wait()和notify(/notifyAll()方法相结合可以实现等待/通知模式,类ReentrantLock也可以实现同样的功能,但需要借助于Condition(condition.await()与condition.singal)对象。Condition类是在JDK5中出现的技术,使用它有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象里面可以创建多个Condition (即对象监视器)实例,线程对象可以注册在批定的Condition中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。
在使用notify()/notifyAllQ)方法进行通知时,被通知的线程却是由JVM随机选择的。但使用ReentrantLock结合Condition类是可以实现前面介绍过的“选择性通知",这个功能是非常重要的,而且在Condition类中是默认提供的。
synchronized就相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在它一个对象的身上。线程开始notifyAll()时,需要通知所有的WAITING线程,没有选择权,会出现非常大的效率问题。
注意,condition.await()方法在调用前需使用lock.lock();获取对象锁,否则会抛IllegalMonitorStateException异常
正确使用condition实现等待/通知 示例
1 package test.lock; 2 3 import java.util.concurrent.locks.Condition; 4 import java.util.concurrent.locks.Lock; 5 import java.util.concurrent.locks.ReentrantLock; 6 7 public class CommonResoure { 8 private int comm = 10; 9 volatile public boolean flag = true; 10 11 Lock lock = new ReentrantLock(); 12 Condition conditionA = lock.newCondition(); 13 Condition conditionB = lock.newCondition(); 14 15 public void add() { 16 try { 17 lock.lock(); 18 while (!flag) { 19 // 类似于synchronized中锁的wait方法,会释放锁 20 conditionA.await(); 21 } 22 comm++; 23 System.out.println("comm++:" + comm); 24 flag = false; 25 // 在A中唤醒B,因为使用了不同的condition,所有直接使用对应的condition即可 26 // 如使用同一个condition,而又不用signalAll,有可能导致唤醒的是同类,最终到导致都进入了wait状态 27 conditionB.signal(); 28 } catch (InterruptedException e) { 29 e.printStackTrace(); 30 }finally { 31 lock.unlock(); 32 } 33 } 34 35 public void decrease() { 36 try { 37 lock.lock(); 38 while (flag) { 39 conditionB.await(); 40 } 41 comm--; 42 System.out.println("comm--:" + comm); 43 flag = true; 44 conditionA.signal(); 45 } catch (InterruptedException e) { 46 e.printStackTrace(); 47 }finally { 48 lock.unlock(); 49 } 50 } 51 52 public void singalall_A() { 53 try { 54 lock.lock(); 55 conditionA.signalAll(); 56 try { 57 Thread.sleep(3000); 58 } catch (InterruptedException e) { 59 // TODO Auto-generated catch block 60 e.printStackTrace(); 61 } 62 }finally { 63 lock.unlock(); 64 } 65 } 66 67 public void singalall_B() { 68 try { 69 lock.lock(); 70 conditionB.signalAll(); 71 }finally { 72 lock.unlock(); 73 } 74 } 75 76 public int getComm() { 77 return comm; 78 } 79 80 public void setComm(int comm) { 81 this.comm = comm; 82 } 83 84 } 85 86 package test.lock; 87 88 public class Produce implements Runnable{ 89 90 private CommonResoure res; 91 92 public Produce(CommonResoure res) { 93 this.res=res; 94 } 95 96 public void run() { 97 res.add(); 98 } 99 100 public CommonResoure getRes() { 101 return res; 102 } 103 104 public void setRes(CommonResoure res) { 105 this.res = res; 106 } 107 108 109 } 110 111 package test.lock; 112 113 public class Comsume implements Runnable{ 114 115 private CommonResoure res; 116 117 public Comsume(CommonResoure res) { 118 this.res=res; 119 } 120 121 public void run() { 122 res.decrease(); 123 } 124 } 125 126 package test.lock; 127 /** 128 * 生成消费模式,交替出现 129 * @author sjt 130 * 131 */ 132 public class ProduceComsumeTest { 133 134 public static void main(String[] args) throws InterruptedException { 135 CommonResoure comm=new CommonResoure(); 136 Produce p=new Produce(comm); 137 Comsume c=new Comsume(comm); 138 for(int i=0;i<50;i++) { 139 new Thread(p,"name"+i).start(); 140 new Thread(c,"name"+i).start(); 141 } 142 Thread.sleep(3000); 143 //p.getRes().singalall_A(); 144 145 Thread[] t=new Thread[Thread.currentThread().getThreadGroup().activeCount()]; 146 Thread.currentThread().getThreadGroup().enumerate(t,true); 147 for(Thread temp:t){ 148 if(temp!=null) { 149 System.out.println(temp.getName()); 150 } 151 } 152 } 153 154 155 }
4.2 ReentrantReadWriteLock
读写锁,涉及到的写的都是互斥的,读读是可以共存的
示例:
用法跟ReentrantLock一样
ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
五、线程状态和线程组
5.1 线程状态
参考:https://blog.csdn.net/pange1991/article/details/53860651
5.2 线程异常处理
1 package test.lock; 2 3 public class ThreadExceptinSolveRunnable implements Runnable{ 4 5 String userName=null; 6 7 public void run() { 8 System.out.println(userName.hashCode()); 9 } 10 11 } 12 13 package test.lock; 14 15 import java.lang.Thread.UncaughtExceptionHandler; 16 17 public class ThreadExceptTest { 18 public static void main(String[] args) { 19 20 ThreadExceptinSolveRunnable solve=new ThreadExceptinSolveRunnable(); 21 Thread t=new Thread(solve); 22 t.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { 23 public void uncaughtException(Thread t, Throwable e) { 24 e.printStackTrace(); 25 System.out.println("线程出现异常:"+t.getName()); 26 } 27 }); 28 t.start(); 29 30 //也可以设置所有线程的默认的处理器,但是需要继承Thread的方式去实现线程 31 //t.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler); 32 } 33 }