java 多线程简述

来源:互联网 发布:华丽上班族电影知乎 编辑:程序博客网 时间:2024/06/04 17:56

   多线程的基本实现

     进程指运行中的程序,每个进程都会分配一个内存空间,一个进程中存在多个线程,启动一个JAVA虚拟机,就是打开个一个进程,一个进程有多个线程,当多个线程同时进行,就叫并发。

   J ava创建线程的两种方式为: 继承Thread类 和实现Runnable接口

   Thread类

   1、通过覆盖run方法实现线程要执行的程序代码

   2、Start()开始执行多线程

package com.bin.duoxiancheng;public class d1 extends Thread{    public void run(){       for(int i=0 ; i<50; i++){           System.out.println(i);           System.out.println(currentThread().getName());           try {              sleep(100);           } catch (InterruptedException e) {              // TODO Auto-generatedcatch block              e.printStackTrace();           }       }    }    public static void main(String[] args){       new d1().start();       new d1().start();    }}


多个线程共享一个实例的时候,代码代码如下:

package com.bin.duoxiancheng;public class d1 extends Thread{    int i=0;    public void run(){       for(i=0 ; i<50; i++){           System.out.println(i);           System.out.println(currentThread().getName());           try {              sleep(100);           } catch (InterruptedException e) {              // TODO Auto-generatedcatch block              e.printStackTrace();           }       }    }    public static void main(String[] args){       new d1().start();       new d1().start();    }}


结果如下所示:

0

Thread-1

0

Thread-0

1

Thread-1

1

实际2个线程在操纵不同的变量a,在执行run方法时候,线程把a都当做自己的变量在执行。

Runnable接口实现多线程

当一个继承自Thread时,就不能再继承其他类,使用Runnable接口解决了此问题,在新建一个Thread类中,在构造方法中初始化

Thread(Runnable target)
          分配新的 Thread 对象。

Thread(Runnable target,String name)
          分配新的 Thread 对象。

package com.bin.duoxiancheng;public class D2 implements Runnable{    int i=0;    public void run(){       for(i=0 ; i<50; i++){           System.out.println(i);           System.out.println(Thread.currentThread().getName());           try {              Thread.sleep(100);           } catch (InterruptedException e) {              // TODO Auto-generatedcatch block              e.printStackTrace();           }       }    }    public static void main(String[] args){       D2 d=new D2();        Thread t=new Thread(d);        t.start();    }}


线程中状态之间的转变

New状态,使用new创建的线程对象处于新建状态,仅仅在堆栈中分配了内存。

Runnable就绪状态。当线程调用start方法后,线程进入就绪状态。虚拟机会为他创建方法调用栈和程序计数器,处于这个状态的线程位于可运行池中,等待获得CPU使用权。

Running运行状态

  这个状态的线程正在占用CPU,如果一个计算机只有一个CPU,那么这个时刻会有一个线程处于这个状态,如多个CPU,可有多个线程占用不同的CPU,只有处于就绪状态的线程才有机会变为运行状态。

阻塞状态(Blocked)

 由于某些原因放弃CPU暂时停止运行,此时虚拟机不会给线程分配CPU,直到重新进入就绪状态,

   阻塞状态可分为以下3种。

1、 当处于运行状态中的某个对象执行了wait()方法,虚拟机就把这个线程放到这个对象等待池中。wait()是用来锁定一个对象的,在调用这个notify()方法前,他后面的代码永远不会被执行。这种锁定不能够自动解锁, 你必须在另一个线程中调用这个对象的notify()方法。

2、 当线程处于运行状态,试图获得某个对象的同步锁,若同步锁被其他线程占用,虚拟机会把这个线程放到这个对象的锁池中。

3、 当线程执行了sleep方法,或者调用join方法,或者发出了I/O请求时候,就会进入这个状态.

当线程执行System.out.println或者System.in.read()方法时,就会发出I/O请求,线程放弃cpu进入阻塞状态,直到I/O处理完成,

死亡状态(dead)

当线程退出run()方法时,就进入死亡状态,有可能正常 执行完run方法退出,也有可能异常退出,

Thread类中的isAlive()方法判断一个线程是否活着,当处于死亡状态或者新建状态时,该方法返回false,否则返回TRUE。

线程的调度

  虚拟机按特定的机制为CPU分配使用权,主要有两种调度模式,分时调度模型和抢占式调度模型。分时调度模型就是线程轮流让CPU获得使用权,平均分配线程。抢占式调度为让线程中优先度高的占用CPU,若优先级相同,那么就随机选择一个。处于运行状态的线程会由以下原因放弃CPU

   1、虚拟机转到就绪状态,其他线程获得机会。

   2、线程进入阻塞状态;

   3、线程运行结束

看以下程序:

package com.bin.duoxiancheng;public class D3 extends Thread {    public static int count=0;    public static StringBuffer log=new StringBuffer();    public void run(){     for(int a=0;a<20;a++){        log.append(currentThread().getName()+":"+a+" ");        if(++count %10 ==0)log.append("\n");     }    }    public static void main(String[] args) throws InterruptedException {       // TODO Auto-generatedmethod stub          D3 d1= new D3();          D3 d2 =new D3();          d1.setName("m1");          d2.setName("d2");          d1.start();          d2.start();          while(d1.isAlive()||d2.isAlive())          Thread.sleep(500);          System.out.print(log);    }}


结果为:

m1:0 m1:1 m1:2 m1:3 m1:4 m1:5m1:6 m1:7 m1:8 m1:9

m1:10 m1:11 m1:12 m1:13 m1:14m1:15 m1:16 m1:17 m1:18 m1:19

m2:0 m2:1 m2:2 m2:3 m2:4 m2:5m2:6 m2:7 m2:8 m2:9

m2:10 m2:11 m2:12 m2:13 m2:14 m2:15 m2:16m2:17 m2:18 m2:19

 

程序中m1先获得主动权,直到结束才交予m2,如果想让一个线程交给另外一个线程运行可以采取以下方法:

1、  调整优先级;调用Thread.setPriority(Thread.MAX_PRIORITY),参数取值范围为1-10,值越高优先度越高。

2、 调用Thread.sleep()方法;参数以毫秒为单位,当睡眠结束时候就会转为就绪状态,当另一个线程处于运行状态中,此线程在运行池 中处于等待状态

3、 调用Thread.yield()方法,如有有相同优先级别的线程处于就绪状态,那么yield把当前运行的线程放到可运行池中并使另一个线程运行;若没有相同级别的,则不运行,执行yield后,当前线程进入就绪状态,跟SLEEP不同,sleep为进入阻塞状态。

4、 调用JOIN方法;使用此方法后,当前程序进入阻塞状态。直至另一个程序结束才会恢复 运行。

  

package com.bin.duoxiancheng;public class D5 extends Thread{    public void run(){       for(int a=0;a<50;a++){           System.out.println(getName()+":"+a);       }    }    public static void main(String[] args) throws InterruptedException {       D5 d=new D5();       d.setName("m1");       d.start();       System.out.println("main: joinmachine");       d.join();       System.out.println("main:end");     }}

System.out.println("main:end");这段程序要等到d这个线程结束后方执行。

也可以写作d.join(10)超过10毫秒恢复运行。

Timer类定时器的使用

 参数使用如下:

(1)Timer.schedule(TimerTask task,Datetime)安排在制定的时间执行指定的任务。
(2)Timer.schedule(TimerTask task,Date firstTime ,long period)安排指定的任务在指定的时间开始进行重复的固定延迟执行.
(3)Timer.schedule(TimerTask task,long delay)安排在指定延迟后执行指定的任务.
(4)Timer.schedule(TimerTask task,long delay,long period)安排指定的任务从指定的延迟后开始进行重复的固定延迟执行.  第一个参数是要操作的方法,第二个参数是要设定延迟的时间,第三个参数是周期的设定,每隔多长时间执行该操作。
(5)Timer.scheduleAtFixedRate(TimerTask task,Date firstTime,long period)安排指定的任务在指定的时间开始进行重复的固定
速率执行.
(6)Timer.scheduleAtFixedRate(TimerTask task,long delay,long period)安排指定的任务在指定的延迟后开始进行重复的固定速率执行.

public class EggTimer {      private final Timer timer = new Timer();    private final int minutes;    public EggTimer(int minutes) {       this.minutes = minutes;    }    public void start() {       timer.schedule(new TimerTask() {           public void run() {               playSound();              // timer.cancel();            }           private void playSound() {               System.out.println("Your egg is ready!");                // Start a new thread to play a sound...            }       }, 0,minutes * 60 * 1000);//几分钟执行一次    }   public static void main(String[] args) {       EggTimer eggTimer = new EggTimer(2);       eggTimer.start();    }}


同步代码块

   为了保证线程能正常的执行原子操作,java引入了同步机制,在代码原子操作的程序代码前加上synchronized标记 ,这样,任何时刻只允许一个线程拥有这把锁,

   这样当加了同步标记后,其他待进入线程放到Stack对象锁池中,线程进入阻塞状态。加锁方式如下:

Public synchronized String pop(){…}

也可以写作

Public String pop(){

 Synchronized(this){…}

}

下面看一个消费者,创造者的例子

public class SyncTest {    public static void main(String[] args) {       Stack stack =new Stack("stack");       Producer producer1=new Producer(stack,"producer1");       Consumer consumer1=new Consumer(stack,"consumer");    }}class Producer extends Thread{       private Stack theStack;       public Producer(Stacks,String name){          super(name);          theStack = s;          start();       }       public void run(){          String goods;          for(int i=0;i<20;i++){              goods="goods"+(theStack.getPoint()+1);              theStack.push(goods);              System.out.println(getName()+": push "+goods+" to "+theStack.getName());              yield();          }       }}class Consumer extends Thread{       private Stack theStack;       public Consumer(Stacks,String name)       {          super(name);          theStack=s;          start();       }       public void run(){          String goods;          for(int i =0;i<20;i++){              goods=theStack.pop();              System.out.println(getName()+":pop "+goods+" from "+theStack.getName());              yield();          }       }}    class Stack{       private  String name;       private String[] buffer=new String[100];       int point = -1;       public Stack(String name){this.name=name;}       public String getName(){return name;}       public int getPoint(){return point;}       public synchronized String pop(){           String goods=buffer[point];           buffer[point]=null;           Thread.yield();           point --;           return goods;       }       public synchronized void push(String goods){           point ++;           Thread.yield();           buffer[point] =goods;       }    }


同步和并发问题

   同步锁解决资源共享资源竞争的有效手段,当一个线程已经操作共享资源,其他线程只有等待,只有当已经在操作共享资源的线程执行完毕后才能同步代码块,其他线程才能有机会共享。

以打水为例子,10个去打水,每人要打10桶,使用同步方式为,一个人打完10桶后,其他人才有机会打水,,当一个人打水,其他人必须等待,显然不合理。

public class Person extends Thread{private Well well;public Person (Well well){    this.well=well;    start();}public void run(){    synchronized(well){       for(int i=0;i<10;i++){           well.withdraw();           yield();       }          }}    /**     * @param args     */    public static void main(String[] args) {       Well well=new Well();       Person persons[]=new Person[10];       for(int i=0;i<10;i++){           persons[i]=new Person(well);       }     } }class Well{    private int water=100;    public void withdraw(){       water --;       System.out.println(Thread.currentThread().getName()+": water left:"+water);    }}


则我们把同步锁加在withdraw(),则可以解决问题,例:

public class Person extends Thread{private Well well;public Person (Well well){    this.well=well;    start();}public void run(){          for(int i=0;i<10;i++){           well.withdraw();           yield();       }}    public static void main(String[] args) {       Well well=new Well();       Person persons[]=new Person[10];       for(int i=0;i<10;i++){           persons[i]=new Person(well);       }    }}class Well{    private int water=100;    public synchronized void withdraw(){       water --;       System.out.println(Thread.currentThread().getName()+": water left:"+water);    }}


线程安全类

一个线程安全类必须满足以下条件:

1、  这个类可以同时被多个线程安全访问。

2、  每个线程都能正常执行原子操作,得到正确结果。

3、  原子操作万仇,对象处于逻辑是合理状态。

  如上我们使用的Stack类就是可变类,point和buffer都可以发生变化,会造成共享资源的竞争。

线程之间的通信

不同线程之间执行不同的任务,如果任务有某种联系,必须实现通信功能,协调完成工作。

Java.lang.object类提供了用于线程通信的方法:

1、  wait(),该方式的线程释放对象锁,虚拟机把该对象放到等待池中,等待其他线程唤醒,并且释放所持有的对象的lock。

2、  notify(),该方法唤醒在等待池中的线程,虚拟机从等待池中随机选择一个线程.

3、  notifyAll()方法用于把对象池中所有的线程都转到对象锁池中。

例t1线程和t2线程可用wait和notify进行通信。

以编程的方式控制线程

实际编程中,一般是在受控的线程中定义一个标志量,其他线程通过改变标准变量的值,来控制线程的暂停、恢复运行已经自然终止,jdk1.2开始一些废弃了一些停止线程的方法

suspend() 使线程暂停,调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend().

resume() 暂停的线程恢复运行;

stop() 终止线程,它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在;

废弃的原因大概由于以上几个方法容易导致死锁等。

ThreadLocal类

 java.lang.ThreaLocal一般用来存放线程的局部变量,每个线程一般都有单独的局部变量,彼此 之间不会共享,该类主要有三个方法:

1、 public T get() 返回巨变线程的变量。

2、 protected T initialValue() 返回局部变量的初始值;

3、 public void set(T value) 设置当前线程的局部变量;

 其实看看TheadLocal的源码,我们可以发现他的思路,在类中有一个Map缓存,用于存储每个线程的局部变量,例:

package com.bin.duoxiancheng;import java.util.Collections;import java.util.HashMap;import java.util.Map;public class ThradLocal<T> {  private Map<Runnable,T> values= Collections.synchronizedMap(new HashMap<Runnable,T>());  public T get(){  Thread cuThread = Thread.currentThread();  T o=values.get(cuThread);  if(o ==null && !values.containsKey(cuThread)){  o=initValue();  values.put(cuThread, o);  }  return o;  }  public void set(T newValue){  values.put(Thread.currentThread(), newValue);  } protected T initValue(){ return null; }}


以上程序仅仅实现了总体思路,细节上ThreadLocal类实现的更健壮,还保证了线程结束后,从Map缓存中删除这个线程的局部变量的引用。

0 0
原创粉丝点击