重温线程的基础知识:
1. Thread相关方法
1、sleep()
使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁。也就是说如果有synchronized同步快,其他线程仍然不能访问共享数据。注意该方法要捕捉异常。
例如有
两个线程同时执行(没有synchronized)一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有
Sleep()方法,只有高优先级的线程执行完毕后,低优先级的线程才能够执行;但是高优先级的线程sleep(500)后,低优先级就有机会执行了。
总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。2、join()
join()方法使调用该方法的线程在此之前执行完毕,也就是等待该方法的线程执行完毕后再往下继续执行。注意该方法也需要捕捉异常。
该影响只存在于执行join方法的线程和调用该线程的线程之间
如在t1线程中调用t2.join(),则需要t2线程执行完后t1方能继续执行
3、yield()
该方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。
4、wait()和notify()、notifyAll()
这三个
方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用。synchronized关键字用于保护共享数据,阻止其他线
程对共享数据的存取,但是这样程序的流程就很不灵活了,如何才能在当前线程还没退出synchronized数据块时让其他线程也有机会访问共享数据呢?
此时就用这三个方法来灵活控制。wait()
方法使当前线程暂停执行并释放对象锁标示,让其他线程可以进入synchronized数据块,当前线程被放入对象等待池中。当调用notify()方法
后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志;如果锁标志等待池中没有线程,则
notify()不起作用。notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。
注意 这三个方法都是java.lang.Object的方法。
二、run和start()
把需要处理的代码放到run()方法中,start()方法启动线程将自动调用run()方法,这个由java的内存机制规定的。并且run()方法必需是public访问权限,返回值类型为void。
三、关键字synchronized
该关键字用于保护共享数据,当然前提条件是要分清哪些数据是共享数据。每个对象都有一个锁标志,当一个线程访问到该对象,被Synchronized修饰的数据将被”上锁”,阻止其他线程访问。当前线程访问完这部分数据后释放锁标志,其他线程就可以访问了。
- 简单尝试各个函数功能:
如下代码实现以下功能:
- 编写一程度,使得字符串“文字的打印效果”每隔一秒钟输出一个字符,并到时间就换行。
- 运行结果提示:
起初:文
1秒后:文字
2秒后:文字的
。。。。。
- 分析:这里边可以写两个线程:一个打字;一个输出换行符。
public class ThreadTest extends Thread{
String str;
ThreadTest(String str){
this.str = str;
}
public void run(){
for(int i=0;i<str.length();i++){
System.out.print(str.charAt(i));
try{
sleep(1000);
}catch(InterruptedException e){
System.out.println("an error occured during my sleep");
}
}
System.out.println("\n打印完毕");
}
public static void main(String[] Args){
String str = new String("文字的打印效果");
System.out.println("this is in main");
ThreadTest TT = new ThreadTest(str);
TT.start();
}
}
输出效果:
文字的打印效果
- 线程的同步问题
- 也可以用Synchronized来标识方法
- 多线程同步可以用Synchronized来标识代码块
如下代码为用Synchronized来标识代码块:
package ThreadPackage;
/* 问题描述:
* - 编写一程度,使得字符每隔一秒钟输出一行字符,并到时间就换行。多线程共同执行,共用一个打印机
- 运行结果提示:(三角阵形式)
起初:A
1秒后:AA
2秒后:AAA
。。。。。
*/
class NewLine extends Thread{
String str;
NewLine(String str){
this.str = str;
}
public void run(){
for(int i=0;i<str.length();i++){
try {
sleep(1000); break;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadTest extends Thread{
String str;
static Object printer = new Object(); //注意这个地方必须要加static,因为这个对象必须要创建一次,且只能创建一次
ThreadTest(String str){
this.str = str;
}
public void run(){
synchronized(printer){ //这个地方是同步代码块
for(int i=0;i<=str.length();i++){
for(int j=0;j<i;j++){
System.out.print(str.charAt(j));
}
try {
ThreadTest.sleep(1000); //这个不会释放锁,注意与wait的区别
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.print("\n");
}
System.out.println("\n打印完毕");
}
}
public static void main(String[] Args){
String str = new String("AAAA");
String str1 = new String("BBBB");
ThreadTest TT = new ThreadTest(str);
ThreadTest TT1 = new ThreadTest(str1);
TT.start();
TT1.start();
}
}
输出结果:
A
AA
AAA
AAAA打印完毕
B
BBB
BBBB
打印完毕
总结:不论用Synchronized来标识代码块还是方法,各个线程都有一个公用的东西,或者是一个对象。如上述的代码块,就有一个static的对象object,这个对象必须是同一个。例如:直接在run()方法上加Synchronized的标识就不能进行线程同步,因为这个时候,多个线程的创建,使得每个线程都有自己独立的对象,这个时候相当于每个线程都有进入run方法的钥匙,所以是没用的
- 线程间通信的例子最好的莫过于这个经典习题了。
生产者与消费者问题问题是线程同步里边一个很经典的问题。
用通俗的语言来描述:一群生产者不断的生产产品,并将产品放到一个容器里边;同时一群消费者不断从容器里边消费产品。
容器的容量是有限的。如果容器满了,生产者不能再往容器放产品,必须进入等待状态。等待产品被消费者拿走了,再往容器放产品。
同样,如果容器空了,消费者也必须进入等待状态。等待生产者往里边放产品,再将消费者唤醒。 下边我们来看看如何用java代码来实现
我们假设生产者是一群厨师,产品是汉堡,容器是一个篮子,消费者是一群消费者。
首先是需要定义的汉堡类,相关属性有:汉堡生产数量,汉堡原材料多少(也可以是篮子的大小),卖出了多少汉堡;
汉堡类源码如下:
package hambergPackage;
class Ham {
static Object basket = new Object(); //这里就是钥匙
static int totalMaterial = 10; //总的制作材料
static int production=3; //起始已经做好了三个
static int sales=0;
}
接下来分析售货员(消费者)类:
售货员在汉堡篮子汉堡数量还有的时候,就一直卖;
如果汉堡没有了,那么就等待厨师去做(wait);
代码如下
package hambergPackage;
class HAssistant extends Thread{
public void sell(){
synchronized(Ham.basket){
try{
sleep(1000);
}catch(InterruptedException e){}
System.out.println("一个新的汉堡包已经卖出了");
//思考,这个时候需要去通知另外一个线程吗
Ham.sales++;
if(Ham.sales==Ham.production){
try {
System.out.println("汉堡数量不够了,请各位稍等");
Ham.basket.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
}
}
}
}
public void run(){
while(Ham.sales<Ham.production)
{sell();
}
}
}
再分析一下厨师类:
进入线程就是做面包,有多少原材料(篮子最大数量)就做多少汉堡;
厨师类代码如下:
package hambergPackage;
class HMaker extends Thread{
public void make(){
synchronized(Ham.basket){
try{
sleep(1000); //做汉堡需要的时间
}catch(InterruptedException e){
}
Ham.production++;
System.out.println("一个新的汉堡包已经做好了");
//思考,这个时候需要去通知另外一个线程吗
//这里不用,要在循环体中放,逻辑更清楚
Ham.basket.notify(); //这里注意个事,就是notify并不释放锁,这个地方还是制做汉堡,篮子有多大就做多少汉堡
}
}
public void run(){
while(Ham.production<Ham.totalMaterial){
make();
}
System.out.println("汉堡篮子已经满了");
}
}
运行的测试代码:
package hambergPackage;
public class HambergTest{
public static void main(String[] Args){
HMaker hm = new HMaker();
HAssistant ha = new HAssistant();
ha.start();
hm.start();
}
}