程序员面试金典(二)
2016-06-07 20:30
495 查看
线程与锁
1.java线程
在java中,每个线程的创建和控制都是由java.lang.Thread类的独特对象对象实现。一个独立的应用运行时,会自动创建一个用户线程,执行
main()方法,这个线程叫主线程。在java中,实现线程有两种方式:
通过实现
java.lang.Runnable接口;
通过扩展
java.lang.Thread类。
(1)实现Runnable接口
Runnable接口结构非常简单:
public interface Runnable{ void run(); }
要用这个接口创建和使用线程,看一个具体的例子;
package com.czl.thread; public class RunnableExample implements Runnable{ public int count = 0; @Override public void run() { System.out.println("线程开始"); try { while (count < 5) { Thread.sleep(2000); count ++ ; } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); System.out.println("线程中断"); } System.out.println("线程终结"); } }
package com.czl.thread; public class TestRunnable { public static void main(String[] args) { RunnableExample instance = new RunnableExample(); Thread thread = new Thread(instance); thread.start();//线程开始 while (instance.count != 5) { try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
(2)扩展Thread类
使用这种方式,基本就意味着要重写
run()方法并且在子类的构造函数里,还需要显式调用这个县城的构造函数。
public class ThreadExaple extends Thread{ int count = 0; @Override public void run() { super.run(); System.out.println("Thread starting"); try { while (count < 5) { Thread.sleep(500); System.out.println("in thread,count is :" + count); count ++; } } catch (InterruptedException e) { // TODO Auto-generated catch block System.out.println("thread interrupted."); } System.out.println("thread terminating."); } } public static void main(String[] args) { ThreadExaple threadExaple = new ThreadExaple(); threadExaple.start(); while (threadExaple.count != 5) { try { Thread.sleep(300); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
Tips:
在创建线程时,相比扩展Thread类,实现Runnable接口可能更优,有两个理由:
Java不支持多重继承。因此,扩展Thread类也就代表这个子类不能扩展其他类。而实现Runnble接口的类还能扩展另一个类。
类可能只要求执行即可,因此,继承整个Thread的开销过大。
2.同步和锁
给定一个进程内的所有线程,都共享同一存储空间,这样有利有弊。这些线程可以共享数据,非常有用。不过,在两个线程同时修改同一资源时,会出现问题。Java提供了同步机制,以控制对共享资源的访问。关键字
synchronized和
lock构成了代码同步执行的基础。
同步方法
最常见的就是使用
synchronized关键字实现同步功能。该关键字可以用在方法和代码块上,限制多个线程,使之不能同时执行同一个对象的代码。
public class MyTest extends Thread{ public void run(){ } } public class MyObject{ public static synchronized void fun(){ } public static synchronized void foo(){ } }
同步块
代码块也可以实现同步化,其实现原理跟同步方法相似。
public class MyTest extends Thread{ public void run(){ } } public class MyObject{ public void fun(String name){ synchronized(this){ } } }
锁
若要实现更细致的控制,使用锁(lock),用于对共享资源的同步访问,方法是用锁将共享资源关联在一起,线程必须先取得与资源关联的锁,才能访问共享资源。不管在任何时间点,最多只能有一个线程拿到锁,因此只有一个线程可以访问共享资源。
锁的常见用法,从多个地方访问同一资源时,同一时刻只有一个线程可以访问。
public class TestLock { private Lock lock; private int balance = 100; public TestLock(){ lock = new ReentrantLock(); } public int withdraw(int value){ lock.lock(); int temp = balance; try { Thread.sleep(200); temp = temp - value; Thread.sleep(200); balance = temp; } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } lock.unlock(); return temp; } public int deposit(int value){ lock.lock(); int temp = balance; try { Thread.sleep(200); temp = temp + value; Thread.sleep(500); balance = temp; } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } lock.unlock(); return temp; } }
3.死锁及死锁的预防
死锁(deadlock)是这样一种情况:第一个线程在等待第二个线程持有的某个对象锁,而第二个线程又在等待第一个线程持有的对象锁(或是由两个以上线程形成的类似情形)。由于每个线程都在等待其他线程释放锁,以致每个线程都会一直这么等下去。于是,这些线程就陷入了死锁状态。死锁的出现必须同时满足以下四个条件:
(1)互斥:某一时刻只有一个线程能访问某一资源。(或者,更准确的说,对某一资源的访问有限制。若有资源限制,也有可能出现死锁)
(2)持有并等待:已持有某一资源的进程不必释放当前拥有的资源,就能要求更多的资源。
(3)没有抢占:一个进程不能强制另一个进程释放资源。
(4)循环等待:两个或两个以上的进程形成循环链,每个进程都在等待循环链中另一进程持有的资源。
若要预防死锁,只需避免上述任一条件,比较困难。比如,想要避免条件1就很困难,因为许多资源同一时刻只能被一个进程使用(如打印机)。大部分预防死锁的算法都把重心放在循环等待上。
相关文章推荐
- 汇编程序员之代码风格指南
- 嵌入式软件工程师 面试经验
- [译]好程序员的五声“呐喊”
- 实习面试小记-腾讯、阿里、大摩、网易
- 链表面试题详解
- 如何在面试中发现优秀程序员(转)
- 剑指Offer----面试题29:数组中出现次数超过一半的数字
- 《黑客与画家》读书笔记
- 应届生如何为工作做准备 程序员 技术大牛
- 转载:一个程序员的顿悟
- 面试题的收集
- 程序员的年龄天花板
- 程序员之路──关于代码风格
- 面试过程中涉及的问题
- 标准的JDBC数据库连接代码(面试)
- 女程序员发的一条微博
- 面试中遇到的挫折与打击
- JAVA面试中的多线程问题
- 笔试面试常见的链表操作
- [置顶] Android开发java程序员常用代码,将字符串以逗号分别取出字符串String