Java多线程编程核心技术
一、Java多线程技能
1.1停止线程
在Java中有以下3种方法可以终止正在运行的线程:
①使用退出标志,使线程正常退出,也就是当run方法完成后线程终止;
②使用stop方法强行终止线程,但是不推荐,该方法和stop、suspend、resume一样,已经被废弃;
③使用interrupt方法中断线程;
1.1.1判断线程是否使停止状态
Thread.java类中提供了两种方法:
①Thread.interrupted():测试当前线程是否已经中断,静态方法,会清除当前线程的状态(默认为false);
②this.isinterrupted():测试调用线程是否已经中断,实例方法,不会清除状态;
1.1.2异常法停止线程
通过第①种方法,判断线程,run方法完成后终止线程
public class MyThread extends Thread{
public void run(){
super.run();
for(int i=0;i<50000;i++){
if(this.isinterrupted()){
System.out.println("已经是停止状态了!我要退出!");
break;
}
}
System.out.println("run方法执行结束");
}
}
通过抛出异常,直接结束线程,不执行后续代码:
public class MyThread extends Thread{
public void run(){
super.run();
try{
for(int i=0;i<50000;i++){
if(this.isinterrupted()){
System.out.println("已经是停止状态了!我要退出!");
throw new InterruptedException();
}
}
System.out.println("run方法执行结束");
}catch(InterruptedException e){
System.out.println("进去run方法中的catch了");
}
}
}
1.1.3在沉睡中停止
当线程处于sleep()状态,调用interrupt()方法,run方法中会立刻抛出异常,停止线程;
当线程先调用interrupt()方法,在执行sleep()方法时,也会立刻抛出异常,停止线程;
1.1.4暴力停止
调用stop()方法, 会抛出ThreadDeath异常;
1.2暂停线程
1.2.1suspend与resume方法的使用
通过suspend方法暂停线程,通过resume方法启动线程;
缺点:独占!!!,数据不同步
如果使用不当,极易造成公共的同步对象的独占,导致其他线程无法访问公共同步对象。
1.3yield()方法
yield()方法的作用是放弃当前的CPU资源,将它让给其他任务去执行。但放弃的时间不确定,有可能刚刚放弃,又马上获取到时间片;
1.4线程的优先级
优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象的任务;当然,优先级还具有“随机性”;
线程优先级分为1-10,数值越大,优先级越高;
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
1.5守护线程
在Java线程中有两种线程,一种是用户线程,一种是守护线程;它的特性是陪伴,当进程中不存在非守护线程了,则守护线程自动销毁;
典型的守护线程就是垃圾回收线程;通过this.setDaemon(true)来设置守护线程;
二、对象及变量的并发访问
2.1synchronized同步方法
关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法当作锁;
2.1.2synchronized锁重入
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象锁的。
2.1.3出现异常,锁自动释放
当一个线程执行的代码出现异常时,其所持有的锁会自动释放;
2.2synchronized同步代码块
通过“对象监视器”,synchronized(对象),来保证同步和效率;
关键字synchronized还可以应用在static静态方法上,如果这样写,那就是对当前*.java文件对应的Class类进行持锁;
可以使用常量池作为加锁对象;
2.3死锁
Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成。
可以通过jdk自带的工具来监测是否有死锁现象:
#通过jps找出所有java进程id
jps
#执行jstack命令,打印进程的线程状态
jstack -l id
2.4volatile关键字
关键字volatile的主要作用是使变量在多个线程间可见;强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。
使用volatile关键字增加了实例变量在多个线程之间的可见性。但是volatile关键字最致命的缺点是不支持原子性。
下面将关键字synchronized和volatile进行比较:
①关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰变量,而synchronized可以修饰方法和代码块。
②多线程访问volatile不会发生堵塞,而synchronized会;
③volatile保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性;
非原子性说明:
public class MyThread extends Thread{
volatile public static int count;
private static void addCount(){
for(int i=0;i<100;i++){
count++;
}
}
@Override
public void run(){
addCount();
}
}
public class Run{
public static void main(String[] args){
MyThread[] mythreadArray=new MyThread[100];
for(int i=0;i<100;i++){
mythreadArray[i]=new MyThread();
}
for(int i=0;i<100;i++){
mythreadArray[i].start();
}
}
}
测试结果,并非预料的10000,造成该结果的原因,就是因为count++是非原子性操作;
可以使用synchronized实现同步,也可以通过AtomicInteger原子类进行实现。
三、线程间通信
3.1等待/通知机制的实现
方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在代码行处停止执行,直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁。即只能在同步方法或者同步块中调用wait()方法,否则抛出异常。
方法notify()也要在同步代码块中调用,该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出一个呈wait状态的线程,对其发出通知notify,并使其获得该对象的对象锁。需要说明的是,notify()方法后,不会立着释放锁,呈wait()状态的线程也并不能马上获取该对象锁,需要等到执行notify()方法的线程执行完,才会释放。而一个线程执行wait()方法后,立刻释放对象锁。
当线程呈wait()状态时,调用对象的interrupt(),也会抛出InterruptedException异常。
3.1.1方法wait(long)的使用
带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒;
3.1.2生产者/消费者模式的实现
一生产与一消费
//生产者
public class B{
private String lock;
public B(String lock){
this.lock=lock;
}
public void setValue(){
try{
synchronized(lock){
if(!ValueObject.value.equals("")){
lock.wait();
}
String value=System.currentTimeMillis()+"_"+System.nanoTime();
ValueObject.value=value;
lock.notify();
}
}catch(InterruptException e){
e.printStackTrace();
}
}
}
//消费者
public class C{
private String lock;
public C(String lock){
this.lock=lock;
}
public void setValue(){
try{
synchronized(lock){
if(ValueObject.value.equals("")){
lock.wait();
}
System.out.println("get 的值是 "+ValueObject.value)
ValueObject.value="";
lock.notify();
}
}catch(InterruptException e){
e.printStackTrace();
}
}
}
//存储值得对象
public class ValueObject{
public static String value="";
}
//测试代码
public class ThreadB extends Thread{
private B b;
public ThreadB(B b){
this.b=b;
}
public void run(){
while(true){
b.setValue();
}
}
}
public class ThreadC extends Thread{
private C c;
public ThreadC(C c){
this.c=c;
}
public void run(){
while(true){
b.getValue();
}
}
}
3.1.3通过管道进行线程间通信
class ReadData{
public void readMethod(PipedInputStream in){
try{
System.out.println("开始读:");
byte[] byteArray=new byte[20];
int readLine = in.read(byteArray);
while (readLine!=-1){
String newData = new String(byteArray, 0, readLine);
System.out.print(newData);
readLine=in.read(byteArray);
}
System.out.println();
in.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
class WriteDate{
public void writeMethod(PipedOutputStream out){
try{
System.out.println("开始写:");
for(int i=0;i<100;i++){
String outData=""+(i+1);
out.write(outData.getBytes());
System.out.print(outData);
}
System.out.println();
out.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
//测试代码
public class ThreadTrans2 {
public static void main(String[] args) throws Exception {
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
in.connect(out);
ReadData readData = new ReadData();
WriteDate writeDate = new WriteDate();
ThreadWrite threadWrite = new ThreadWrite(writeDate, out);
ThreadRead threadRead = new ThreadRead(readData, in);
threadRead.start();
threadWrite.start();
}
}
class ThreadRead extends Thread{
private ReadData readData;
private PipedInputStream in;
public ThreadRead(ReadData readData,PipedInputStream in){
this.readData=readData;
this.in=in;
}
@Override
public void run() {
readData.readMethod(in);
}
}
class ThreadWrite extends Thread{
private WriteDate writeDate;
private PipedOutputStream out;
public ThreadWrite(WriteDate writeDate,PipedOutputStream out){
this.writeDate=writeDate;
this.out=out;
}
@Override
public void run() {
writeDate.writeMethod(out);
}
}
3.2方法join的使用
join(long)与sleep(long),都是等待一段时间,但join方法内部是通过wait(long)实现,所以具有释放锁的特点。
当b线程等待a.join()时,如果调用interrupt()方法,b会抛出异常,a正常执行;
3.3ThreadLocal的使用
ThreadLocal是JDK通过public static 变量的形式,实现了线程间私有数据的存放,底层其实是一个的map,key值为当前线程;