Java并发编程基础

来源:互联网 发布:unity3d小地图制作 编辑:程序博客网 时间:2024/05/21 11:15

线程优先级: 一共5级

线程的状态:

NEW: 初始状态,线程被创建,还未执行start()

RUNNABLE:运行状态,包括运行态和就绪态

BLOCKED:阻塞状态,表示线程阻塞于锁,阻塞在线程进入synchronized代码快的地方

WAITING:等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或者中断)

TIME_WAITING:超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的

TERMINATED: 终止状态,表示当前线程已经执行完毕。


jps(Java Virtual Machine Process Status Tool)是JDK 1.5提供的一个显示当前所有java进程pid的命令,简单实用,非常适合在linux/unix平台上简单察看当前java进程的一些简单情况。 



跑一个java线程状态的程序,执行jps,获取到线程pid,然后jstack pid查看线程的状态。



Daemon线程

Daemon线程是支持型线程,主要作为后台调度或支持型工作,当java虚拟机中不存在非daemon线程时,java虚拟机将会推出。谨记,不要用finally代码块清理daemon线程,因为当java虚拟机退出时,是不会执行finallly代码块的


启动和终止线程

线程在init时,需要提供线程所需要的属性,如所属线程组,线程优先级,是否为Daemon,查看线程初始化源码,一个新构造的线程对象是由其parent线程来进行空间分配的,child继承parent的,是否为Daemon,优先级,加载资源的contextClassLoader,及inheritableThreadLocals。初始化后,线程在堆中等待运行。

start()启动线程

中断

中断可以理解为线程的一个标志位属性,许多声明抛出InterrupptedException的方法在抛出异常之前,java虚拟机会先将线程中的中断标志位清除,对于sleep中的线程,调用interrupt()方法,中断标志位会被清除,而runnable状态的线程调用interrupt()方法,中断标志位不会被清除

public class Interrupted {public static void main(String[] args){// SleepThread一直不停得尝试睡眠Thread sleepThread=new Thread(new SleepThread());sleepThread.setDaemon(true);Thread busyThread=new Thread(new BusyThread());busyThread.setDaemon(true);sleepThread.start();busyThread.start();SleepUtils.second(5);sleepThread.interrupt();busyThread.interrupt();System.out.println("SleepThread interrupted is "+ sleepThread.isInterrupted());System.out.println("BusyThread interrupted is "+busyThread.isInterrupted());SleepUtils.second(2);}static class SleepThread implements Runnable {public void run() {while (true) {SleepUtils.second(10);}}}static class BusyThread implements Runnable {public void run() {while (true) {}}}public static class SleepUtils {public static void second(long seconds) {try {Thread.sleep(seconds);} catch (InterruptedException e) {// do nothing}}}}



输出结果

SleepThread interrupted is false
BusyThread interrupted is true


过期的suspend(),resume(),和stop()

分别对线程执行暂停,重启,和停止,以suspend为例,它在暂停期间不会释放已经占有的资源(比如锁),而占着锁进入睡眠状态很容易引起死锁。所以这些方法被deprecated了,这些方法可以由wait()和notify()来代替。

安全的终止线程

可以通过interrupt或者利用boolean变量来控制是否需要停止任务

import java.util.concurrent.TimeUnit;public class Shutdown {public static void main(String[] args) throws InterruptedException {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();}public static class Runner implements Runnable {private long i;private volatile boolean on = true;public void run() {while (on && !Thread.currentThread().isInterrupted()) {i++;}System.out.println("Count i=" + i);}public void cancel() {on = false;}}}

输出:

Count i=446723267
Count i=456889859

这种通过标识位或者中断操作的方式,能够使线程在终止时有机会去清理资源,更加安全的终止线程。


线程间通信

javac Synchronized.java,对类文件进行编译,生成Synchronized.class

javap -v Sycnhronzied.class 对字节码文件进行反编译


public class Synchronized {public static void main(String[] args) {synchronized (Synchronized.class) {}m();}public static synchronized void m() {}}//反编译后代码public static void main(java.lang.String[]);    descriptor: ([Ljava/lang/String;)V    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=2, locals=3, args_size=1         0: ldc           #2                  // class Synchronized         2: dup         3: astore_1         4: monitorenter // monitorenter: 监视器进入,获取锁         5: aload_1         6: monitorexit  // monitorexit:监视器退出,释放锁         7: goto          15        10: astore_2        11: aload_1        12: monitorexit        13: aload_2        14: athrow        15: invokestatic  #3                  // Method m:()V        18: returnpublic static synchronized void m();    descriptor: ()V    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED    Code:      stack=0, locals=0, args_size=0         0: return


对于同步块的使用,用到了monitorenter和monitorexit,而同步方法是用方法上的修饰符ACC_SYNCHRONIZED来完成的。本质都是对一个对象的监视器(monitor)进行获取
下图解释了这个获取过程是具有排他性的,


如果获取监视器失败,线程将进入阻塞队列。当获取锁成功的线程释放监视器后,同步队列中的阻塞线程再次尝试获取锁。

等待/通知机制
notify ,notifyAll, wait, wait(long)方法是object的方法,所以每一个对象都具有这些方法,wait()方法调用后,会释放锁,这是不同于suspend()的地方,suspend()不会释放锁,所以容易造成死锁,线程A调用对象O的wait()方法进入阻塞状态,而线程B调用O的notify()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。线程AB就是通过对象O来通知彼此交互工作的。

import java.text.SimpleDateFormat;import java.util.Date;import java.util.concurrent.TimeUnit;public class WaitNotify {static boolean flag = true;static Object lock = new Object();public static void main(String[] args) throws InterruptedException {Thread waitThread = new Thread(new Wait(), "WaitThread");waitThread.start();TimeUnit.SECONDS.sleep(1);Thread notifyThread = new Thread(new Notify(), "NotifyThread");notifyThread.start();}static class Wait implements Runnable {public void run() {// 加锁,拥有lock的monitorsynchronized (lock) {// 当条件不满足时,继续wait,并且释放lock的锁while (flag) {try {System.out.println(Thread.currentThread() + " flag is true. wait@"+ new SimpleDateFormat("HH:mm:ss").format(new Date()));lock.wait();} catch (InterruptedException e) {// do nothing}}// 条件满足,完成工作System.out.println(Thread.currentThread()+ " flag is false. running@"+ new SimpleDateFormat("HH:mm:ss").format(new Date()));}}}static class Notify implements Runnable {@Overridepublic void run() {// 加锁,拥有lock的monitorsynchronized (lock) {// 获取lock的锁,然后进行notify,通知时不会释放lock的锁,此时,线程由waiting状态变成blocked状态// 也就是从等待队列到了同步队列中// 直到当前线程释放了lock之后,WaitThread才能从wait方法中返回System.out.println(Thread.currentThread()+ " hold the lock. notify@"+ new SimpleDateFormat("HH:mm:ss").format(new Date()));lock.notify();flag = false;try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {}}synchronized (lock) {System.out.println(Thread.currentThread()+ " hold the lock again. sleep@"+ new SimpleDateFormat("HH:mm:ss").format(new Date()));try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {}}}}}

输出:
Thread[WaitThread,5,main] flag is true. wait@16:42:23
Thread[NotifyThread,5,main] hold the lock. notify@16:42:24
Thread[NotifyThread,5,main] hold the lock again. sleep@16:42:26 
Thread[WaitThread,5,main] flag is false. running@16:42:28

34行可能会互换
注意一下细节:
1,使用wait(), notify()和notifyAll()时需要对调用对象加锁
2,调用wait()方法后,线程状态由running变成waiting,并将当前线程放置到等待队列,同时释放所占有的锁
3,notify和notifyAll方法调用后,等待线程不会从wait方法中立即返回,需要调用notify和notifyAll的线程释放锁之后,等待线程才有机会从wait()返回
4,notify方法将等待队列中的一个等待线程从等待队列中移到同步队列中,被移动线程状态由waiting变成blocked
5,从wait()方法返回的前提是获得了调用对象的锁



等待/通知的经典范式
等待方:
1>获取对象锁
2>如果条件不满足,调用对象的wait()方法,被通知后仍要检查条件
3>条件满足则执行相应的逻辑
对应伪代码为:
  
synchronized(对象){     while(条件不满足){          对象.wait()      }      对应的逻辑处理}

通知方:
1> 获得对象的锁
2>改变条件
3>通知所有等待在对象上的线程
对应伪代码为:
synchronized(对象){    改变条件    对象.notify()}

管道输入输出流

管道输入输出流与文件输入输出流和网络输入输出流之间的区别主要是,它主要用于线程之间的数据传输,而传输媒介为内存
import java.io.IOException;import java.io.PipedReader;import java.io.PipedWriter;public class Piped {public static void main(String[] args) throws IOException {PipedWriter out = new PipedWriter();PipedReader in = new PipedReader();// 将输出流和输入流进行连接,否则在使用时会抛出IOExceptionout.connect(in);Thread printThread = new Thread(new Print(in), "PrintThread");printThread.start();int receive = 0;try {while ((receive = System.in.read()) != -1) {out.write(receive);}} finally {out.close();}}static class Print implements Runnable {private PipedReader in;public Print(PipedReader in) {this.in = in;}public void run() {int receive = 0;try {while ((receive = in.read()) != -1) {System.out.println((char) receive);}} catch (IOException e) {}}}}

对于Piped类型的流,必须先要进行绑定,也就是connect()

Thread.join的使用

public class Join {public static void main(String[] args) {Thread previous = Thread.currentThread();for (int i = 0; i < 10; i++) {//每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能从等待中返回Thread thread=new Thread(new Domino(previous),String.valueOf(i));thread.start();previous=thread;}System.out.println(Thread.currentThread().getName()+" terminated");}static class Domino implements Runnable {private Thread thread;public Domino(Thread thread) {this.thread = thread;}public void run() {try {thread.join();} catch (InterruptedException e) {}System.out.println(Thread.currentThread().getName() + " terminated.");}}}

输出:
main terminated
0 terminated.
1 terminated.
2 terminated.
3 terminated.
4 terminated.
5 terminated.
6 terminated.
7 terminated.
8 terminated.
9 terminated.

join方法源码与前面提到的等待/通知经典范式一致,即加锁,循环等待, 和处理逻辑

查看join方法的源码,
// 加锁当前线程对象public final synchronized void join() throws InterruptedException {    //条件不满足,等待    while(isAlive){        wait(0);    }    //条件满足,方法返回}

等待超时模式
前面介绍的等待/通知经典范式,即加锁,条件循环和处理逻辑三个步骤,这种范式无法做到超时等待。 我们只需要对经典范式稍作改动,就可以实现超时等待范式。
定义如下变量:
等待持续时间:REMAINING=T
超时时间:FUTURE=now+T
仅需要执行wait(REMAINING),在wait(REMAINING)返回之后执行,REMAINING=FUTURE-NOW,如果REMAINING小于0,说明已经超时,直接退出,处理逻辑,否则继续wait(REMAINING)
伪代码如下:
public synchronized Object get(long millis) throws InterruptedException {        long future=System.currentTimeMillis()+millis;        long remaining=millis;        //当超时大于0并且result返回值不满足要求时        while(result==null&&remaining>0){                wait(remaining);                remaining=future-System.currentTimeMillis();        }        return result;}



下文中将要使用CountDownLatch类,可以参考该篇文章了解该类的使用方法和使用场景,http://www.importnew.com/15731.html

一个简单的数据库连接池示例
通过采用等待超时模式,保证当无法获取到数据库连接时,可以返回null,不会一直阻塞
import java.sql.Connection;import java.util.LinkedList;public class ConnectionPool {private LinkedList<Connection> pool = new LinkedList<Connection>();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){//完全超時---我理解就是millis如果设置成负数或者0,获取不到connection的话就一直等待if(millis<=0){while(pool.isEmpty()){pool.wait(0);}return pool.removeFirst();}else{long future=System.currentTimeMillis()+millis;long remaining=millis;while(pool.isEmpty()&&remaining>0){pool.wait(remaining);remaining=future-System.currentTimeMillis();}Connection result=null;result=pool.removeFirst();return result;}}}}

import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.sql.Connection;import java.util.concurrent.TimeUnit;public class ConnectionDriver {// InvocationHandler 可以实现java反射static class ConnectionHandler implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {if (method.getName().equals("commit")) {TimeUnit.MILLISECONDS.sleep(100);}return null;}}public static final Connection createConnection() {return (Connection) Proxy.newProxyInstance(ConnectionDriver.class.getClassLoader(),new Class<?>[]{Connection.class}, new ConnectionHandler());}}
import java.sql.Connection;import java.sql.SQLException;import java.util.concurrent.CountDownLatch;import java.util.concurrent.atomic.AtomicInteger;public class ConnectionPoolTest {static ConnectionPool pool = new ConnectionPool(10);// 可以修改线程数量static int threadCount = 10;// 保证所有ConnectionRunner同时开始static CountDownLatch start = new CountDownLatch(1);// main线程会等待所有ConnectionRunner结束后才能继续执行static CountDownLatch end = new CountDownLatch(threadCount);public static void main(String[] args) throws InterruptedException {int count = 20;AtomicInteger got = new AtomicInteger();AtomicInteger notGot = new AtomicInteger();for (int i = 0; i < threadCount; i++) {Thread thread = new Thread(new ConnectionRunner(count, got, notGot));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: " + notGot);}static class ConnectionRunner implements Runnable {int count;AtomicInteger got;AtomicInteger notGot;public ConnectionRunner(int count, AtomicInteger got,AtomicInteger notGot) {this.count = count;this.got = got;this.notGot = notGot;}@Overridepublic void run() {try {start.await();} catch (InterruptedException e) {e.printStackTrace();}while (count > 0) {// 从连接池里获取连接,如果1000ms内无法获得,将返回null// 分别统计连接获取的数量got和未获取到的数量notGottry {Connection connection = pool.fetchConnection(1000);if (connection != null) {try {connection.createStatement();connection.commit();} catch (SQLException e) {}got.decrementAndGet();} else {notGot.decrementAndGet();}} catch (InterruptedException e) {} finally {count--;}}end.countDown();}}}

线程池技术及其实例






 
原创粉丝点击