Java多线程

多线程基本知识

一、线程的定义及创建

线程:每一个任务称为一个线程。应用线程可以实现一个应用同时执行不同的任务。

线程的创建:创建线程可以有两种方法

①直接继承Thread类(不提倡,因为这样会限制线程类继承其他类),需重写run()方法。

class MyThread extends Thread{

     publicvoid run(){

…………………

    }

}

类写好之后,创建对象,然后调用start()方法启动线程

②实现Runnable接口

class MyThread implements Runnable{

     publicvoid run(){

…………………

    }

}

类写好之后,创建对象,然后用Thread()类创建一个对象,将MyThread类对象传给Thread()对象,然后调用start()方法。例:

MyThread r = new MyThread();

Thread t = new Thread(r);

t.start();

注:不能直接调用run()方法,这样不会创建新的线程,只会在当前线程内调用此方法。

二、线程的状态

就绪态:处于这个状态的线程可以随时被CPU分配时间片段执行

阻塞态:当一个线程试图获取一个被其他线程占用的内部锁的时候,这个线程将进入阻塞态。当别的线程释放这个锁的时候,这个线程重新进入就绪态状态。当CPU分配给线程执行的时间片段用完的时候,线程被中断进入阻塞态。

等待态:当一个线程调用wait()方法的时候,这个线程将进入等待的状态,这个时候必须得到其他线程给他一个唤醒的信号如调用notify()方法,这时这个线程从新进入就绪态。

计时等待:处于这一状态的线程必须等待规定好的时间才能够重新被激活处于就绪态。如:sleep(1000)调用这个方法线程将会睡眠1秒钟。

线程的中断:当一个线程调用interrupt方法的时候,线程的中断状态将会被置位,可以通过检测这一状态来判断线程是否被终止,在当一个线程处于阻塞状态的时候,不能够将线程中断,这样会抛出一个异常:InterruptedException。同理当一个线程处于中断状态的时候,如果调用sleep这样的方法将线程进入阻塞状态,同样会抛出一个异常,同时中断状态将会被取消。

线程终止:当线程run方法正常退出,或者有未捕获的异常的时候,线程将会被终止。还可以通过stop方法终止线程,这个方法目前已经不被提倡。

以上介绍了一个线程的一个生命周期。

三、线程的属性

①线程优先级

可以给一个线程设置优先级。线程的优先级代表了一个线程被CPU调用的优先顺序,MIN_PRIORITY(1)、MAX_PRIORITY(10)、NORM_PRIORITY(5)、默认情况下线程的优先级为5,

可调用setPriority方法设置线程的优先级。可以调用yield方法释放线程的使用权。

②守护线程

可以调用setDaemon(true)方法将线程设置为守护线程,守护线程可以为其他线程进行服务,如计时线程。当只剩下守护线程在运行的时候,虚拟机将会自动退出。尽量少用守护线程去访问固有资源。

③未捕获异常处理器

未捕获异常处理器会在线程死亡之前接收未捕获异常。

处理器实际上就是一个实现了Thread.UncaughtExceptionHandler接口的类,这个接口中有一个void uncaughtException(Thread t, Throwable e)方法,同时还可以为任何线程安装一个处理器(setUncaughtExceptionHander方法),或者为所有的线程安装一个默认的处理器(setDefaultUncaughtExceptionHandler方法)。如果没有安装处理器这时处理器就是线程组对象(ThreadGroup)。处理器接收了线程的异常。

并不需要研究异常处理器内部是怎么实现的。

还可以将错误记录在日志文件中。

四、同步

(一)为什么需要线程同步

在一个复杂的应用软件中,会有许多独立的线程,这些线程可能实现不同的功能,但是有可能访问相同的资源。因此线程之间就产生了竞争,这种竞争的产生会导致程序数据的不一致性。

例:

packagethread;

publicclass TestSync implements Runnable {

      Timer timer = new Timer();

      public static void main(String[] args) {

            // TODO Auto-generated method stub

            TestSync test = new TestSync();

            Thread t1 = new Thread(test);

            Thread t2 = new Thread(test);

           

            t1.setName("t1");

            t2.setName("t2");

            t1.start();

            t2.start();

      }

      @Override

      public void run() {

            // TODO Auto-generated method stub

            timer.add(Thread.currentThread().getName());      //获取到当前的线程的名字

      }

 

}

classTimer{

      private static int num = 0;

      public void add(String name){ 

                  num++;

                  try{

                       Thread.sleep(1);

                  }catch(InterruptedExceptionew){}

                  System.out.println(name +", 你是第"+num+"个使用timer的线程");

            //}

      }

}   

这段程序我们可以看出线程之间出现了竞争。

为了线程之间产生竞争的问题,线程同步产生了。

(二)锁

实现线程同步有两种方法:

①实现一个ReentrantLock类对象(可重入锁,可有多个相关条件)

例:private Lock myLock = newReentrantLock();

    myLock.lock();  //加锁

     myLock.unlock();  //解锁

可重入锁的意思是:调用这个锁的线程可以重复调用这个锁(用一个计数器记录锁被调用的次数),调用几次锁,在线程结束之前必须释放几次锁。

使用相同锁的方法之间可以互相调用。

条件对象:一个锁可以拥有多个条件对象,通过条件对象,线程可以释放当前锁。

条件对象的创立:

Private Condition con =myLock.newCondition();  //获得一个条件对象

当条件不满足的时候:con.await()将线程转为阻塞态,同时释放占用的锁对象。此时,线程不能自动的由阻塞态转变成就绪状态。

另外一个线程调用:con.signal()方法随机的将一个调用await方法的线程从阻塞态转变成就绪态,当这个线程重新获得锁后,线程重新检测条件对象如果不满足从新进入阻塞态,反之继续从被阻塞的地方继续执行。

 con.signalAll()方法也可以激活所有用await方法阻塞的线程。这是与con.signal()方法的不同之处。

由于被阻塞的线程不能够自启,所以在编写代码的时候要预防死锁的情况发生。

②使用synchronized关键字(内部锁从1.0版本开始每一个对象都有一个内部锁,只有一个相关条件)

实现:

public synchronized void method(){

     Methodbody

}

此时method对象就获得了一个内部锁,

同上上面介绍的锁对象有所不同,内部锁只有一个相关条件

相关条件:wait方法使一个线程转入阻塞态,同await方法一样,这个线程也不能自己转为就绪态。

调用notify、notifyAll方法将线程激活,notify激活其中一个线程,notifyAll激活所有的线程。

注:尽量使用synchronized方法加锁

(三)同步阻塞

所谓同步阻塞就是利用别的java对象所持有的锁来锁定当前代码块

如:

private Object lock = new Object();

synchronized (lock){

………………..

}

一个类中要想得到完全的同步必须实现类中所有的方法都必须加锁,如果只是部分加锁,没有加锁的方法可以被其他线程访问从而导致出错。

在线程安全方面,Per Brinch Hansen 和 Tony Hoare提出了监视器。监视器自动的为方法加锁和解锁,监视器中的变量都是私有的,这保证了数据的安全。

扩充:当只需要对一个属性进行加锁的时候这样会浪费一定的计算机资源,这时需要使用volatile关键字,被这个关键字修饰的属性为可变的,当多个CPU同时访问这个属性的时候,一个CPU中的线程对它进行了修改,另外一个CPU在使用的时候重新检测,要从内存中重新读取。因此volatile不能保证原子性。

原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

线程锁保证了原子性。

为了提高线程的效率,可以为线程提供局部变量,局部变量属于当前线程。

终止线程:可以用stop终止线程,但是这个方法已经不被应用了,无法保证线程安全,在调用stop的时候线程会立即终止,释放资源,这是极不安全的。

 五、Callable与Future

Callable接口与Runnable接口类似,但是Callable接口拥有一个call方法,且Callable接口是一个参数化的类型,类型参数确定返回值的类型。

Futrue

用来接收返回值,接收call中计算出来的值。

public interface Future<V>{

      Vget() throws…;   //阻塞方法

      Vget(long timeout, TimeUnit unit) throws…;  //阻塞方法

      voidcancel(Boolean mayInterrupt);      //取消计算

      BooleanisCancelled();     //检测计算是否被取消

      BooleanisDone();         //检测计算是否完成

}

FutrueTask

包装器可将Callable转换成Future和Runnable。

例:

Callable<Integer> one = ………..;

FutrueTask<Integer> task =new FutrueTask<Integer>(one);

Thread t = new Thread(task);

 

 

 

 

 

   
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值