Java并发编程,是Java的高级开发部分,平时项目很少用到,主要原因还是不熟悉,从今天开始整体学习研究下,后面会有一个系列的学习,也为以后在项目中经常使用打下基础。首先来回顾下Java最基本的多线程开发,就是java.lang.Thread类。
以下是本文包含的知识点:
一、线程的基本概念
二、线程的创建和启动
三、线程的状态控制
四、线程的同步
五、生产者消费
下面开始本文的内容:
一、线程的基本概念
线程(英语:thread)是操作系统能夠進行運算调度的最小單位。 它被包含在进程之中,是进程中的實際運作單位。 一条
线程指的是进程中一个单一顺序的控制流,一個进程中可以並行多個
线程,每条
线程并行执行不同的任务。(来自维基百科)
简单来说:线程是一个程序内部的顺序控制流。
线程和进程的区别:
1.每个进程有独立的代码和数据空间,进程间的切换会有较大开销。
2.在一个操作系统中可以运行多个进程。
3.一个进程中可以并行运行多个线程。
4.一个进程中的多个线程共享这个进程中的资源,如代码,数据空间。
还可以参考阮一峰的:http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html
二、Java线程的创建和启动
当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的
main
方法)。
在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆。
只要有一个非守护线程还在运行,守护线程就不能结束,当所有非守护线程都运行结束,守护线程也随着JVM一同结束。最典型的守护线程是GC(垃圾回收器)。
JVM启动时会有一个主方法(public static void main(String []args))所定义的线程。也叫主线程
Java通过创建Thread实例来创建新的线程,
每个线程都是通过某个特定的Thread对象的run()方法来完成它的操作,run()方法称为线程体。
通过调用Thread类的start()方法来启动一个线程。
通过JDK6的API文档
http://tool.oschina.net/apidocs/apidoc?api=jdk-zh,我们看到有两种创建线程的方式:
一种方法是将类声明为
Thread
的子类, 该子类应重写
Thread
类的
run
方法:
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
然后,下列代码会创建并启动一个线程:
PrimeThread p = new PrimeThread(143);
p.start();
另一种方法是声明实现 Runnable
接口的类。该类然后实现 run
方法:
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
然后,下列代码会创建并启动一个线程:
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
每个线程都有一个标识名,多个线程可以同名。如果线程创建时没有指定标识名,就会为其生成一个新名称。
三、线程的状态控制
线程控制的基本方法有:
isAlive():判断线程是否还“活”着,即线程是否还未终止
Thread.sleep():将当前线程睡眠指定的毫秒数
join():调用某线程的该方法,将当前线程与该线程合并,即等待该线程结束,再恢复当前线程的运行。
yield():让出CPU,当前线程进入就绪队列等待调度。
wait():当前线程进入对象的wait pool。
notify()/notifyAll():唤醒对象的wait pool中的一个/所有等待线程。
Thread.currentThrad():获得当前线程的引用。
线程的优先级:
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。
线程调度器按线程的优先级来决定应调度哪个线程来执行。
通过阅读源码,看到线程优先级用数字表示,从1到10,默认为5。
通过下列方法来获取或设置某个线程的优先级:
getPriority():获得线程的优先级数
setPriority():设置线程的优先级数
四、线程同步:
先来看下面一段代码:
public class TestSync implements Runnable {
Timer timer = new Timer();
public static void main(String[] args) {
TestSync test = new TestSync();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.setName( "t1");
t2.setName( "t2");
t1.start();
t2.start();
}
public void run() {
timer.add(Thread. currentThread().getName());
}
}
class Timer {
private static int num = 0;
public void add(String name) {
num++;
System. out.println(name + ", 你是第" + num + "个使用timer的线程" );
}
}
我们期望的结果是:
t1, 你是第1个使用timer的线程
t2, 你是第2个使用timer的线程
然而,实际执行的结果却是:
t1, 你是第2个使用timer的线程
t2, 你是第2个使用timer的线程
为什么会出现意想不到的情况呢,我们按实际结果的情况来分析下:
首先,t1先执行了,将num++变成了1。
此时,t2开始执行(注意这里t1并未执行完),将num++变成了2 。
然后,t1接着执行,打印System.out输出,结果为 t1, 你是第2个使用timer的线程。
最后,t2接着执行,打印System.out输出,结果为 t2, 你是第2个使用timer的线程。
按上面的分析,我们知道出现这种情况的原因是t1,t2在执行线程体访问同一个对象的过程中被打断了。
那么如何不被打断呢,这就要用到线程同步。
在Java中引入对象互斥锁的概念,保证共享数据操作的完整性。
每个对象都对应于一个称为“互斥锁”的标记,这个标记保证在任一时刻,只能有一个线程访问该对象。
关键字synchronized来表示对象的互斥锁,当某个对象被synchronized修饰时,表示该对象在任一时刻只能由一个线程访问。
synchronized的使用方法,如对上例的修改:
1.作用于代码块上:
synchronize(this){
num++;
System. out.println(name + ", 你是第" + num + "个使用timer的线程" );
}
2.放在方法声明中,表示整个方法为同步方法:
public synchronized void add(String name) {
num++;
System. out.println(name + ", 你是第" + num + "个使用timer的线程" );
}
五、生产者消费者
/**
* 生产者消费者
* 举例:生产窝头,消费窝头
* @author Yuwl
*/
public class TestProducerConsumer {
public static void main(String[] args) {
SyncStack ss = new SyncStack();
Producer p = new Producer(ss);
Consumer c = new Consumer(ss);
new Thread(p).start();
new Thread(c).start();
}
}
/**
* 窝头
*/
class WoTou {
int id;
WoTou(int id){
this. id = id;
}
public String toString() {
return "WoTou id=" + id ;
}
}
/**
* 篮子-栈的实现[先进后出]
*/
class SyncStack {
int index = 0;
WoTou []arrWt = new WoTou[6];
public synchronized void push(WoTou wt){
while( index == arrWt. length){ //如果篮子满了
try {
this.wait(); //让当前线程进入当前对象的wait pool
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll(); //唤醒其它正在等待的线程
arrWt[ index] = wt;
index++;
}
public synchronized WoTou take(){
while( index == 0){//如果篮子空了
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
index--;
return arrWt[ index];
}
}
/**
* 生产者
*/
class Producer implements Runnable {
SyncStack ss;
Producer(SyncStack ss){
this. ss = ss;
}
public void run(){
for( int i=0; i<20; i++){
WoTou wt = new WoTou(i);
ss.push(wt);
System. out.println( "生产了 "+wt);
try {
Thread. sleep((long)(Math.random()*200));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 消费者
*/
class Consumer implements Runnable {
SyncStack ss;
public Consumer(SyncStack ss) {
this. ss = ss;
}
public void run(){
for( int i=0; i<20; i++){
WoTou wt = ss.take();
System. out.println( "消费了 "+wt);
try {
Thread. sleep((long)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
其中一次的运行结果:
生产了 WoTou id=0
消费了 WoTou id=0
生产了 WoTou id=1
生产了 WoTou id=2
消费了 WoTou id=2
生产了 WoTou id=3
生产了 WoTou id=4
生产了 WoTou id=5
生产了 WoTou id=6
生产了 WoTou id=7
消费了 WoTou id=7
生产了 WoTou id=8
消费了 WoTou id=8
生产了 WoTou id=9
生产了 WoTou id=10
消费了 WoTou id=9
消费了 WoTou id=10
生产了 WoTou id=11
生产了 WoTou id=12
消费了 WoTou id=11
生产了 WoTou id=13
消费了 WoTou id=12
消费了 WoTou id=13
生产了 WoTou id=14
消费了 WoTou id=14
生产了 WoTou id=15
生产了 WoTou id=16
消费了 WoTou id=15
消费了 WoTou id=16
生产了 WoTou id=17
消费了 WoTou id=17
生产了 WoTou id=18
消费了 WoTou id=18
生产了 WoTou id=19
消费了 WoTou id=19
消费了 WoTou id=6
消费了 WoTou id=5
消费了 WoTou id=4
消费了 WoTou id=3
消费了 WoTou id=1
参考:
JDK6中文API
尚学堂马士兵老师J2SE视频