线程

五.线程

1.线程的状态

Java中的线程有五种状态分别是:新建、就绪、运行、死亡、阻塞。

1.新建状态:当利用new关键字创建线程对象实例后,它仅仅作为一个对象实例存在,JVM没有为其分配线程运行资源;对处于创建状态的线程可以进行两种操作:一是启动 (start)操作,使其进入可运行状态,二是终止(stop)操作,使其进入消亡状态。如果进入到消亡状态,那么,此后这个线程就不能进入其他状态,也就是说,它不再存在了。(不能进入阻塞状态)

start方法是对应启动操作的方法,其具体功能是为线程分配必要的系统资源;将线程设置为就绪状态,从而可以使系统调度这个线程。  

2.就绪状态(可运行状态):在处于新建状态的线程中调用start方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间之外的其它系统资源,只等JVM的线程调度器按照线程的优先级对该线程进行调度,从而能够获得CPU时间片的机会。

在可运行状态可以进行多种操作,最通常的是从run()方法正常退出而使线程结束,进入消亡状态。此外,还可以有如下操作:

     挂起操作,通过调用suspend方法来实现; //须通过恢复(resume)操作使线程回到可运行状态。

     睡眠操作,通过调用sleep方法来实现;

     等待操作,通过调用wait方法来实现;

     退让操作,通过调用yield方法来实现;//把CPU控制权提前转交给同级优先权的其他线程。

     终止操作,通过调用stop方法来实现。

     前面三种操作都会使一个处于可运行状态的线程进入不可运行状态。  

3.阻塞状态:线程能够运行,但有某个条件阻止它运行。当线程处于阻塞状态时,调度机制将忽略线程,不会分配给线程任何CPU时间直到线程重新进入了就绪状态,它才有可能执行操作。

一个处于可运行状态的线程,如果遇到挂起 (suspend)操作、睡眠(sleep)操作或者等待(wait)操作,就会进入不可运行状态(阻塞状态)。另外,如果一个线程是和I/O操作有关的,那么,在执行I/O指令时,由于外设速度远远低于处理器速度而使线程受到阻塞,从而进入不可运行状态,只有外设完成输入/输出之后,该线程才会自动回到可运行状态。线程进入不可运行状态后,还可以再回到可运行状态。通常有三种途径 使其恢复到可运行状态。

     一是自动恢复。通过睡眠(sleep)操作而进入不可运行状态的线程会在过了指定睡眠时间以后自动恢复到可运行状态;由于I/O阻塞而进入不可运行状态的线程在外设完成I/O操作后,自动恢复到可运行状态。

     二是用恢复(resume)方法使其恢复。 如果一个线程由于挂起(suspend)操作而从可运行状态进入不可运行状态,那么,必须用恢复(resume)操作使其再恢复到可运行状态。

     三是用通知(notify或notiyA11)方法使其恢复。 如果由于等待(wait)操作转入不可运行状态,那么,必须通过调用notify方法或notifyAll方法才能使其恢复到可运行状态。采用等待操作往往是由于线程需要等待某个条件变量,当获得此条件变量后,便可由notify或notifyAll方法使线程恢复到可运行状态。

     在不可运行状态,也可由终止(stop)操作使其进入消亡状态。  

4.死亡状态:当线程的run()方法完成时就认为它死去。线程一旦死亡,就不能复生。如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

线程在一定条件下,状态会发生变化。线程变化的状态转换图如下:

在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。注意:阻塞态只能进入就绪态。

常见线程名词解释

主线程:JVM调用程序main()所产生的线程。

当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。

后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。

5.线程与进程的区别

线程和进程是不同的,每个进程都是一个独立运行的程序,拥有自己的变量,且不同进程间的变量不能共享;而线程是运行在进程内部的,每个正在运行的进程至少有一个线程,而且不同的线程之间可以在进程范围内共享数据。也就是说进程有自己独立的存储空间,而线程是和它所属的进程内的其他线程共享一个存储空间。线程的使用可以使我们能够并行地处理一些事情。线程通过并行的处理给用户带来更好的使用体验,比如你使用的邮件系统(outlook、Thunderbird、foxmail等),你当然不希望它们在收取新邮件的时候,导致你连已经收下来的邮件都无法阅读,而只能等待收取邮件操作执行完毕。这正是线程的意义所在。

3实现一个线程的方法

多线程有两种实现方法,分别是继承Thread与实现Runnable接口

继承java.lang.Thread,并重写它的run()方法,将线程的执行主体放入其中。

实现java.lang.Runnable接口实现它的run()方法,并将线程的执行主体放入其中。

这两种实现方式的区别并不大。继承Thread的方式实现起来较为简单,但是继承它的类就不能再继承别的类了,因此也就不能继承别的类的有用的方法了。而使用Runnable接口的方式就不存在这个问题了,而且这种实现方式将线程主体和线程对象本身分离开来,逻辑上也较为清晰,所以推荐大家更多地采用这种方式。

继承Thread类实现线程的示例:

public class ThreadTest extendsThread

    public void run() { 

        // 在这里编写线程执行的主体 

        // dosomething 

    } 

实现Runnable接口实现多线程的示例:

public class RunnableTestimplements Runnable

    public voidrun() { 

        // 在这里编写线程执行的主体 

        // do something 

    } 

5.启动一个线程是用run()还是start()?

启动线程肯定要用start()方法。当用start()开始一个线程后,线程就进入就绪状态,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。cpu分配给它时间时,才开始执行run()方法(如果有的话)。START()是方法,它调用RUN()方法.而RUN()方法是你必须重写的. run()方法中包含的是线程的主体。

继承Thread类的启动方式:

public class ThreadStartTest { 

    public staticvoid main(String[] args) { 

        ThreadTest tt = new ThreadTest();// 创建一个线程实例 

         tt.start(); // 启动线程 

    } 

实现Runnable接口的启动方式:

public class RunnableStartTest { 

    public staticvoid main(String[] args) { 

       Thread t = newThread(new RunnableTest());    // 创建一个线程实例

        t.start(); // 启动线程 

    } 

实际上这两种启动线程的方式原理是一样的。首先都是调用本地方法启动一个线程,其次是在这个线程里执行目标对象的run()方法。那么这个目标对象是什么呢?为了弄明白这个问题,我们来看看Thread类的run()方法的实现:

public void run() { 

    if (target !=null) { 

       target.run(); 

    } 

当我们采用实现Runnable接口的方式来实现线程的情况下,在调用new Thread(Runnable target)构造器时,将实现Runnable接口的类的实例设置成了线程要执行的主体所属的目标对象target当线程启动时,这个实例的 run()方法就被执行了。当我们采用继承Thread的方式实现线程时,线程的这个run()方法被重写了,所以当线程启动时,执行的是这个对象自身的 run()方法。总结起来就一句话,如果我们采用的是继承Thread类的方式,那么这个target就是线程对象自身,如果我们采用的是实现Runnable接口的方式,那么这个target就是实现了Runnable接口的类的实例。

4.线程同步的方法

用什么关键字修饰同步方法 ?synchronized关键字修饰同步方法

 同步有几种实现方法,都是什么?分别是synchronized,waitnotify

wait():使一个线程处于等待状态,并且释放所持有的对象的lock
sleep():
使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
notify():
唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
Allnotity():
唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

实现同步的方式

同步是多线程中的重要概念。同步的使用可以保证在多线程运行的环境中,程序不会产生设计之外的错误结果。同步的实现方式有两种,同步方法和同步块,这两种方式都要用到synchronized关键字。

给一个方法增加synchronized修饰符之后就可以使它成为同步方法,这个方法可以是静态方法和非静态方法,但是不能是抽象类的抽象方法,也不能是接口中的接口方法。下面代码是一个同步方法的示例:

public synchronized voidaMethod() { 

    //do something 

public static synchronized voidanotherMethod() { 

    //do something 

线程在执行同步方法时是具有排它性的。当任意一个线程进入到一个对象的任意一个同步方法时,这个对象的所有同步方法都被锁定了,在此期间,其他任何线程都不能访问这个对象的任意一个同步方法,直到这个线程执行完它所调用的同步方法并从中退出,从而导致它释放了该对象的同步锁之后。在一个对象被某个线程锁定之后,其他线程是可以访问这个对象的所有非同步方法的。

同步块是通过锁定一个指定的对象,来对同步块中包含的代码进行同步;而同步方法是对这个方法块里的代码进行同步而这种情况下锁定的对象就是同步方法所属的主体对象自身。如果这个方法是静态同步方法呢?那么线程锁定的就不是这个类的对象了,也不是这个类自身,而是这个类对应的java.lang.Class类型的对象。同步方法和同步块之间的相互制约只限于同一个对象之间,所以静态同步方法只受它所属类的其它静态同步方法的制约,而跟这个类的实例(对象)没有关系。

如果一个对象既有同步方法,又有同步块,那么当其中任意一个同步方法或者同步块被某个线程执行时,这个对象就被锁定了,其他线程无法在此时访问这个对象的同步方法,也不能执行同步块。

synchronized关键字用于保护共享数据。请大家注意“共享数据”,你一定要分清哪些数据是共享数据,请看下面的例子:

publicclass ThreadTest implements Runnable{

publicsynchronized void run(){

for(inti=0;i<10;i++) {

System.out.print("" + i);

}

}

publicstatic void main(String[] args) {

Runnabler1 = new ThreadTest(); //也可写成ThreadTestr1 = new ThreadTest();

Runnabler2 = new ThreadTest();

Threadt1 = new Thread(r1);

Threadt2 = new Thread(r2);

t1.start();

t2.start();

}}

在这个程序中,run()虽然被加上了synchronized关键字,但保护的不是共享数据。因为这个程序中的t1,t2 是两个对象(r1,r2)的线程。而不同的对象的数据是不同的,r1,r2 有各自的run()方法,所以输出结果无法预知。

synchronized的目的是使同一个对象的多个线程在某个时刻只有其中的一个线程可以访问这个对象的synchronized 数据每个对象都有一个“锁标志”,当这个对象的一个线程访问这个对象的某个synchronized 数据时,这个对象的所有被synchronized修饰的数据将被上锁(因为“锁标志”被当前线程拿走了),只有当前线程访问完它要访问的synchronized 数据时,当前线程才会释放“锁标志”,这样同一个对象的其它线程才有机会访问synchronized 数据。

示例3:

publicclass ThreadTest implements Runnable{

publicsynchronized void run(){

for(inti=0;i<10;i++){

System.out.print("" + i);

}

}

publicstatic void main(String[] args){

Runnabler = new ThreadTest();

Threadt1 = new Thread(r);

Threadt2 = new Thread(r);

t1.start();

t2.start();

}}

如果你运行1000 次这个程序,它的输出结果也一定每次都是:0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9。因为这里的synchronized 保护的是共享数据。t1,t2 是同一个对象(r)的两个线程,当其中的一个线程(例如:t1)开始执行run()方法时,由于run()synchronized保护,所以同一个对象的其他线程(t2)无法访问synchronized 方法(run 方法)。只有当t1执行完后t2 才有机会执行。

示例4:

publicclass ThreadTest implements Runnable{

public voidrun(){

synchronized(this){

for(inti=0;i<10;i++){

System.out.print("" + i);

}} }

publicstatic void main(String[] args){

Runnable r= new ThreadTest();

Thread t1 =new Thread(r);

Thread t2 =new Thread(r);

t1.start();

t2.start();

}}

这个程序与示例3 的运行结果一样。在可能的情况下,应该把保护范围缩到最小,可以用示例4 的形式,this 代表“这个对象”。没有必要把整个run()保护起来,run()中的代码只有一个for循环,所以只要保护for 循环就可以了。

示例5:

publicclass ThreadTest implements Runnable{

public voidrun(){

for(intk=0;k<5;k++){

System.out.println(Thread.currentThread().getName()+" : for loop : " + k);

}

synchronized(this){

for(intk=0;k<5;k++) {

System.out.println(Thread.currentThread().getName()+" : synchronized for loop : " + k);

}} }

publicstatic void main(String[] args){

Runnable r= new ThreadTest();

Thread t1 =new Thread(r,"t1_name");

Thread t2 =new Thread(r,"t2_name");

t1.start();

t2.start();

} }

运行结果:

t1_name :for loop : 0

t1_name : forloop : 1

t1_name :for loop : 2

t2_name :for loop : 0

t1_name :for loop : 3

t2_name :for loop : 1

t1_name :for loop : 4

t2_name :for loop : 2

t1_name :synchronized for loop : 0

t2_name :for loop : 3

t1_name :synchronized for loop : 1

t2_name :for loop : 4

t1_name :synchronized for loop : 2

t1_name :synchronized for loop : 3

t1_name :synchronized for loop : 4

t2_name :synchronized for loop : 0

t2_name :synchronized for loop : 1

t2_name :synchronized for loop : 2

t2_name : synchronizedfor loop : 3

t2_name :synchronized for loop : 4

第一个for 循环没有受synchronized保护。对于第一个for 循环,t1,t2 可以同时访问。运行结果表明t1执行到了k=2 时,t2 开始执行了。t1 首先执行完了第一个for 循环,此时t2还没有执行完第一个for循环(t2 刚执行到k=2)t1 开始执行第二个for 循环,当t1 的第二个for 循环执行到k=1 时,t2 的第一个for 循环执行完了。t2想开始执行第二个for 循环,但由于t1 首先执行了第二个for 循环,这个对象的锁标志自然在t1 手中(synchronized 方法的执行权也就落到了t1 手中),在t1 没执行完第二个for循环的时候,它是不会释放锁标志的。所以t2 必须等到t1 执行完第二个for循环后,它才可以执行第二个for 循环。 

5.synchronized

每个对象只有一把监视锁(monitor lock),对于同步块,synchornized获取的是参数中的对象锁:

synchornized(obj){

  //...............

}

线程执行到这里时,首先要获取obj这个实例的锁,如果没有获取到线程只能等待.如果多个线程执行到这里,只能有一个线程获取obj的锁,然后执行{}中的语句,所以,obj对象的作用范围不同,控制程序不同.假如:

public void test(){

  Object o = newObject();

  synchornized(obj){

  //...............

  } }

对于这段程序,多个线程之间执行到Object o = new Object();时会各自产生一个对象然后获取这个对象有监视锁,各自皆大欢喜地执行.而如果是类的属性:

class Test{

    Object o = new Object();

    public void test(){

    synchornized(o){

    //...............

    }   }

}

所有执行到Test实例的synchornized(o)的线程,只有一个线程可以获取到监视锁.有时我们会这样:

public void test(){

    synchornized(this){

    //...............

    }  }

那么所有执行Test实例的线程只能有一个线程执行.而synchornized(o)和synchornized(this)的范围是不同的,因为执行到Test实例的synchornized(o)的线程等待时,其它线程可以执行Test实例的synchornized(o1)部分,但多个线程同时只有一个可以执行Test实例的synchornized(this).而对于

synchornized(Test.class){

    //...............

}

这样的同步块而言,所有调用Test多个实例的线程只能有一个线程可以执行.

[synchornized方法]

如果一个方法声明为synchornized的,则等同于在此方法上调用synchornized(this).如果一个静态方法被声明为synchornized,则等同于在此方法上调用synchornized(.class).

如果一个类有两个synchronized方法,是不是同一时间只能有一个处于运行,这个两个方法的锁是一样的吗?答:它们的锁是this对象,如果是对不同的对象调用方法,起不到锁的作用.这儿有一个前提是加在了非静态方法上,如果是静态方法上则锁得是类对象,那么同一时间只能有一个处于运行。

5简述synchronizedjava.util.concurrent.locks.Lock的异同

主要相同点:Lock能完成synchronized所实现的所有功能

主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。

Lock是一个接口,它位于java.utils.concurrent包的子包locks中。concurrent包及其子包中的类都是用来处理多线程编程的。实现 Lock接口的类具有与synchronized关键字同样的功能,但是它更加强大一些。 java.utils.concurrent.locks.ReentrantLock是较常用的实现了Lock接口的类。下面是 ReentrantLock类的一个应用实例:

private Lock lock = newReentrantLock();   

public void testLock() { 

    lock.lock();// 锁定对象 

    try { 

        // dosomething 

    } finally{ 

        // 释放对对象的锁定 

        lock.unlock(); 

    } 

lock()方法用于锁定对象,unlock()方法用于释放对对象的锁定一般用法是将需要在lock()unlock()方法之间执行的代码放在try{}块中,并且在finally{}块中调用unlock()方法,这样就可以保证即使在执行代码抛出异常的情况下,对象的锁也总是会被释放,否则的话就会为死锁的产生增加可能。

使用synchronized关键字实现的同步,会把一个对象的所有同步方法和同步块看做一个整体,只要有一个被某个线程调用了,其他的就无法被别的线程执行,即使这些方法或同步块与被调用的代码之间没有任何逻辑关系,这显然降低了程序的运行效率。

而使用Lock,当一个同步块被执行时,这个线程只会锁定与当前运行代码相关的其他代码最小集合,而并不影响其他线程对其余同步代码的调用执行

6.以下 a方法和 b方法都实现同步,有何不同?

   public class A {

      public synchronized List a(Object obj) {

         int id = dao.getPrimaryKey(obj);

         List result = dao.listAccounts(id);

         return result;

      }     

      public List b(Object obj) {

         int id = dao.getPrimaryKey(obj);

         List result = null;

         synchronized (obj) {

            result = dao.listAccounts(id);

         }

         return result;

      }

   }

  

2.stop() 和 suspend() 方法为何不推荐使用?

反对使用stop(),是因为它不安全。它会解除由线程获取的所有锁定当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,假如一个线程正在执行:synchronized void { x = 3; y = 4;} 由于方法是同步的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x = 3;时,被调用了 stop()方法,即使在同步块中,它也干脆地stop了,这样就产生了不完整的残废数据。而多线程编程中最最基础的条件要保证数据的完整性,所以请忘记线程的stop方法,以后我们再也不要说“停止线程”了。而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。

suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用 wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。

7.sleep() 和 wait() 有什么区别?

    sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。在sleep 时间间隔期满后,线程不一定立即恢复执行。这是因为在那个时刻,其它线程可能正在运行而且没有被调度为放弃执行,除非(a)“醒来”的线程具有更高的优先级,(b)正在运行的线程因为其它原因而阻塞。

waitObject类的方法,对此对象调用wait方法导致本线程放弃对象锁释放当前线程锁定的任何对象。进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

sleep()方法是本地方法,属于Thread类,它有两种定义:

publicstatic native void sleep(long millis) throws InterruptedException;  

publicstatic void sleep(long millis, int nanos) throws InterruptedException { 

   //other code 

其中的参数millis代表毫秒数(千分之一秒),nanos代表纳秒数(十亿分之一秒)。这两个方法都可以让调用它的线程沉睡(停止运行)指定的时间,到了这个时间,线程就会自动醒来,变为可运行状态(RUNNABLE),但这并不表示它马上就会被运行,因为线程调度机制恢复线程的运行也需要时间。调用sleep()方法并不会让线程释放它所持有的同步锁;而且在这期间它也不会阻碍其它线程的运行。上面的2个方法都声明抛出一个 InterruptedException类型的异常,这是因为线程在sleep()期间,有可能被持有它的引用的其它线程调用它的 interrupt()方法而中断。中断一个线程会导致一个InterruptedException异常的产生,如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。

为了更好地理解interrupt()效果,我们来看一下下面这个例子:

public class InterruptTest { 

    public staticvoid main(String[] args) { 

       Thread t = new Thread() { 

           public void run() { 

               try { 

                   System.out.println("我被执行了-在sleep()方法前"); 

                    // 停止运行10分钟 

                    Thread.sleep(1000 * 60 * 60* 10); 

                   System.out.println("我被执行了-在sleep()方法后"); 

                } catch (InterruptedExceptione) { 

                   System.out.println("我被执行了-在catch语句块中"); 

                } 

               System.out.println("我被执行了-在try{}语句块后"); 

           } 

        }; 

        // 启动线程 

       t.start(); 

        // 在sleep()结束前中断它 

        t.interrupt(); 

    } 

运行结果:

我被执行了-在sleep()方法前

我被执行了-在catch语句块中

我被执行了-在try{}语句块后

 

wait()方法也是本地方法,属于Object类,有三个定义:

public final void wait()throws InterruptedException { 

   //do something 

8.sleep()和yield()的区别

1) sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。

2) sleep()可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会;yield()只能使同优先级的线程有执行的机会

public class ThreadTest implements Runnable{

public void run(){

for(int k=0;k<10;k++){

if(k == 5 && Thread.currentThread().getName().equals("t1")){

Thread.yield();

}

System.out.println(Thread.currentThread().getName()+" : " + k);

}

}

public static void main(String[] args) {

Runnable r = new ThreadTest();

Thread t1 = new Thread(r,"t1");

Thread t2 = new Thread(r,"t2");

t1.setPriority(Thread.MAX_PRIORITY);

t2.setPriority(Thread.MIN_PRIORITY);

t1.start();

t2.start();

}

}

输出结果:

t1 : 0

t1 : 1

t1 : 2

t1 : 3

t1 : 4

t1 : 5

t1 : 6

t1 : 7

t1 : 8

t1 : 9

t2 : 0

t2 : 1

t2 : 2

t2 : 3

t2 : 4

t2 : 5

t2 : 6

t2 : 7

t2 : 8

t2 : 9

多次运行这个程序,输出也是一样。这说明:yield()方法不会使不同优先级的线程有执行的机会。

9.区分interupt()和 notify().

isInterrupted()和interrputed()方法的区别

isInterrupted方法是实例方法,interrupted方法是静态方法

Thread.currentThread().isInterrupted()

Thread.interrupted()

首先说明:wait(),notify(),notifyAll()这些方法由java.lang.Object 类提供,而上面讲到的方法都是由java.lang.Thread 类提供(Thread类实现了Runnable接口)。wait(),notify(),notifyAll()这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized 语句块内使用这三个方法。先看下面了例子:

示例12:

public class ThreadTest implements Runnable{

public static int shareVar = 0;

public synchronizedvoid run(){

if(shareVar == 0){

for(int i=0;i<10;i++){

shareVar++ ;

if(shareVar == 5){

try{

this.wait();

}catch(Exception e){}

}} }

if(shareVar != 0){

System.out.print(Thread.currentThread().getName());

System.out.println(" shareVar = "+ shareVar);

this.notify();

}

}

public static void main(String[] args){

Runnable r = new ThreadTest();

Thread t1 = new Thread(r,"t1");

Thread t2 = new Thread(r,"t2");

t1.start();

t2.start();

}}

运行结果:

t2 shareVar = 5

t1 shareVar = 10

t1线程最先执行。由于初始状态下shareVar为0,t1 将使shareVar 连续加1,当shareVar的值为5 时,t1 调用wait()方法,t1 将处于休息状态,同时释放锁标志。这时t2 得到了锁标志开始执行,shareVar 的值已经变为5,所以t2 直接输出shareVar 的值,然后再调用notify() 方法唤醒t1。t1 接着上次休息前的进度继续执行,把shareVar 的值一直加到10,由于此刻shareVar 的值不为0,所以t1 将输出此刻shareVar的值,然后再调用notify()方法,由于此刻已经没有等待锁标志的线程,所以此调用语句不起任何作用。

6.Wait()与notify()

  wait()->本线程进入等待队列,只能通过别的线程的notify()或notifyall()唤醒  

  notify()->随机地从等待队列删除一个线程,也就是说,该线程进入执行状态  

  notifyall()->删除所有在等待队列中的线程,按线程的优先级执行  

  不推荐使用notify():因为这是随机地从等待队列中取一个线程执行,我们不能通过设定优先级进行控制,如果,随机抽取的线程不能往前执行,这就有可能产生死锁,所以,建议使用notifyall()  

如:

class MyThread extends Thread{

   Test t = new Test();

   public void run(){

   t.test();

   System.out.println("Thread say:Hello,World!");

    }

   public class Test {

   int x = 0;

   public void test(){

   if(x==0)

   try{ wait();}catch(Exception e){}

    }

    }

   public static void main(String[] args) throws Exception{

   new MyThread().start();

    }

}

结果这个线程就不会进入twait方法而直接打印出Thread say:Hello,World!.而如果改成:

public class Test {

     int x = 0;

     public synchornized void test(){

     if(x==0)

     try{

      wait();

     }catch(Exception e){}

     }

     public static void main(String[] args) throws Exception{

     new MyThread().start();

     }

}

我们就可以看到线程一直等待,注意这个线程进入等待后没有其它线程唤醒,除非强行退出JVM环境,否则它一直等待.所以请记住:[线程要想调用一个对象的wait()方法就要先获得该对象的监视锁,而一旦调用wait()后又立即释放该锁]

public class ThreadA{   

   public  static void  main(String[] args){  

     ThreadB   b=new   ThreadB();  

     b.start(); //这里只是将线程b处于可运行状态,并不会立刻执行,实际上是先执行主线(进)程,再去执行线程b,如果主线程运行时间过长的话,则会在期间执行b 

     System.out.println("b  is   start....");  

     synchronized(b){ //定义一个同步块,使用b作为资源锁。对于wait方法必须有的     

       try{  

         System.out.println("Waiting  for   b   to  complete...");  

         b.wait();//临时释放锁,并阻塞当前线程。让其他使用同一把锁的线程ThreadB有机会执行。注意:直接写wait()是不对的。 

         System.out.println("waiting....");  

       }catch(InterruptedException e){}  

    }  

     System.out.println("Total  is   :"+b.total);

    }   

 }  

 class  ThreadB extendsThread{  

 int   total;  

 public   void   run() {  

     System.out.println("ThreadB  is   running   ....");  

      synchronized(this){ //必须有

     System.out.println("ThreadB  is   excuting   for  statement..");  

     for(int i=0;i<5;i++){  

        total+=i;  

        System.out.println("total  is   "+total);  

     }  

      notify();  //在已经获得了一个对象的锁的前提下,调用notify()会通知线程调度程序,唤醒其它等待这个锁的线程队列中的线程,notifyAll()唤醒所有这类线程。

    }   //endfor synchronized

 } 

  }

运行结果:

b  is   start....

Waiting  for   b   to  complete...

ThreadB   is  running   ....

ThreadB  is   excuting   for  statement..

total  is   0

total  is   1

total  is   3

total  is   6

total  is   10

waiting....

Total  is   :10

6.死锁

程序中必须同时满足以下四个条件才会引发死锁:

互斥(Mutual exclusion):线程所使用的资源中至少有一个是不能共享的,它在同一时刻只能由一个线程使用。

持有与等待(Hold and wait:至少有一个线程已经持有了资源,并且正在等待获取其他的线程所持有的资源。

非抢占式(No pre-emption:如果一个线程已经持有了某个资源,那么在这个线程释放这个资源之前,别的线程不能把它抢夺过去使用。

循环等待(Circular wait):假设有N个线程在运行,第一个线程持有了一个资源,并且正在等待获取第二个线程持有的资源,而第二个线程正在等待获取第三个线程持有的资源,依此类推……第N个线程正在等待获取第一个线程持有的资源,由此形成一个循环等待。

举例:

1.未发生死锁

public class Test {   

   static Objecto1 = new Object();//静态对象

   static Objecto2 = new Object();   

    public staticvoid main(String[] args) {        

         synchsynch1 = new synch();

         synchsynch2 = new synch();

         synch1.a=0;

        synch2.a=2;

         Threadt1 = new Thread(synch1);

         Threadt2 = new Thread(synch2);

        t1.start();

        t2.start();

    }  

}//Test类结束

class synch implements Runnable{   

    int a;

    public voidrun() {

        System.out.println(a);

        if(a ==0){

          try {

           synchronized (Test.o1) {//对类Testt中的静态对象同步               

               Thread.sleep(500);

            } //synchronized是对o1的对象锁,500毫秒以后会释放掉对o1的锁,所以不会发生死锁        

           synchronized (Test.o2) {

               System.out.println("aaaa");

            }

        } catch(InterruptedException ex) {}

       }else

           try {

               synchronized (Test.o2) {

                  Thread.sleep(500);

           }           

           synchronized (Test.o1) {

               System.out.println("aaba");

            }

        } catch(InterruptedException ex) {}

       

    }

}

运行后显示

0

2

aaba

aaaa

没有发生死锁

2.发生了死锁

修改如下:

public voidrun() {

    System.out.println(a);

    if(a == 0){

        try {

            synchronized (Test.o1) { //已获取o1对象锁

                Thread.sleep(500);

                System.out.println("Try toget o2");

                synchronized (Test.o2) {  //未释放o1(同步块中的语句执行完以后才释放该对象的锁),尝试获取o2,被挂起

                    System.out.println("aaaa");

                }

            }

        } catch (InterruptedException ex) {}

    } else {

        try {

            synchronized (Test.o2) { //已获取o2的对象锁

                Thread.sleep(500);

                System.out.println("Try toget o1");

                synchronized (Test.o1) {  //未释放o2,尝试获取o1,也被挂起,死锁

                   System.out.println("aaba");

                }

            }

        } catch (InterruptedException ex) {}

    }

}

结果:

0

2

Try to get o2

Try to get o1

另一个例子:

class Friendly {

    privateFriendly partner;

    privateString name;

    publicFriendly(String name) {

        this.name= name;

    }

    publicsynchronized void hug() {

       System.out.println(Thread.currentThread().getName()+" in " +name + ".hug() trying to invoke " + partner.name+".hugBack()");

       partner.hugBack();

    }

    privatesynchronized void hugBack() {

       System.out.println(Thread.currentThread().getName()+" in " +name + ".hugBack()");

    }

    public voidbecomeFriend(Friendly partner) {

       this.partner = partner;

    }

public static void main(String[] args) {

    finalFriendly jareth = new Friendly("jareth");

    finalFriendly cory = new Friendly("cory");

   jareth.becomeFriend(cory);

   cory.becomeFriend(jareth);

    newThread(new Runnable() {

        publicvoid run() { jareth.hug(); }

    },"Thread1").start();

 

    newThread(new Runnable() {

        publicvoid run() { cory.hug(); }

    },"Thread2").start();

}

}

结果:

Thread1 injareth.hug() trying to invoke cory.hugBack()

Thread1 incory.hugBack()

Thread2 incory.hug() trying to invoke jareth.hugBack()

Thread2 injareth.hugBack()

7.线程池

线程池就像数据库连接池一样,是一个对象池。所有的对象池都有一个共同的目的,那就是为了提高对象的使用率,从而达到提高程序效率的目的。比如对于Servlet,它被设计为多线程的(如果它是单线程的,你就可以想象,当1000个人同时请求一个网页时,在第一个人获得请求结果之前,其它999个人都在郁闷地等待),如果为每个用户的每一次请求都创建一个新的线程对象来运行的话,系统就会在创建线程和销毁线程上耗费很大的开销,大大降低系统的效率。因此,Servlet多线程机制背后有一个线程池在支持,线程池在初始化初期就创建了一定数量的线程对象,通过提高对这些对象的利用率,避免高频率地创建对象,从而达到提高程序的效率的目的。

 

8.同步和异步有何异同,阻塞与非阻塞,在什么情况下分别使用他们?举例说明。

如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。

当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率

同步就是指:多个线程可以同时访问同一个资源。比如对一个变量而言,线程们可以同时对他进行读写。 异步就是指:多个线程不能同时对资源进行某项操作,比如对上面变量的同时写操作。如果进行同步写操作,容易产生错误,我们则可以通过互斥量来解决。

以通讯为例  

  同步:发送一个请求,等待返回,然后再发送下一个请求  

  异步:发送一个请求,不等待返回,随时可以再发送下一个请求  

  并发:同时发送多个请求  

10.设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。写出程序。

Thread类

public Thread() 创建线程对象

public Thread(Runnable target)//target 称为被创建线程的目标对象,负责实现Runnable接口

Thread类有三个有关线程优先级的静态常量:MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY

新建线程将继承创建它的副相承的优先级,用户可以调用Thread类的setPriority(int a)来修改

a的取值:Thread.MIN_PRIORITY,Thread.MAX_PRIORITY,Thread.NORM_PRIORITY

主要方法

启动线程 voidstart()

定义线程操作 voidrun()

使线程休眠 staticvoid sleep()

sleep(int millsecond) 以毫秒为单位的休眠时间

sleep(int millsecond,int nanosecond) 以纳秒为单位的休眠时间

currentThread() 判断谁在占用CPU的线程

final String getName()    获取线程名

final int getPriority()       获取线程优先级

final void setPriority(int level)   设置线程优先级

final boolean isAlive()      确定线程是否运行

final   voidjoin()              等待线程终止

以下程序使用内部类实现线程,对j增减的时候没有考虑顺序问题。

public class ThreadTest1{

  private int j;

  public staticvoid main(String args[]){

ThreadTest1 tt=new ThreadTest1();

Inc inc=tt.newInc();

Dec dec=tt.newDec();

for(int i=0;i<2;i++){

Thread t=new Thread(inc);

t.start();

t=new Thread(dec);

t.start();

}

}

private synchronized voidinc(){

j++;

System.out.println(Thread.currentThread().getName()+"-inc:"+j);

  }

private synchronized void dec(){

j--;

System.out.println(Thread.currentThread().getName()+"-dec:"+j);

  }

class Inc implements Runnable{

public void run(){

for(int i=0;i<100;i++){

inc();

}

}

  }

class Dec implements Runnable{

public void run(){

for(int i=0;i<100;i++){

dec();

}

 }

  }

}

11.threadLocal的用法及使用场景

ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Threadthread local variable(线程局部变量)。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。

ThreadLocal的设计
 首先看看ThreadLocal的接口:
 Object get() ; // 返回当前线程的线程局部变量副本 

protected Object initialValue(); // 返回该线程局部变量的当前线程的初始值
void set(Object value); // 设置当前线程的线程局部变量副本的值
 ThreadLocal有3个方法,其中值得注意的是initialValue(),该方法是一个protected的方法,显然是为了子类重写而特意实现的。该方法返回当前线程在该线程局部变量的初始值,这个方法是一个延迟调用方法,在一个线程第1次调用get()或者set(Object)时才执行,并且仅执行1次。ThreadLocal中的确实实现直接返回一个null:

protected Object initialValue() { return null; }
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。比如下面的示例实现:
public class ThreadLocal{
 private Map values = Collections.synchronizedMap(new HashMap());
 public Object get() {
 Thread curThread = Thread.currentThread(); 
 Object o = values.get(curThread); 
 if (o == null && !values.containsKey(curThread)) {
 o = initialValue();
 values.put(curThread, o); 
 }
 return o; 
 }

public void set(Object newValue) {
 values.put(Thread.currentThread(), newValue);
 }
 public Object initialValue() {
 return null; 
 }
}

 ThreadLocal的使用
 如果希望线程局部变量初始化其它值,那么需要自己实现ThreadLocal的子类并重写该方法,通常使用一个内部匿名类对ThreadLocal进行子类化,比如下面的例子,SerialNum类为每一个类分配一个序号:
public class SerialNum {
 // The next serial number to be assigned
 private static int nextSerialNum = 0; 
 private static ThreadLocal serialNum = new ThreadLocal() {
 protected synchronized Object initialValue()  {
 return new Integer(nextSerialNum++);
 }
 };

 public static int get()  {
 return ((Integer) (serialNum.get())).intValue(); 
 }
}
SerialNum类的使用将非常地简单,因为get()方法是static的,所以在需要获取当前线程的序号时,简单地调用:int serial = SerialNum.get();即可。
   在线程是活动的并且ThreadLocal对象是可访问的时,该线程就持有一个到该线程局部变量副本的隐含引用,当该线程运行结束后,该线程拥有的所以线程局部变量的副本都将失效,并等待垃圾收集器收集。

 ThreadLocal与其它同步机制的比较

   ThreadLocal和其它所有的同步机制都是为了解决多线程中的对同一变量的访问冲突,在普通的同步机制中,是通过对象加锁来实现多个线程对同一变量的安全访问的。这时该变量是多个线程共享的,使用这种同步机制需要很细致地分析在什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放该对象的锁等等很多。所有这些都是因为多个线程共享了资源造成的。ThreadLocal就从另一个角度来解决多线程的并发访问,ThreadLocal会为每一个线程维护一个和该线程绑定的变量的副本,从而隔离了多个线程的数据,每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的整个变量封装进ThreadLocal,或者把该对象的特定于线程的状态封装进ThreadLocal。

 由于ThreadLocal中可以持有任何类型的对象,所以使用ThreadLocal get当前线程的值是需要进行强制类型转换。但随着新的Java版本(1.5)将模版的引入,新的支持模版参数的ThreadLocal类将从中受益。也可以减少强制类型转换,并将一些错误检查提前到了编译期,将一定程度地简化ThreadLocal的使用。

 当然ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量),这样当然不需要对多个线程进行同步了。所以,如果你需要进行多个线程之间进行通信,则使用同步机制;如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程序,使程序更加易读、简洁。

12.怎么样做到线程安全? 

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

比如一个ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在Items[Size] 的位置存放此元素;2. 增大 Size 的值。

  在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;

  而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。

  那好,现在我们来看看ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。

如何做到线程安全:

四种方式   sychronized关键字

   1. sychronizedmethod(){}

   2. sychronized(objectReference) {/*block*/}

   3. static synchronized method(){}

   4.sychronized(classname.class)

    其中1和2是代表锁当前对象,即一个对象就一个锁,3和4代表锁这个类,即这个类的锁。要注意的是sychronized method()不是锁这个函数,而是锁对象,即:如果这个类中有两个方法都是sychronized,那么只要有两个线程共享一个该类的reference,每个调用这两个方法之一,不管是否同一个方法,都会用这个对象锁进行同步。

   注意:long 和double是简单类型中两个特殊的咚咚:java读他们要读两次,所以需要同步。  


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值