线程的基本概念
进程以及使用环境
程序是计算机指令的集合,它以文件形式存储在磁盘上,而进程就是一个执行中的程序,每一个进程都有其独立的内存空间和系统资源。
进程就是一个运行的程序,Windows操作系统是支持多进程的操作系统,即同一时间l可以执行多个程序,每个程序是在自己独立的内存空间内,使用自己被分配到的系统资源。其实,这种说法并不准确,一个CPU在某个时刻,实际上只能运行一个程序,即一个进程。所谓的支持多进程,其实就是CPU在非常快速的交替轮流执行多个程序,例如,利用Windows操作系统可以边听歌曲、一边上网等。
线程以及使用环境
线程是CPU调度和分派的基本单位,一个进程可以由多个线程组成,而这多个线程共享同一个存储空间,这使得线程间的通信比较容易。在一个多进程的程序中,如果要切换到另一个进程,需要改变地址空间的位置。然而在多线程的程)犷中,就不会出现这种情况因为它们位于同一个内存空间内.只需改变运行的顺序即可。
多线程指单个程序可通过同时运行多个不同线程,以执行不同任务。所谓同时,也要依据CPU。如果是多个CPU,则并发运行,如果是一个CPU,则根据系统具体情况,执行多个线程。
线程创建
创建线程的方法一般有两种:
1.通过实现Runnable接口的方式创建线程。
2.通过继承Thread类来创建线程。
通过Runnable接口的方式创建线程
在Java中,线程是一种对象,但不是所有的对象都可以称为线程。只有实现Runnable接口的类,才可以称为线程。先看看Runnable接口的定义:
public interface Runnable { public abstract void run(); }
Runnable接口只有一个抽象方法run()。要实现这个接口。只要实现这个抽象方法就可以。只要实现了这个接口的类,才有资格称为线程。
创建线程的结构如下:
Thread t=new Thread(runnable 对象);
runnable对象是指实现了Runnable接口类的对象。当线程执行时.runnable对象中的"run()”方法会被调用,如果想要运行上面创建的线程,还需要调用一个Thread类的方法。
t.start();
例子:
public class threadtest{ //创建测试两个线程类,让其交替运衍 public static void main(String[]args) { //创建对象c和c1 compute c=new compute(); compute1 c1=new compute1(); //创建线程对象t和t1 Thread t=new Thread(c); Thread t1=new Thread(c1); t.start();//启动线程对象t t1.start();//启动线程对象t1 } } //创建通过循环语句输出数字的类 class compute implemeats Runnable{ //创建实现线程的类compute public void run(){ //实现方法run() for(int i=0; i<10;i++){ System.out .println(i); } } } //创建通过循环语句输出数字的类 class compute1 implements Runnable{ //创建实现线程的类compute1 public void run(){//实现方法run() for(int i=0;i<10;i++){ System.out.println(”这个数字是:”+i); } } }
运行结果:
0
这个数字是:0
1
这个数字是:1
2
这个数字是:2
3
这个数字是:3
4
这个数字是:4
5
这个数字是:5
6
这个数字是:6
7
这个数字是:7
8
这个数字是:8
9
这个数字是:9
PS:这个程序段中,创建了两个线程,不过多次运行后结果可能会有不同。那为什么会输出不同的结果呢?
因为在程序具体运行时会存在执行顺序的问题。在Java技术中,线程通常是通过调度模式来执行的。所谓抢占式调度模式是指,许多线程处于可以运行状态,即等待状态,但实际只有一个线程在运行。该线程一直运行到它终止,或者另一个具有更高优先级变成可运行状态。在后一种情况下,低优先级线程被高优先级线程抢占且获得运行机会。
通过继承Thread类来创建线程
其实Thread类本身也实现了Runnable接口,所以只要让一个类能够继承Thread类,并覆盖"run()”方法,也会创建线程。
例子:
public class threadtest{ //创建测试两个线程类,让其交替运衍 public static void main(String[]args) { //创建对象c和c1 compute c=new compute(); compute1 c1=new compute1(); //创建线程对象t和t1 Thread t=new Thread(c); Thread t1=new Thread(c1); t.start();//启动线程对象t t1.start();//启动线程对象t1 } } //创建通过循环语句输出数字的类 class compute extends Thread{ //创建实现线程的类compute public void run(){ //实现方法run() for(int i=0; i<10;i++){ System.out .println(i); } } } //创建通过循环语句输出数字的类 class compute1 extends Thread{ //创建实现线程的类compute1 public void run(){//实现方法run() for(int i=0;i<10;i++){ System.out.println(”这个数字是:”+i); } } }
这两种方式的详细使用:
线程的使用
线程的优先级
线程的执行顺序是一种抢占方式,优先级高的比优先级低的要获得更多的执行时间,如果想让一个线程比其他线程有更多的时间运行,可以通过设置线程的优先级解决。
如一个线程创建后,可通过在线程中调用“set Priority()”方法,来设置其优先级,具体方法如下:
public final void setPriority(int newPriority);
newPriority是一个1到10之间的正整数,数值越大,优先级别越高,系统定义了一些常数值如下:
public final static int MIN_PRIORITY=1,表示最低优先级。
public final static int MAX_ PRIORITY=10,表示最高优先级。
public final static int NORM_PRIORITY=5,表示默认优先级。
例子:
public class threadtest{ //创建测试两个线程类,让其交替运衍 public static void main(String[]args) { //创建对象c和c1 compute c=new compute(); compute1 c1=new compute1(); //创建线程对象t和t1 Thread t=new Thread(c); Thread t1=new Thread(c1); t .setPrioritp(10); t1.setPrioritp(1); t.start();//启动线程对象t t1.start();//启动线程对象t1 } } //创建通过循环语句输出数字的类 class compute extends Thread{ //创建实现线程的类compute public void run(){ //实现方法run() for(int i=0; i<10;i++){ System.out .println(i); } } } //创建通过循环语句输出数字的类 class compute1 extends Thread{ //创建实现线程的类compute1 public void run(){//实现方法run() for(int i=0;i<10;i++){ System.out.println(”这个数字是:”+i); } } }
输出结果:
0
1
2
3
4
5
6
7
8
9
这个数字是:0
这个数字是:1
这个数字是:2
这个数字是:3
这个数字是:4
这个数字是:5
这个数字是:6
这个数字是:7
这个数字是:8
这个数字是:9
此时的输出变得很有规律,因为程序中将输出数字的线程的优先级设为最高了,而输出汉字的线程是系统最低优先级,所以程序执行时,会给数字输出线程更多的时间执行。
线程的休眠和唤醒
线程的休眠
线程的休眠,是指线程暂时处于等待的一种状态,通俗地说,就是线程暂I付停止运行了。
要达到这种功能需要调用Thread类的“sleep()"方法。"sleep()”方法可以使线程在指定的时间,处于暂时停止的状态,等到指定时间结束后,暂时停止状态就会结束,然后继续执行没有完成的任务。"sleep()”方法的方法结构如下
public static native void sleep(long millis)throws interruptedExcption
"millis”参数是指线程休眠的毫秒数。上述代码涉及了抛出异常的问题,由于目前还没有开始讲述抛出异常,所以只要知道是抛出异常就可以。
线程的唤醒
线程的唤醒是指,使线程从休眠等待状态进入可执行状态,可以通过调用方法"interrupt()”来实现。
用法:
public class thread6{ public static void main(String[] args){ compute t=new compute(); t .start() t .interrupt(); } } //创建一个线程类在这个类中通过休眠来输出不同结果 class compute extends Thread{ int i = 0; public void run(){ //输出相应信息 System.out.println(”在工作中,不要打扰‘’); try{ sleep(1000000); }catch(exception e){ System.out.priatla("哦,电话来了’‘); } } }
线程让步
所谓线程让步,就是使当前正在运行的线程对象退出运行状态,让其他线程运行,其方法是通过调用“yield()”方法来实现。这个方法不能将运行权让给指定的线程,只是允许这个线程把运行权让出来,至于给谁,这就需要看由哪个线程抢占到了。
例子:
//创建一个主运行类 public class thread7{ public static void main(String[]args){ //创建两个线程对象t和t1 compute t=new compute(); compute1 t1=new compute1(); //启动两个线程 t.start(); t1.start(); } } //创建通过循环语句输出数字的类 class compute extends Thread{ //创建继承线程的类compute int i=0; //创建成员变量 public void run(){ //实现run()方法 for(int i=0;i<10;i++){ System.out.println(i); Yield(); //让线程暂停 } } } //创建通过循环语句输出数字的类 class compute1 extends Thread{//创建继承线程的类compute1 public void run(){//实现run()方法 for(int i=0;i<10;i++){ System.out.println(”这个数字是:”+i); } } }
结果:
0
这个数字是:0
这个数字是:1
这个数宇是:2
这个数字是:3
这个数字是:4
这个数字是:5
这个数字是:6
这个数字是:7
1
这个数字是:8
这个数字是:9
2
3
4
5
6
7
8
9
从运行结果来看.第1个线程比第0个线程运行的几率要小,因为它总是放弃运行权
线程同步
前面讲述过,线程的运行权通过一种叫抢占的方式获得。一个程序运行到一半时,突然被另一个线程抢占了运行权,此时这个线程数据处理了一半.而另一个线程也在处理这个数据那么会出现重复操作数据的现象,最终整个系统将会混乱。
同步块
同步块是使具有某个对象监视点的线程,获得运行权限的一种方法,每个对象只能在拥有这个监视点的情况下才能获得运行权限。举个例子,一个圆桌有4个人吃饭,但是只有一个勺子,4人中只有一个人能吃饭,并且,这个人必项是拥有勺子的人,而这个勺子就相当于同步块中的监视点。
同步块的结构如下
synchronized(someobject) { 代码段 }
"someobject”是一个监视点对象可以是实际存在的,也可以是假设的。在很多程序段中,这个监视点对象都是假设的。其实这个监视点;就相当于一把锁,给一个线程上了锁,那么其他线程就会被拒之门外,就无法得到这把锁。直到这个线程执行完了,才会将这个锁交给其他线程。其他的线程得到锁后,将自己的程序锁住,再将其他线程拒之门外。
例子:
//创建一个主运行类 public class threadl0{ public static void main(String[] //创建两个线程对象t, t1和t2 compute t=new compute(’a’); compute t1=new computel(‘b‘); compute t2”new compute(’c’); //启动三个线程 t.start(); t1.start(); t2.start(); } } //创建通过循环语句输出数字的类 class compute extends Thread{ char ch; static Object obj=new Object(); compute(char ch){ this.ch=ch; } public void print(char ch){ for(int i,0;i<10;i++){ System.out.print(ch); } } public void run(){ synchronized (obj){ for(int i=1;i<10;i++){ print(ch); System.out.println(); } }} }
结果:
aaaaaaaaa
aaaaaaaaa
aaaaaaaaa
aaaaaaaaa
aaaaaaaaa
aaaaaaaaa
aaaaaaaaa
aaaaaaaaa
aaaaaaaaa
bbbbbbbbb
bbbbbbbbb
bbbbbbbbb
bbbbbbbbb
bbbbbbbbb
bbbbbbbbb
bbbbbbbbb
bbbbbbbbb
bbbbbbbbbccccccccc
ccccccccc
ccccccccc
ccccccccc
ccccccccc
ccccccccc
ccccccccc
ccccccccc
ccccccccc
在运行程序中添加一个监视点,那么将锁内的程序段执行完后,就会自动打开锁,再由另外两个线程抢占这个锁。然后反复执行这个同步块中的程序,这样一个线程执行完后,才会执行另一个线程。对于多线程操作同一个数据。就不会出现混乱的现象。
同步化方法
同步化方法就是对整个方法进行同步。它的结构如下:
synchronized void f() { 代码 }
例子:
//创建一个主运行类 public class threadl0{ public static void main(String[] //创建两个线程对象t, t1和t2 compute t=new compute(’a’); compute t1=new computel(‘b‘); compute t2”new compute(’c’); //启动三个线程 t.start(); t1.start(); t2.start(); } } //创建通过循环语句输出数字的类 class compute extends Thread{ char ch; static Object obj=new Object(); compute(char ch){ this.ch=ch; } synchronized void print(char ch){ for(int i,0;i<10;i++){ System.out.print(ch); } } public void run(){ for(int i=1;i<10;i++){ print(ch); System.out.println(); } } }
从上面的结果可以看出,使用同步块和同步方法的输出结果是一样的。
实例分析
生产者与消费者
1个汉堡包店,有1个厨师和1个营业员。厕师负责做汉堡包,营业员负责卖汉堡包,当然还有1个存放汉堡包的箱子。厨师不停地做汉堡包,做好了就放在箱子里面,当每次客人来的时候,营业员就会从箱子里面取出1个汉堡包卖掉。假设前提是客人每隔1秒来1个,也就是说营业员1秒卖1个汉堡包,而厨师3秒做1个汉堡包。
目前总共只能做10个汉堡(材料只有10份),箱子中已经有了5个汉堡,请编写一个程序代码来显示这个买卖的关系。
代码如下:
设计汉堡包盒子类,将其作为监视点,代码如下: class ham { //创建把装汉堡包的盒子作为监视器类 Static Object box = new Object(); //创建对象box static int totalmaterial=10; //关于制作汉堡包的材料属性 static int sales=0; //关于销售多少个汉堡包属性 static int production=5; //关于一共有多少个汉堡包属性 } 设计厨师类,代码如下: class hmaker extends Thread//厨师线程类 { //make方法使用了一个同步块,在这个函数里会不断地生产汉堡包 public void make(){ synchroaiaed (ham.box){//创建同步块 (ham.production)++; try{ ham.box.notify(); }catch(Exception e){ } } } public void run(){ //重写run()方法 //使用循环语句来保证在汉堡包材料用完之前,不断地生产汉堡包 while(ham.production<ham.totalmaterial){ //使用判断语句判断只要有汉堡包,厨师就通知营业员可以卖了 if (ham.production>0){ System.out.println("厨师‘.+getName()+”:‘’十”汉堡包来了(总共‘’ +(ham.production-ham.sales)+‘’个)‘’); } try{ sleep(3000); //因为3秒后才能制造好一个,线程休眠3秒 }catch(Exception e){ } make(); //调用make()方法 } } } 设计营业员类: class hassistant extends Thread{ //关于营业员的线程类 public void sell(){ //创建营业员卖汉堡包的方法 if(ham.production==0) //当没有汉堡包时 System.out.println(顾客朋友们,请稍微等一下,汉堡包没了!!); } try{ ham.box.wait(); //使线程暂停,等待厨师类的通知 }catch(Exception e){ } ham.sales++; System.out.println("营业员:顾客好汉堡包上来了 (总共卖了‘’十ham.sales十’·个)); } public void run(){ //重写run()方法 //当盒子里面有汉堡包的情况下不断地卖 while(ham.sales<ham.production){ try{ sleep(1000); //线程休眠1秒 }catch(Exception e){ } sell(); //调用sell()方法 } } } 测试类: public class thread13{ //创建测试类 public static void main(String[]args){ //主方法 hmaker maker=new hmaker(); //创建对象maker hassistant assistant=new hassistant(); //创建对象assistant //对对象maker进行设置 maker .setName(”甲”); //启动线程 maker.start(); assistant.start(); } }
厨师类能做汉堡包的产品数量,应该小于总材料的数量。在这个前提条件下,如果产品不等于零,就让厨师告诉一声:汉堡包上来了,曾、共有多少个。接下来开始3秒做1个汉堡包,做好了后,再通知一声:汉堡包上来了,总共几个。另外,只要箱子里的汉堡包不等于零,就通知营业员可以卖了。而营业员类,主要是销售汉堡包,每1秒卖1个,如果箱子里面的汉堡包等于零,就通知顾客:汉堡包没了,需要等待。
结果:
厨师甲:汉堡包来了(总共5个)
营业员:顾客好,汉堡包上来了 (总共卖了1个)
营业员:顾客好,汉堡包上来了 (总共卖了2个)
厨师甲:汉堡包来了(总共4个)
营业员:顾客好,汉堡包上来了 (总共卖了3个)
营业员:顾客好,汉堡包上来了 (总共卖了4个)
营业员:顾客好,汉堡包上来了 (总共卖了5个)
厨师甲:汉堡包来了(总共2个)
营业员:顾客好,汉堡包上来了 (总共卖了6个)
营业员:顾客好汉堡包上来了 (总共卖了7个)
厨师甲:汉堡包来了(总共1个)
厨师甲:汉堡包来了(总共2个)