Java学习笔记---多线程

时间:2023-02-25 12:01:35

     由于本人也处于学习java的阶段,文章错误之处还请指正,谢谢!

1.进程与线程的概念:

   线程是进程内一个相对独立的基本调度单元。

   线程是操作系统的基本调度单元。

   进程在创建的时候必须同时创建一个线程(主线程)。

   线程可以创建其他线程。

   线程不被分配资源,而是使用进程的资源。

   线程在共享资源时,必须实现通信和同步机制。(很重要!)(以上资料参考《JAVA开发入行真功夫》)

2.操作系统多线程的实现

    创建一个进程时,它的第一个线程称为主线程(Primary   thread),由系统自动生成。然后可以由这个主线程生成额外的线程,而这些线程,又可以生成更多的线程。在运行一个多线程的程序时,从表面上看,这些线程似乎在同时运行。而实际情况并非如此,为了运行所有的这些线程,操作系统为每个独立线程安排一些CPU时间。单CPU操作系统以轮转方式向线程提供时间片(Quantum),每个线程在使用完时间片后交出控制,系统再将CPU时间片分配给下一个线程。由于每个时间片足够的短,这样就给人一种假象,好像这些线程在同时运行。创建额外线程的唯一目的就是尽可能地利用CPU时间。(摘自CSDN博客http://blog.csdn.net/whwjn/article/details/670852

3.线程的创建

   线程的创建有两种方法,一种是直接继承Thread类,然后重写run方法。另外一种是实现Runnable接口。

   直接继承Thread类:

public class ThreadT extends Thread
{
       public void run()
       {
              System.out.println("当前的进程名:"+this.getName());
       }
       public static void main(String[] args)
       {
              ThreadT t=new ThreadT();//创建线程实例
              t.start();//开始线程
              System.out.println("当前的进程名:"+Thread.currentThread().getName());
       }
}
运行结果如下:

当前的进程名:main
当前的进程名:Thread-0

    在这里我要解释几个问题,我也是通过看了孙鑫的java视频了解的。

    1).为什么首先运行的进程不是我们创建的t线程。

         代码中我们首先创建了t线程,然后通过start方法让其运行,可是为什么第一个输入的尽然是主线程main。这是因为当java程序从main函数进入之后便会自动创建一个主线程main,我们虽然定义了t.start()来让t线程启动,但此时,main线程仍然处于操作系统分配的时间片中,所以此时t线程并没有启动,而是等后面的代码执行完,也就是main线程结束时才真正的启动了。稍后我会通过一段代码来测试这种情况。

   2)main函数中输出进程名为什么不能通过this.getName()来获得。

        因为main函数属于staic函数,不属于任何对象,无法调用thi对象,只能通过Thread.currentThread()方法获得当前的进程名。(currentThread()方法属于Thread类当中的static类型,可以直接通过类访问。)这中现象当我们用继承Runnable接口来创建进程时也会遇到,稍后在解释。

      下面我们来通过一段代码来了解操作系统处理多线程时的时间片概念。我们把上面的代码的输出分别加上死循环,我们多创建几个线程,如下:

public class ThreadT extends Thread
{
public void run()
{      
              while(true)
System.out.println("当前的线程:"+this.getName());
}
public static void main(String[] args)
{
ThreadT t1=new ThreadT();//创建线程实例
              ThreadT t2=new ThreadT();
              t1.start();//开始线程
              t2.start();
              while(true)
              System.out.println("当前的线程main:"+Thread.currentThread().getName());
}
}
     大家可以把这段代码自己试着编译运行一下,因为输出结果较多,我就不贴出来了,结果应该三个线程交替输出,这就是时间片的原因,操作系统会分配给每个线程一个时间片。如果时间片时间没到,就会继续执行这个线程,如果时间片的时间到了,不管当前进程的任务有没有执行完,都会执行别的进程,这样循环往复,操作系统就实现了多线程处理。

    间接继承Runnable 接口创建线程

  

public class ThreadT
{
       public static void mian(String[] args)
       {    
             RunnableT a=new Runnable();//注意。此时a并不是一个线程,他并没有继承Thread类,而只是实现了Runnable接口
             Thread t=new Thread(a);//创建一个线程实例
             t.start();
             System.out.println("当前的线程名:"+Thread.currentThread().getName());
       }
}
class RunnableT implenments Runnable
{
       public void run()//实现Runnable接口里面的run方法      
       {
              System.out.println("当前的线程名:"+Thread.currentThread().getName());
       }

}

程序输出结果和继承Thread一样,一般情况下我们用的比较多的是实现Runnable接口,很少去继承Thread类,因为在java中并不支持多继承,但是继承多接口。也就是我们继承了Thread类就不能在继承其他类了,这样给编程工作带来了很大麻烦。

4.线程的调度规则

       java通过java线程调度器来管理线程,一般来说是这样的:当线程优先级不同时,线程调度管理企优先调度优先级高的线程,而当线程优先级相同时,线程调度管理器则先调度先进入准备状态的线程。线程被创建时默认的优先级是5(最高10,最小1),我们可以通过函数setPriority和getPriotity来设置和取得当前线程的优先级,不过值得注意的是,不同平台间的差异会使java无法担保优先级对线程执行的影响,因而尽量避免使用线程优先级来控制应用程序。

5.线程的同步与控制

       前面我们说过线程不分配资源,而是共享进程的资源,这样当一个进程中有多个线程时,多个线程便会同时能对进程中的变量进行操作,而有些场合,我们只能让其中一个进程访问该变量时,我们就需要对进程进行控制,

       我们假如这样一个场景:*在*这安排了一个间谍,间谍每次得到情报后都会把情报放在一个树洞里,然后通知*的人来去,*的人取走放在树洞的情报后便会通知间谍继续收集情报放在树洞里。(思路来自孙鑫JAVA视频)

      我们来为这个场景写一个例子

       首先定义一个树洞类,提供内存,以及存数取数的方法。

      

class Quene
{
private int value;//存放情报
       public void setValue(int i)
               value=i;
       public int getValue()
               return value;
       
}
    然后我们定义间谍线程

class Producer extends Thread

{

Quene q;
Producer(Quene q)
 {
           this.q=q;
 }
       public void run()
       {
           for(int i=0;i<10;i++)
              {
                q.setValue(i);
                System.out.println("setValue:"+i);
              }
       }
}

   再定义*线程,负责取走情报

class Customer extends Thread
{
      Quene q;
      Customer(Quene q)
      {
           this.q=q;
      }
      public void run()
      {
          System.out.println("getValue:"+q.getValue());
      }

   再定义主函数

public static void main(String[] args)
{
     Quene q=new Quene();
     Producer p=new Producer(q);
     Customer c=new Customer(q);
     p.start();
     q.start();
}
执行结果如下:


setValue:0

setValue:1

setValue:2

setValue:3

setValue:4

setValue:5

setValue:6

setValue:7

setValue:8

setValue:9

 

getValue:9

getValue:9

getValue:9

getValue:9

getValue:9

getValue:9

getValue:9

 
我们发现间谍确实是依次放入了10个数,但是*这边只取走了9,这是什么原因呢,我们来分析一下代码 

      当我们进入 p线程时,也就是for循环那,当其放入0之后,由于该线程的时间片还没停止,所以会继续执行放数操作,这就导致了该进程结束后,c进程启动时得到的数永远都是9的原因。

      这时,我们就需要在p线程每次放入数之后暂停线程,唤醒c线程完成取数操作,待c完成取数操作后,再唤醒p线程再向里面存数。这是我们就需要用到wait和notify函数

   完整修改代码如下:

public class test
{
public static void main(String[] args)
{
Quene q=new Quene();
Producer p=new Producer(q);
Customer c=new Customer(q);
p.start();
c.start();
}
}
class Quene
{
private int value;//存放情报i
boolean full=false;
public synchronized void setValue(int i)//synchronzied 关键字表示为当前方法加锁,只有当在使用该方法的线程结束解锁当前方法后才能别的线程访问
{
if(!full)//当前数据为空,存放数据
{
value=i;
full=true;
notify();//通知c线程取走数据
}
try
{
wait();//暂停当前进程
}
catch(Exception e)
{
e.printStackTrace();
}

}
public synchronized int getValue()
{
if(!full)//当前数据为空
try
{
wait();//暂停进程,等待p进程通知
}
catch(Exception e)
{
e.printStackTrace();
}
full=false;
notify();// 通知p进程存放数据
return value;
}

}
class Producer extends Thread
{

       Quene q;
       Producer(Quene q)
       {
           this.q=q;
       }
       public void run()
       {
           for(int i=0;i<10;i++)
              {
               
                System.out.println("setValue:"+i);
               q.setValue(i);
               
              }
       }
}
class Customer extends Thread
{
      Quene q;
      Customer(Quene q)
      {
           this.q=q;
      }
      public void run()
      {
           while(true)
          {
             System.out.println("getValue:"+q.getValue());
           }
      }
}  
最后执行结果如下:已经实现程序的功能了

setValue:0
getValue:0
setValue:1
getValue:1
setValue:2
getValue:2
setValue:3
getValue:3
setValue:4
getValue:4
setValue:5
getValue:5
setValue:6
getValue:6
setValue:7
getValue:7
setValue:8
getValue:8
setValue:9
getValue:9
感觉线程的知识有点多,下班了,先写到这吧!