Java多线程编程介绍

Java是一个支持多线程(Multi-threading)的编程语言,JVM为多线程编程提供了内在的支持。JVM可以看做宿主系统的一个进程,不管你的程序是否显式的采用了多线程的编程方式,JVM本身总是以多线程的方式来执行程序。例如,在JVM中除了运行你个人的线程之外,还会有垃圾收集、鼠标与键盘时间分发等以守护线程(daemon thread)形式运行的线程。

线程(thread)是CPU进行调度的基本单位,与进程不同的是,每一个进程都享有独立的地址空间(代码、数据以及系统资源),但每一个线程除了有自己的程序计数器、寄存器集和堆栈之外,同一程序中的线程共享代码段、数据段和操作系统的资源,因此又把线程称为轻量级进程(light-weight process)。线程之间可以通过共享的数据区间进行通信,因而开销较小,效率更高。

下面主要简单介绍Java 多线程的一些基本概念、如何创建线程以及线程同步。


线程状态


Java线程有如下几种状态:

  • New  线程已经创建,等待运行(start())
  • Runnable 线程可执行
  • Running 线程正在执行
  • Blocked 线程阻塞(I/O blocking, suspend(), wait(), sleep() etc) 
  • Dead 线程终止
线程各个状态之间的转换如下图所示:

Java多线程编程介绍_第1张图片

注意:wait()/notfiy()/notifyAll() 需要持有对象实例的锁时才能调用,因此这些方法的调用必须放在以synchronized标识的同步代码中,否则运行时会抛出一个java.lang.IllegalMonitorStateException


创建线程


Java 有两种方式来创建线程:

  • 实现(implements) Runnable 接口(interface)
  • 继承(extends) Thread 类
Runnable 是一个简单的接口,其定义了所有Thread类必须实现的抽象方法run(),如下所示:
public interface Runnable{
	public abstract void run();
}
Thread类本身实现了Runnable接口,并且还包含了一个Runnable类型的对象实例Target, 该类为run()方法提供了最简单的实现,可供Thread类调用:

public class Thread extends Object implements Runnable {
	...
	private Runnable target;
	...
	private final static int MIN_PRIORITY = 1;
	private final static int NORM_PRIORITY = 5;
	private final static int MAX_PRIORITY = 10;
	...
	public Thread();
	public Thread(String name); // Thread name
	public Thread(Runnable R); // Thread ? R.run()
	public Thread(Runnable R, String name);
	...
	public void start(); // begin thread execution
	...
	// if this thread was contructed using a separated Runnable run object
	// then that Runnable object's run method is calld; otherwise,this method
	// does nothing and returns
	public void run(){
		if(target != null){
			target.run();
		}
	}; 
	...
}

Java线程优先级最小为1 (MIN_PRIORITY),最大为10 (MAX_PRIORITY),创建的线程默认优先级为5(NORM_PRIORITY).

用实现Runnable接口的方法创建线程
  • 首先,实现run() 方法
  • 实例化一个Thread对象 Thread(Runnable threadObj, String threadName)
  • 通过调用 start(),启动线程
示例代码:
class MyRunnable implements Runnable{
	private Thread t;
	private String threadName;
	
	MyRunnable(String name){
		threadName = name;
		System.Out.println("Creating " + threadName);
	}
	
	public void Run(){
		System.Out.println("Running " + threadName);
		
		try{
			for(int i = 0; i < 10; ++i){
				System.Out.println("Counter = " + i);
				// sleep for a while
				Thread.sleep(50);
			}
		}catch(InterruptedException e){
			System.Out.println("Thread " + threadName + " is interrupted");
		}
		System.Out.println("Thread " + threadName + " is exiting");
	}
	
	public void start(){
		System.Out.println("Starting " + threadName);
		
		if(t == null){
			t = new Thread(this, threadName);
			t.start();
		}
	}
}

public class RunnableTest{
	public static void main(String args[]){
	
		MyRunnable r1 = new MyRunnable("thread-1");
		r1.start();
		
		MyRunnable r2 = new MyRunnable("thread-2");
		r2.start();
	}
}

用继承Thread类的方法创建线程
  • 首先,覆盖Thread类中的run()方法
  • 调用start()创建线程
示例代码:
class MyThread implements Thread{
	private Thread t;
	private String threadName;
	
	MyThread(String name){
		threadName = name;
		System.Out.println("Creating " + threadName);
	}
	
	public void Run(){
		System.Out.println("Running " + threadName);
		
		try{
			for(int i = 0; i < 10; ++i){
				System.Out.println("Counter = " + i);
				// sleep for a while
				Thread.sleep(50);
			}
		}catch(InterruptedException e){
			System.Out.println("Thread " + threadName + " is interrupted");
		}
		System.Out.println("Thread " + threadName + " is exiting");
	}
	
	public void start(){
		System.Out.println("Starting " + threadName);
		
		if(t == null){
			t = new Thread(this, threadName);
			t.start();
		}
	}
}

public class RunnableTest{
	public static void main(String args[]){
	
		MyThread r1 = new MyThread("thread-1");
		r1.start();
		
		MyThread r2 = new MyThread("thread-2");
		r2.start();
	}
}


线程同步


为确保多个并发线程可互斥地共享资源,Java使用管程(monitor)机制,每一个实例对象都与管程相关联,称之为“锁”(lock)。每一个实例对象有唯一的锁,每一类对象也只有唯一的一个锁。Java管程支持两种线程同步方式:互斥(mutual exclusion)和 协作(cooperation)。互斥是通过Lock机制来实现的,它允许多个线程独立的访问某个数据而不会相互干扰;协作则是通过Object类中的wait(), notify(), notifyAll()来实现的。

互斥

Java中使用关键字synchronized来定义同步代码锁的控制:在进入同步代码之前,线程必须获取指定对象实例的锁,若无法获得锁则该线程进入锁等待队列中等候;同步代码执行完或者出现异常中断时,线程需释放锁并唤醒锁等待队列中的线程。在任一时刻,一个对象实例的锁只能由一个线程持有,从而保证了特定代码段之间的互斥。

一般,synchronized关键字可用于如下两种不同情况:

  • 将某个代码块作为代码临界区
  • 将整个方法作为代码临界区

示例代码:

public class Stack{
	...
	// 代码块
	public void push(char c){
		synchronized(this){
			++top;
			data[top] = c;
		}
	}
	// 整个方法
	public synchronized char pop(){
		char temp = data[top];
		--top;
		return temp;
	}
}

线程协作

线程协作主要用的如下几个方法:

  • sleep(long millis): 使调用的当前前程在指定的一段时间内处于阻塞状态。线程进入睡眠状态时,不会释放已占有的任何锁;
  • join(long millis)/join(): 让当前执行的线程处于阻塞状态,等待指定线程(即调用join的线程)运行结束后再重新运行。若没有指定时间参数,则当调用线程执行结束后返回可运行状态;若指定了时间参数,则当调用线程执行结束后,或执行时间超过指定时间长度,均会导致线程重返可运行状态;
  • wait(): 通知调用线程释放锁(管程),进入睡眠状态直到其他线程进入同一管程并且调用notify();
  • notify():唤醒调用某个对象的wait()的线程
  • notifyAll(): 唤醒所有调用某个对象wait()的线程,具有最高优先级的首先运行

下面的代码是一个producer-consumer的例子:如果Producer线程填满了buffer,则其需要释放管程的锁,等待Consumer线程从buffer中取出部分字符才能继续运行;同样地,如果buffer为空,则Consumer线程需要释放管程的锁,使Producer线程可以运行。

class Buffer{
	private char[] buffer;
	private int count = 0;
	private int in = 0;
	private int out = 0;
	
	public Buffer(int size){
		buffer = new char[size];
	}
	
	public synchronized void put(char c){
		while(count == buffer.length){
			try{
				wait();
			}catch(InterruptException e){
				e.printStackTrace();
			}finally{ 
				// do something here
			}
		}
		System.out.println("Producing " + c + " ...");
		buffer[in] = c;
		in = (++in)%buffer.length;
		++count;
		notify();
	}
	
	public synchronized char get(){
		while(count == 0){
			try{
				wait();
			}catch(InterruptException e){
				e.printStackTrace();
			}finally{
				//do something here
			}
		}
		char c = buffer[out];
		out = (++out)%buffer.length;
		--count;
		System.out.println("Consuming " + c + " ...");
		notify();
		
		return c;
	}
}

一般地,在实践中,对于线程同步,可以参照如下几条规则:

  • If two or more threads modify an object, declare the methods that carry out the modification as synchronized(如果有两个以上的线程修改一个对象,将修改对象的代码声明为synchronized);
  • If a thread must wait for the state of an object to change, it should wait inside the object, not outside, by entering the synchronized method and calling wait() (如果一个线程需要等待一个对象状态的变化,它应该通过调用wait()方法在对象内进行等待而不是对象的外面);
  • Whennever a method changes the state of an object, it should call notify()/notifyAll(), which gives the waiting threads a chance to see if circumstances have changed(不管什么时候一个方面改变了对象的状态,它应该调用notify(),这样使得正在等待的线程可以看到运行状态的变化).

参考资料:

http://tutorials.jenkov.com/java-concurrency/index.html

你可能感兴趣的:(Java,java,多线程,线程,编程,jvm)