Java 多线程编程

程序,进程与线程

程序(program)是对数据描述与操作的代码的集合 ,是应用程序执行的脚本。 进程(process)是程序的一次执行过程,是操作系统运行程序的基本单位。程序是静态的,进程是动态的 。系统运行一个程序就是一个进程从创建、运行到消亡的过程。
多任务是指在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每一个任务对应一个进程。

线程是比进程更小的运行单位,是程序中单个顺序的流控制。一个进程中可以包含多个线程。
线程是一种特殊的多任务方式。当一个程序执行多线程时,可以运行两个或更多的由同一个程序启动的任务。 这样,一个程序可以使得多个活动任务同时发生。 线程与任何一个程序一样有一个开始、一系列可执行的命令序列、一个结束。在执行的任何时刻,只有一个执行点。线程与程序不同的是线程本身不能运行,它只能包含在程序中,只能在程序中执行。一个线程在程序运行时,必须争取到为自己分配的系统资源,如执行堆栈 、程序计数器等等。

多线程是相对于单线程而言的,指的是在一个程序中可以定义多个线程并同时运行它们,每个线程可以执行不同的任务
与进程不同的是,同类多线程共享一块内存空间和一组系统资源,所以,系统创建多线程花费单价较小。因此,也称线程为轻负荷进程
在这里插入图片描述

进程与线程比较示意图

在这里插入图片描述

进程与多线程示例

进程举例:
比如:同时运行的 word 和 PowerPoint 进程间没有公共数据(内存)
进程面向不同的软件
线程举例:
网络聊天服务器软件为每一个用户采用一个线程及时接收和转发该用户信息
浏览器在下载数据的同时还可以浏览其他网页
Java的无用单元收集器
线程面向一个软件内的不同事务

线程的状态与转换

线程的生命状态周期

同进程一样,线程也有从创建、运行到消亡的过程,称为线程的生命周期。
① 线程有创建(New)
② 可运行(Runnable)
③ 运行中(Running)
④ 挂起(Not Runnable)
⑤ 死亡(Dead)五种状态。
在这里插入图片描述

线程的状态

① 新建状态
new运算符创建一个线程后,该线程处于新建状态
该线程仅是一个空对象,并未得到系统的资源

② 可运行状态
start方法启动一个线程后,系统为该线程分配除处理机外的所有资源
线程进入就绪队列排队,等待处理机的调度

③ 运行状态
轮到线程占用CPU资源时,JVM将CPU使用权切换给该线 程,该线程就可以脱离创建它的主线程开始自己的生命周期
JVM执行该线程的run( )方法

④ 阻塞状态
本线程让出CPU使用权,JVM将CPU资源切换给其它线程
在引起阻塞的原因没有取消前,即使处理器空闲,该线程也不会被执行
当引起阻塞的原因被取消,线程重新转入runnable状态,进入就绪队列等待再次被调度

⑤ 引起阻塞的原因
线程在运行期间执行sleep方法使自身进入休眠状态。休眠指定时间后,重新进入就绪队列等待CPU资源,以便从中断处执行
线程执行期间,执行wait( )方法使自身进入等待状态。必须由其它线程执行notify( )或notifyAll( )方法通知它,才能重新进入就绪队列等待CPU资源,以便从中断处继续执行
线程执行期间,执行某个操作进入阻塞状态(如执行读/ 写操作),只有当引起阻塞的原因消除时,线程才重新回 到线程队列中排队等待CPU资源
线程执行期间,访问某同步方法,而该方法其它线程正在访问,引起阻塞

⑥ 死亡状态
线程运行结束后进入死亡状态
导致线程死亡的原因:
a) 自然撤消:线程执行完run( )方法
b) 强制终止: 强制run( )方法结束。可以调用线程的stop( ) 方法来强制杀死一个线程,但不推荐使用
c) 被停止: 应用程序因故停止运行时,系统将终止该程序正在运行的所有线程 JVM释放分配给线程的内存
可以使用isAlive方法判断线程是否处于活跃状态

线程的创建方法

创建一个线程

Java 提供了三种创建线程的方法:
通过实现 Runnable 接口;
通过继承 Thread 类本身;
通过 Callable 和 Future 创建线程

1.通过Runnable接口来创建线程
为了实现 Runnable,一个类只需要执行一个方法调用 run(),声明如:
public void run();
新线程创建之后,调用 start() 方法它才会运行: void start();

class RunnableDemo implements Runnable { 
	private Thread t; 
	private String threadName;
	
	RunnableDemo( String name) { 
		threadName = name; 
		System.out.println("Creating " +  threadName ); 
	}
	
	public void run() { 
		System.out.println("Running " +  threadName ); 
		try { 
			for(int i = 4; i > 0; i--) { 
				System.out.println("Thread: " + threadName + ", " + i); 
				Thread.sleep(50); // 让线程睡眠一会 
			} 
		}catch (InterruptedException e) { 
			System.out.println("Thread " +  threadName + " interrupted."); 
		}
		System.out.println("Thread " +  threadName + " exiting.");
	}
	
	public void start () { 
		System.out.println("Starting " +  threadName ); 
		if (t == null) { 
			t = new Thread (this, threadName); 
			t.start (); 
		} 
	}
} 

public class TestThread {
	public static void main(String args[]) { 
		RunnableDemo R1 = new RunnableDemo( "Thread-1"); 
		R1.start();
		
		RunnableDemo R2 = new RunnableDemo( "Thread-2"); 
		R2.start(); 
	}   
}

注:这里R1.start()是自己定义的类RunnableDemo的方法,而这个对象t是一个Thread对象,这个对象的t为Thread的方法,其中会调用run()方法,而run()方法继承自接口Runnable,必须要重写。

2.通过继承Thread类创建线程
创建一个线程的第二种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。
继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。
该方法尽管被列为一种多线程实现方式,但是本质 上也是实现了 Runnable 接口的一个实例。

class ThreadDemo extends Thread { 
	private Thread t; 
	private String threadName;
	
	ThreadDemo( String name) { 
		threadName = name; 
		System.out.println("Creating " +  threadName ); 
	}
	
	public void run() { 
		System.out.println("Running " +  threadName ); 
		try { 
			for(int i = 4; i > 0; i--) { 
				System.out.println("Thread: " + threadName + ", " + i); 
				Thread.sleep(50); // 让线程睡眠一会 
			} 
		}catch (InterruptedException e) { 
			System.out.println("Thread " +  threadName + " interrupted."); 
		 } 
		System.out.println("Thread " +  threadName + " exiting."); 
	}
		
	public void start () { 
		System.out.println("Starting " +  threadName ); 
		if (t == null) { 
			t = new Thread (this, threadName); 
			t.start (); 
		} 
	} 
}
 
public class TestThread {
	public static void main(String args[]) {
	ThreadDemo T1 = new ThreadDemo( "Thread-1"); 
	T1.start();
	
	ThreadDemo T2 = new ThreadDemo( "Thread-2"); 
	T2.start(); 
	}   
}

Thread类的主要方法
① public void start()
使该线程开始执行
Java 虚拟机调用该线程的 run 方法。

② public void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。

③ public final void setName(String name)
改变线程名称,使之与参数 name 相同。

④ public final void setPriority(int priority)
更改线程的优先级

⑤ public final void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。

⑥ public final void join(long millisec)
等待该线程终止的时间最长为 millis 毫秒。

⑦ public void interrupt()
中断线程。

⑧ public final boolean isAlive()
测试线程是否处于活动状态。

Thread类的静态方法
① public static void yield()
暂停当前正在执行的线程对象,并执行其他线程。

② public static void sleep(long millisec)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

③ public static boolean holdsLock(Object x)
当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。

④ public static Thread currentThread()
返回对当前正在执行的线程对象的引用。

⑤ public static void dumpStack()
将当前线程的堆栈跟踪打印至标准错误流。

综例:

public class DisplayMessage implements Runnable { 
	private String message;
	
	public DisplayMessage(String message) { 
		this.message = message; 
	}
	
	public void run() { 
		while(true) { 
			System.out.println(message); 
		} 
	}
}

public class GuessANumber extends Thread { 
	private int number; public GuessANumber(int number) { 
		this.number = number; 
	}
	
	public void run() { 
		int counter = 0; 
		int guess = 0; 
		
		do { 
			guess = (int) (Math.random() * 100 + 1); 		
			System.out.println(this.getName() + " guesses " + guess); 	
			counter++; 
		} while(guess != number); 

		System.out.println("** Correct!" + this.getName() + "in" + counter + "guesses.**"); 
	}
}

public class ThreadClassDemo {
	public static void main(String [] args) { 
	Runnable hello = new DisplayMessage("Hello"); 
	Thread thread1 = new Thread(hello); 
	thread1.setDaemon(true); 
	thread1.setName("hello"); 
	System.out.println("Starting hello thread..."); 
	thread1.start();
	
	Runnable bye = new DisplayMessage("Goodbye"); 
	Thread thread2 = new Thread(bye); 
	thread2.setPriority(Thread.MIN_PRIORITY); 
	thread2.setDaemon(true); 
	System.out.println("Starting goodbye thread..."); 
	thread2.start();
	
	System.out.println("Starting thread3..."); 
	Thread thread3 = new GuessANumber(27); 
	thread3.start(); 
	try { 
		thread3.join(); 
	}catch(InterruptedException e) { 
		System.out.println("Thread interrupted."); 
	} 

	System.out.println("Starting thread4..."); 
	Thread thread4 = new GuessANumber(75);
	thread4.start(); 

	System.out.println("main() is ending...");
	}
}

3.通过Callable和Future创建线程
创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值
使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程
调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; 

public class CallableThreadTest implements Callable<Integer> { 
	public static void main(String[] args)  {   
		CallableThreadTest ctt = new CallableThreadTest();  		
		FutureTask<Integer> ft = new FutureTask<>(ctt);  
		for(int i = 0;i < 10;i++)  { 	
			System.out.println(Thread.currentThread().getName()+" 的循环变量 i 的值 "+i);  
			if(i==2)  { 
				new Thread(ft,"有返回值的线程").start(); 
			}  
		} try { 
				System.out.println(" 子线程的返回值: "+ft.get()); 
		  	} catch (InterruptedException e)  { 
		  		e.printStackTrace();  
		  	} catch (ExecutionException e) { 
		  		e.printStackTrace();  
		  	}  
	}

@Override 
	public Integer call() throws Exception  {  
		int i = 0;  
		for(;i<10;i++)  {  
			System.out.println(Thread.currentThread().getName()+" "+i);  
		}  
		return i;  
	}  
}
三种创建线程的方式的对比

用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了Runnable 接口或 Callable 接口,还可以继承其他类。
使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

线程间的同步

线程的同步机制与共享资源

Java提供了同步设定功能。共享对象可将自己的成员方法定义为同步化(synchronized)方法,通过调用同步化方法来执行单一线程,其它线程则不能同时调用同一个对象的同步化方法。

生产者和消费者线程同步化问题

使用某种资源的线程称为消费者产生或释放这个资源的线程称为生产者
生产者生成10个整数(0~9),存储到一个共享对象中。 消费者把它们打印出来。每生成一个数就随机休眠0~ 100毫秒,然后重复这个过程。 生产者生产这10个数到共享对象中,消费者将尽可能快地消费这10个数,即把它们取出后打印出来。这个模型由4个程序组成。

1.1共享成员Share的成员变量——非同步模式

package threadSynchroniz;
	
public class Share {
	private int contents;
	public int get(){ 
		return contents; 
	} 
	public void put(int value){ 
		contents=value; 
	}
}

1.2生产者程序

package threadSynchroniz; 

public class Producer extends Thread { 
	private Share shared; 
	private int number; 
	
	public Producer(Share s, int number) { 
		shared=s; 
		this.number=number; 
	} 
	
	public void run() { 
		for (int i=0; i<10; i++) { 
			shared.put(i); 
			System.out.println(" 生产者 "+this.number+"  输出的数据为: "+i); 	
			try { 
				sleep((int)(Math.random() * 100)); 
				} catch (InterruptedException e) {} 
		} 
	} 
}

1.3消费者程序

package threadSynchroniz;

public class Consumer extends Thread { 
	private Share shared; 
	private int number; 
	
	public Consumer(Share s, int number) { 
		shared=s; this.number=number; 
	} 
	
	public void run() { 
		int value = 0; 
		for (int i=0; i<10; i++) { 
			value=shared.get(); 
			System.out.println(" 消费者 "+this.number+"  得到的数据为: "+value); 
		} 
	} 
}

1.4主程序

package threadSynchroniz;

public class PCTest { 
	public static void main(String[] args) { 
		Share s=new Share(); 
		Producer p=new Producer(s,1); 
		Consumer c=new Consumer(s,1); 
		p.start(); 
		c.start(); 
	} 
}

1.5修改同步化共享资源Share成员变量

package threadSynchroniz; 

public class Share { 
	private int contents; 
	private boolean available=false; 
	
	public synchronized int get() { 
		while (available==false) { 
			try { 
				wait(); 
			} catch (InterruptedException e) {} 
		} 
	
		available=false; 
		notifyAll(); return contents; 
	} 

	public synchronized void put(int value) { 
		while (available==true) { 
			try { 
				wait(); 
			} catch (InterruptedException e) {} 
		} 

		contents=value; 
		available=true; 
		notifyAll(); 
	} 
}

在这里插入图片描述
在这里插入图片描述

2.1栈Stack的共享
(临界资源问题)

 class Stack{ 
 	int idx=0; 
 	char[ ] data = new char[6]; 

	public void push(char c){ 
		data[idx] = c; idx++; 
	} 
	public char pop(){ 
		idx--; 
		return data[idx]; 
	}
}

2.2无同步的栈Stack共享
两个线程A和B在同时使用Stack的同一个实例对象,A正在往堆栈里push一 个数据,B则要从堆栈中pop一个数据。
最后的结果相当于r没有入栈。
产生这种问题的原因在于对共享数据访问的操作的不完整性。

2.3同步栈SynStack定义

package stack; 

public class SynStack { 
	private int index=0; 
	private char[] buffer = new char[6]; 
	
	public synchronized void push(char c) { 
		while(index == buffer.length){  
			try{ 
				this.wait(); 
			} //栈满,调用wait()释放互斥锁,进入等待队列 
			catch(InterruptedException e) {  
				System.out.println(e); 
			} 
		} 
		this.notify();//唤醒在互斥锁等待队列的线程重新去争取互斥锁 
		buffer[index]=c; index++; 
	} 

	public synchronized char pop() { 
		while(index==0) { 
			try{  
				this.wait();
			}  // 栈空,调用wait()释放互斥锁,进入等待队列 
			catch(InterruptedException e)  { 
				System.out.println(e); 
			} 
		} 
		this.notify();//唤醒在互斥锁等待队列的线程重新去争取互斥锁 
		index--; 
		return buffer[index]; 
	} 
}

2.4生产者Producer类

package stack;

class Producer implements Runnable { 
	SynStack theStack; 
	
	public Producer(SynStack s){ 
		theStack=s; 
	} 

	public void run() { 
		char c; 
		for(int i=0;i<10;i++){ 
			c=(char)(Math.random()*26+'A'); 
			theStack.push(c); 
			System.out.println("Produced:"+c); 
			try {   
				Thread.sleep((int)(Math.random()*100));
			} catch(InterruptedException e) { 
				System.out.println(e);   
				} 
		} 
	} 
}

2.5消费者Consumer类

package stack; 
class Consumer implements Runnable { 
	SynStack theStack; 
	
	public Consumer(SynStack s){ 
		theStack=s; 
	} 

	public void run() { 
		char c; 
		for(int i=0;i<10;i++) { 
			c=theStack.pop(); 
			System.out.println("Consumed:"+c); 
			try{ 
				Thread.sleep((int)(Math.random()*100));
			} catch(InterruptedException e) { 
				System.out.println(e); 
				} 
		} 
	} 
}

2.6测试栈同步的主程序

package stack;

public class SynStackTest { 
	public static void main(String args[]) { 
		SynStack stack = new SynStack(); 
		Runnable source=new Producer(stack); 
		Runnable sink=new Consumer(stack);
		Thread t1=new Thread(source); 
		Thread t2=new Thread(sink);
		t1.start(); t2.start();
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值