多线程

1.什么是进程?

进程:程序的一次执行过程,实质上就是一个正在执行的程序。
特点:资源占用大,进程间通信困难

进程是一个“执行中的程序”,程序是一个没有生命的实体,只有处理器运行程序时,

它才能成为一个活动的实体,才能称其为进程。


2.什么是线程?

*线程:进程内部的控制流。它也是一段可运行的指令。
*
*特点:资源占用小,线程间通信容易。

*线程的运行方式和进程一样,在同一个进程内部,线程也是交替占用CPU。每个线程的运行时间,称为线程时间片。
*线程有开始、中间和结束部分,即有一定的生命周期。
*同一个进程中的多个线程是共享一块内存空间和一组系统资源。

*线程不能作为具体的可执行命令体存在。最终用户不能直接执行线程,线程只能运行在进程中

3.如何创建线程?
两种方法来创建线程:

继承java.lang.Thread类,并覆盖run( )方法。
class mythread extends Thread {
public void run( ) {
/* 覆盖该方法*/
}
}

实现java.lang.Runnable接口,并实现run( )方法。
class mythread implements Runnable{
public void run( ) {
/* 实现该方法*/
}
}

不用说肯定是后者好,因为实现接口的方式比继承类的方式更灵活,也能减少程序之间的耦合度,面向接口编程也是设计模式6大原则的核心。

4.线程的启动?
新建的线程不会自动开始运行,必须通过start( )方法启动线程。如果不调用这个方法,线程将不会运行。

也就是说可以事先创建线程,并在需要的时候才启动他们。如:
继承Thread的线程:
Typer t = new Typer ( );
t.start( );

实现Runnable接口的线程:
Runnable mt = new Typer ( );
Thread t = new Thread(mt);
t.start( );

5.线程的执行过程?
*调用start()方法时,将创建一个新的线程,并为线程分配系统资源,如内存。接着它将调用 run( ) 方法。
*
*run() 方法中的代码定义执行线程所需的功能。
*run()方法能够调用其他方法,引用其他的类,申明变量。
*run()方法在程序中确定另一个并发线程的执行入口。
*当run()方法中的任务完成返回时,该线程也将结束。


6.
*为什么不直接调用run()方法来启动线程运行,而是调用start()方法?
*
*如果在t.start()之后加入一条输出语句输出字符串“main”,会发生什么情况?

7.线程的生命周期
   一个线程从创建到运行完毕,要经历四种状态,这四
种状态称为线程的生命周期:
创建状态:线程刚刚被创建出来的状态。当线程处于“新线程“状态时,仅仅是一个空线程对象,它还没有分配到系统资源。因此只能启动或终止它。任何其他操作都会引发异常。

可运行状态:当线程对象调用了start()方法后的状态。start()只是告诉系
统“所需资源已经到位,可以投入运行”,此时线程并不一定马上开始运行,
因为一个CPU只能运行一个线程,是否真正开始运行,还要看当前系统的调
度情况,比如CPU的占用情况等。

阻塞状态:所谓阻塞,是指线程从运行状态进入的暂停运行状态。

死亡状态:当run()方法执行完毕,或别的线程调用stop()方法,线程进入死
亡态。线程死亡后,其占据的资源被系统收回,将无法再通过调用start()进
入可运行状态

8.runnable接口创建多线程的优点
对象可以自由地继承自另一个类,解决java只支持单继承的特点。
同一个runnable对象可以传递给多个线程,可以实现资源共享。
减小创建新线程实例所需的可观内存和cpu时间。

9.java中的优先级
Java 中的线程优先级是在 Thread 类中定义的常量
Thread.MIN_PRIORITY  : 值为 1
Thread.NORM_PRIORITY: 值为 5(缺省值)
Thread.MAX_PRIORITY  : 值为 10
有关优先级的方法有两个:
finalvoid setPriority(int level) : 修改线程的当前优先级
finalint getPriority() : 返回线程的优先级

10.线程优先级的应用

线程A:

publicclass A extends Thread{

  public void run(){

  System.out.println(“线程A”);

  }

}

和线程B:

publicclass A extends Thread{

  public void run(){

  System.out.println(“线程B”);

  }

}

实例化并运行:

  A a=new A();

  B b=new B();

  a.start();

  b.start();


11.

方法

用途

final String getName( )

返回线程的名称

final void setName(String name)

将线程的名称设置为由name指定的名称

void stop()

杀死线程

void start( )

调用run( )方法启动线程,开始线程的执行

static void sleep( )

用于将线程休眠一段时间,之后自动醒来

void interrupt()

结束线程当前的状态



12.线程休眠
sleep():让线程停止执行一段时间,该时间由给定的毫秒数决定。在线程休眠过程中,线程通常不能唤醒他们自己。
sleep提供了两种静态的调用方法:
 1. public static voidsleep(long millis)

                   throws InterruptedException

 2. public static void sleep(long millis,

        int nanos) throws InterruptedException


13.并发和互斥锁
*并发:由两个及其以上的线程同时访问一个共享资源的现象称为并发。
*
*
*互斥锁:一种标志,表明在给定的时刻只允许一个线程访问共享资源,具有互相排斥的效果;这种机制称为“互斥”(mutex)



14.同步的两种方法
Java中同步的两种方法:
方法级同步(method-level synchronized)

  synchronized voidmethod(  ) { 

      //同步的方法

  }

对象级同步( block-level synchronized )

  synchronized ( object) {

   //要同步的语句

  }


15.对象级同步
1、实现方法:用synchronized来指定某个对象,此对象的锁被用来对花括号内的代码进行同步控制。如:

  synchronized( target ){

       target.call(msg);

  }

3、实现原理:在进入同步代码前,必须得到object对象的锁,如果其他线程已经得到这个锁,那么就得等到锁被释放后才能进入临界区。
4、锁的作用域:只在代码块运行的时间内。

方法级同步:

publicsynchronized String sell(String name){

  if(count>0){

  count--;

  return name+"购买成功.剩余:"+count;

  }

  System.out.println("已售完.");

  return null;

  }



*代码块同步:

public String sell(String name){

  synchronized(count){

  if(count>0){

  count--;

  returnname+"购买成功.剩余:"+count;

  }

}

  System.out.println("已售完.");

  returnnull;


16.public static void main(String[] args) throws InterruptedException
{
System.out.println("main start");

Thread t1 = new Thread(new Worker("thread-1"));
t1.start();
t1.join();
System.out.println("main end");
}

在上面的例子中,main线程要等到t1线程运行结束后,才会输出“main end”。如果不加t1.join(),main线程和t1线程是并行的。而加上t1.join(),程序就变成是顺序执行了。

我们在用到join()的时候,通常都是main线程等到其他多个线程执行完毕后再继续执行。其他多个线程之间并不需要互相等待。

下面这段代码并没有实现让其他线程并发执行,线程是顺序执行的。

public static void main(String[] args) throws InterruptedException
{
System.out.println("main start");

Thread t1 = new Thread(new Worker("thread-1"));
Thread t2 = new Thread(new Worker("thread-2"));
t1.start();
//等待t1结束,这时候t2线程并未启动
t1.join();

//t1结束后,启动t2线程
t2.start();
//等待t2结束
t2.join();

System.out.println("main end");
}



===========================面试题==========================

比较基础的多线程面试题点击打开链接

 比较好的关于多线程的面试题链接 点击打开链接

 面象对象的及格面试题点击打开链接

  上机题的多线程问题点击打开链接


1、多线程有什么用?

一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个回答更扯淡。所谓”知其然知其所以然”,”会用”只是”知其然”,”为什么用”才是”知其所以然”,只有达到”知其然知其所以然”的程度才可以说是把一个知识点运用自如。OK,下面说说我对这个问题的看法:

1)发挥多核CPU的优势


随着工业的进步,现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的,4核、8核甚至16核的也都不少见,如果是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费了75%。单核CPU上所谓的”多线程”那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程”同时”运行罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的。


(2)防止阻塞


从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。


(3)便于建模


这是另外一个没有这么明显的优点了。假设有一个大的任务A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、任务D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。

3、start()方法和run()方法的区别


只有调用了start()方法,才会表现出多线程的特性,不同线程的run()方法里面的

代码交替执行。如果只是调用run()方法,那么代码还是同步执行的,必须等待一

个线程的run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其

run()方法里面的代码。

4、Runnable接口和Callable接口的区别


有点深的问题了,也看出一个Java程序员学习知识的广度。

相同点:
Callable和Runnable都是接口
Callable和Runnable都可以应用于Executors

不同点:Callable要实现call方法,Runnable要实现run方法
   call方法可以返回值,run方法不能
           call方法可以抛出checked exception,run方法不能
           Runnable接口在jdk1.1就有了,Callable在Jdk1.5才有
7、什么是线程安全

又是一个理论的问题,各式各样的答案有很多,我给出一个个人认为解释地最好的:如果你的代码在多线程下执行和在单线程下执行永远都能获得一样的结果,那么你的代码就是线程安全的。
这个问题有值得一提的地方,就是线程安全也是有几个级别的:
(1)不可变
像String、Integer、Long这些,都是final类型的类,任何一个线程都改变不了它们的值,要改变除非新创建一个,因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用
(2)绝对线程安全
不管运行时环境如何,调用者都不需要额外的同步措施。要做到这一点通常需要付出许多额外的代价,Java中标注自己是线程安全的类,实际上绝大多数都不是线程安全的,不过绝对线程安全的类,Java中也有,比方说CopyOnWriteArrayList、CopyOnWriteArraySet
(3)相对线程安全
相对线程安全也就是我们通常意义上所说的线程安全,像Vector这种,add、remove方法都是原子操作,不会被打断,但也仅限于此,如果有个线程在遍历某个Vector、有个线程同时在add这个Vector,99%的情况下都会出现ConcurrentModificationException,也就是fail-fast机制。
(4)线程非安全
这个就没什么好说的了,ArrayList、LinkedList、HashMap等都是线程非安全的类


0、Java中多线程同步是什么?

在多线程程序下,同步能控制对共享资源的访问。如果没有同步,当一个Java线程在修改一个共享变量时,另外一个线程正在使用或者更新同一个变量,这样容易导致程序出现错误的结果。

1、解释实现多线程的几种方法?

Java线程可以实现Runnable接口或者继承Thread类来实现,当你打算多重继承时,优先选择实现Runnable。还可以使用线程池。

2、Thread.start()与Thread.run()有什么区别?

Thread.start()方法(native)启动线程,使之进入就绪状态,当cpu分配时间该线程时,由JVM调度执行run()方法。

3、为什么需要run()和start()方法,我们可以只用run()方法来完成任务吗?

我们需要run()&start()这两个方法是因为JVM创建一个单独的线程不同于普通方法的调用, 所以这项工作由线程的start方法来完成,start由本地方法实现,需要显示地被调用,使用这俩个方法的另外一个好处是任何一个对象都可以作为线程运 行,只要实现了Runnable接口,这就避免因继承了Thread类而造成的Java的多继承问题。

4、什么是ThreadLocal类,怎么使用它?

ThreadLocal是一个线程级别的局部变量,并非“本地线程”。ThreadLocal为每个使用该变量的线程提供了一个独立的变量副本,每个线程修改副本时不影响其它线程对象的副本(译者注)。

5、Sleep()、suspend()和wait()之间有什么区别?

Thread.sleep()使当前线程在指定的时间处于“非运行”(Not Runnable)状态。线程一直持有对象的监视器。比如一个线程当前在一个同步块或同步方法中,其它线程不能进入该块或方法中。如果另一线程调用了 interrupt()方法,它将唤醒那个“睡眠的”线程。

注意:sleep()是一个静态方法。这意味着只对当前线程有效,一个常见的错误是调用t.sleep(),(这里的t是一个不同于当前线程的线 程)。即便是执行t.sleep(),也是当前线程进入睡眠,而不是t线程。t.suspend()是过时的方法,使用suspend()导致线程进入停 滞状态,该线程会一直持有对象的监视器,suspend()容易引起死锁问题。

object.wait()使当前线程出于“不可运行”状态,和sleep()不同的是wait是object的方法而不是thread。调用 object.wait()时,线程先要获取这个对象的对象锁,当前线程必须在锁对象保持同步,把当前线程添加到等待队列中,随后另一线程可以同步同一个 对象锁来调用object.notify(),这样将唤醒原来等待中的线程,然后释放该锁。基本上wait()/notify()与sleep() /interrupt()类似,只是前者需要获取对象锁。

6、在静态方法上使用同步时会发生什么事?

同步静态方法时会获取该类的“Class”对象,所以当一个线程进入同步的静态方法中时,线程监视器获取类本身的对象锁,其它线程不能进入这个类的任何静态同步方法。它不像实例方法,因为多个线程可以同时访问不同实例同步实例方法。

7、当一个同步方法已经执行,线程能够调用对象上的非同步实例方法吗?

可以,一个非同步方法总是可以被调用而不会有任何问题。实际上,Java没有为非同步方法做任何检查,锁对象仅 仅在同步方法或者同步代码块中检查。如果一个方法没有声明为同步,即使你在使用共享数据Java照样会调用,而不会做检查是否安全,所以在这种情况下要特 别小心。一个方法是否声明为同步取决于临界区访问(critial section access),如果方法不访问临界区(共享资源或者数据结构)就没必要声明为同步的。

8、 在一个对象上两个线程可以调用两个不同的同步实例方法吗?

不能,因为一个对象已经同步了实例方法,线程获取了对象的对象锁。所以只有执行完该方法释放对象锁后才能执行其 它同步方法。看下面代码示例非常清晰:Common 类 有synchronizedMethod1()和synchronizedMethod2()方法,MyThread调用这两个方法。

10、 什么是死锁

死锁就是两个或两个以上的线程被无限的阻塞,线程之间相互等待所需资源。这种情况可能发生在当两个线程尝试获取其它资源的锁,而每个线程又陷入无限等待其它资源锁的释放,除非一个用户进程被终止。

11、现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?

使用join()方法

12、在Java中Lock接口比synchronized块的优势是什么?

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

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

13、用Java写代码来解决生产者——消费者问题。

与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在Java中怎么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞队列实现的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。

14、什么是竞争条件?你怎样发现和解决竞争?

这是一道出现在多线程面试的高级阶段的问题。大多数的面试官会问最近你遇到的竞争条件,以及你是怎么解决的。有些时间他们会写简单的代码,然后让你检测出代码的竞争条件。可以参考我之前发布的关于Java竞争条件的文章。在我看来这是最好的java线程面试问题之一,它可以确切的检测候选者解决竞争条件的经验,or writing code which is free of data race or any other race condition。关于这方面最好的书是《Concurrency practices in Java》。

15、为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?

这是另一个非常经典的java多线程面试问题。这也是我刚开始写线程程序时候的困惑。现在这个问题通常在电话面试或者是在初中级Java面试的第一轮被问到。这个问题的回答应该是这样的,当你调用start()方法时你将创建新的线程,并且执行在run()方法里的代码。但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码。







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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值