程序员面试金典(二)

线程与锁

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提供了同步机制,以控制对共享资源的访问。
关键字 synchronizedlock 构成了代码同步执行的基础。

  • 同步方法
    最常见的就是使用 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就很困难,因为许多资源同一时刻只能被一个进程使用(如打印机)。大部分预防死锁的算法都把重心放在循环等待上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值