java系统学习(十) --------线程

线程的基本概念

进程以及使用环境

程序是计算机指令的集合,它以文件形式存储在磁盘上,而进程就是一个执行中的程序,每一个进程都有其独立的内存空间和系统资源。
进程就是一个运行的程序,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);
                }
        }
}

这两种方式的详细使用:

ExecutorService实现java多线程

线程的使用

线程的优先级

线程的执行顺序是一种抢占方式,优先级高的比优先级低的要获得更多的执行时间,如果想让一个线程比其他线程有更多的时间运行,可以通过设置线程的优先级解决。
如一个线程创建后,可通过在线程中调用“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个)

时间: 2024-10-12 13:34:26

java系统学习(十) --------线程的相关文章

java基础学习总结——线程(一)

永不放弃,一切皆有可能!!! 只为成功找方法,不为失败找借口! java基础学习总结——线程(一) 一.线程的基本概念 线程理解:线程是一个程序里面不同的执行路径 每一个分支都叫做一个线程,main()叫做主分支,也叫主线程. 程只是一个静态的概念,机器上的一个.class文件,机器上的一个.exe文件,这个叫做一个 进程.程序的执行过程都是这样的:首先把程序的代码放到内存的代码区里面,代码放到代码区后并没有马上开始执行,但这时候说明了一个进程准备开始,进程已 经产生了,但还没有开始执行,这就是

Java并发学习之五——线程的睡眠和恢复

本文是学习网络上的文章时的总结,感谢大家无私的分享. 1.Thread类的sleep方法,可以使线程睡眠.此方法接收一个整数作为参数,表示线程暂停运行的毫秒数.在调用sleep方法后,当时间结束时,JVM会安排他们CPU时间,线程会继续按指令执行. 另一种可能是使用一个有TimeUnit列举元素的sleep方法,使用线程类的sleep方法让当前线程睡眠,但是它接收的参数单位后会转成毫秒的. 2.当你调用sleep()方法,Thread离开CPU并在一段时间内停止运行.在这段时间内,他是不消耗CP

java SE学习之线程同步(详细介绍)

       java程序中可以允许存在多个线程,但在处理多线程问题时,必须注意这样一个问题:               当两个或多个线程同时访问同一个变量,并且一些线程需要修改这个变量时,那么这个程序是该如何执行?              也就是先访问这个变量还是先修改这个变量.              在学习线程的这段时间里,我也一直被这个问题所困扰!但是今天终于算是搞明白了.              于是将这些好的列子一一列举出来,分享一下. (1)什么是线程同步 ?      

java基础学习总结——线程(二)

永不放弃,一切皆有可能!!! 只为成功找方法,不为失败找借口! java基础学习总结——线程(二) 一.线程的优先级别 线程优先级别的使用范例: 1 package cn.galc.test; 2 3 public class TestThread6 { 4 public static void main(String args[]) { 5 MyThread4 t4 = new MyThread4(); 6 MyThread5 t5 = new MyThread5(); 7 Thread t1

Java并发学习之三——线程的中断

本文是学习网络上的文章时的总结,感谢大家无私的分享. 1.一个多个线程在执行的Java程序,只有当其全部的线程执行结束时(更具体的说,是所有非守护线程结束或者某个线程调用System.exit()方法的时候),它才会结束运行.有时,你需要为了终止程序而结束一个线程,或者当程序的用户想要取消某个Thread对象正在做的任务. 2.Java提供中断机制来通知线程表明我们想要结束它.中断机制的特性是线程需要检查是否被中断,而且还可以决定是否相应结束的请求.所以,线程可以忽略中断请求并且继续运行. 3.

java基础学习总结-----线程(一)

转:基础学习总结----线程(一)http://www.cnblogs.com/xdp-gacl/p/3633936.htmljava 一.线程的概念 线程:线程是一个程序里面不同的执行路径 进程:一个静态的概念,机器上的一个.class文件,机器上的一个.exe文件,这个叫做一个进程. 程序的执行过程: 1.首先把程序的代码放到内存的代码区里面,代码放到代码区后并没有马上开始执行,但这时候说明了一个进程准备开始,进程已经产生,但还没有开始执行,这就是进程.其实是2.一进程的执行指的是进程里面主

你要Java学习指南来了!整理了一份零基础Java系统学习路线送给你们!

这里整理一份 Java 的学习路线,希望对想要学习 Java 还没有头绪的小伙伴有所帮助~ 第一阶段.学习 JavaSE 第二阶段.HTML+CSS   js+jQuery   第三阶段.学习JavaEE:jsp+servlet   数据库:MySQL+Oracle (建议先学MySQL)   第四阶段.框架SSH(Struts2+Spring+Hibernate)   SSM(Spring+SpringMVC+mybatis)   大致就是这些了,相信小伙伴们学完这些,学习能力已经有了很大的提

Java多线程学习之线程的状态及中断线程

线程的状态 新建(new):当线程被创建时,它只会短时间处于这种状态.它已经分配了必要的系统资源,完成了初始化.之后线程调度器将把这个线程转变为可运行或者阻塞状态: 就绪(Runnable):在这种状态下,只要调度器分配时间片给线程,线程就可以运行了: 阻塞(Blocked):有某个条件阻止线程运行,调度器将忽略阻塞状态的线程,不会分配时间片给它,直到线程进入就绪状态,它才有可能执行: 死亡(Dead):处于死亡或者终结状态的线程将不再是可调度的,并且也不会被分配到时间片.任务死亡的方式通常是从

java核心学习(十八) javaNIO框架---“块”模型的IO

一.java新IO概述 javaIO中的输入流和输出流都是通过字节的移动来处理的,面向流的输入输出系统一次只能处理一个字节,因此效率不高,而且传统的输入输出流是阻塞试的,也就是说当无法读到数据时,当前线程会被阻塞直到读取到有效数据才会继续运行. java1.4之后提供了一系列改进的输入输出类与方法,并且以NIO为基础改写了java.io包中的类,新增了满足NIO的功能. NIO采用内存映射文件的方式,java.nio中主要的包有: java.nio ,主要包含于Buffer相关的类: java.