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);
    }
}
View Code

  在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*/
        }

    }
}
View Code

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

}
View Code

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 }
View Code

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 }
View Code
原文地址:https://www.cnblogs.com/laoyin666/p/9413607.html