热爱技术,追求卓越
不断求索,精益求精

Java面试经典基础问答六

1. 启动一个线程是用run()方法还是start()方法?

启动一个线程是调用start()方法,使线程就绪状态,以后可以被调度为运行状态,一个线程必须关联一些具体的执行代码,run()方法体内是该线程所关联的执行代码。

2. 当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?

这个要分情况而论:
(1)其他方法前是否加了synchronized关键字,如果没加,则能,否则不能。
(2)如果这个方法内部调用了wait,则可以进入其他synchronized方法。
(3)如果其他个方法都加了synchronized关键字,并且内部没有调用wait,则不能。
(4)如果其他方法是static,它用的同步锁是当前类的字节码,与非静态的方法不能同步,因为非静态的方法用的是this。

我们用一个实例来讲解这个问题:

package cn.lovecto.model;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        final Test test = new Test();
        //第一个线程执行实例方法test2(),sleep10秒,看其他线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    test.test2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        //sleep 5秒启动第二个线程执行test3()
        Thread.sleep(5000);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    test.test3();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        // 这种写法能调用,但会报警告,实际调用的是Test.test1():
        // The static method test1() from the type Test should be accessed in a
        // static way
        test.test1();
        // sleep 20秒
        Thread.sleep(20000);
    }

    public synchronized static void test1() {
        System.out.println("static method test1");
    }

    public synchronized void test2() throws InterruptedException {
        // sleep 10秒
        Thread.sleep(10000);
        System.out.println("instance method test2");
    }

    public synchronized void test3() throws InterruptedException {
        System.out.println("instance method test3");
    }
}

打印结果:

static method test1
instance method test2
instance method test3

第一个线程获取到对象test锁,持有锁的时间为20秒,static的方法test1在test2持有锁期间打印出“static method test1”,说明static的方法和实例方法同步锁并不是同一个(static方法的同步锁是类,而实例方法的同步锁是对象)。“instance method test3”在“instance method test2”之后打印,说明第二个线程需要等待第一个线程释放实例锁才获取到执行权限。

3. 线程的基本概念、线程的基本状态以及状态之间的关系是什么?

一个程序中可以有多条执行线索同时执行,一个线程就是程序中的一条执行线索,每个线程上都关联有要执行的代码,即可以有多段程序代码同时运行,每个程序至少都有一个线程,即main方法执行的那个线程。如果只是一个cpu,它怎么能够同时执行多段程序呢?cpu是使用时间片轮转来执行各个线索的,cpu一会执行a线索,一会执行b线索,切换时间很快,给人的感觉是a,b在同时执行,好比大家在同一个办公室上网,只有一条链接到外部网线,其实,这条网线一会为a传数据,一会为b传数据,由于切换时间很短暂,所以,大家感觉都在同时上网。

线程状态有就绪、运行、synchronize阻塞、wait和sleep挂起、结束等。wait必须在synchronized内部调用。调用线程的start方法后线程进入就绪状态,线程调度系统将就绪状态的线程转为运行状态,遇到synchronized语句时,由运行状态转为阻塞,当synchronized获得锁后,由阻塞转为运行,在这种情况可以调用wait方法转为挂起状态,当线程关联的代码执行完后,线程变为结束状态。

4. synchronized和java.util.concurrent.locks.Lock的异同?

主要相同点:Lock能完成synchronized所实现的所有功能。
主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally语句块中释放。Lock还有更强大的功能,例如,它的tryLock方法可以非阻塞方式去拿锁。

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

下面是一个简单的实现,更完美的实现可以自行脑补。

package cn.lovecto.model;

public class Test {
    private int j = 0;

    public static void main(String[] args) throws InterruptedException {
        final Test test = new Test();
        final int times = 10;
        // 设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1
        for (int i = 0; i < 4; i++) {
            final int num = i;
            if (i % 2 == 0) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("第"+ num + "个加线程");
                        for (int c = 0; c < times; c++) {
                            test.incr(1);
                        }
                    }
                }).start();
            } else {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("第"+ num + "个减线程");
                        for (int c = 0; c < times; c++) {
                            test.incr(-1);
                        }
                    }
                }).start();
            }
        }
        Thread.sleep(10000);
        System.out.println(test.getJ());
    }

    public synchronized void incr(int v) {
        j += v;
    }

    public int getJ() {
        return j;
    }

    public void setJ(int j) {
        this.j = j;
    }
}

6. 子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次,请写出程序。

如下:

package cn.lovecto.model;

public class Test {
    public static void main(String[] args) {
        final SubThreadTest subTest = new SubThreadTest();
        new Thread(new Runnable() {
            public void run() {
                for (int i = 1; i <= 50; i++) {
                    subTest.sub(i);
                }
            }
        }).start();
        for (int i = 1; i <= 50; i++) {
            subTest.main(i);
        }
    }
}

class SubThreadTest {
    private boolean bShouldSub = true;

    public synchronized void sub(int i) {
        while (!bShouldSub) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int j = 1; j <= 10; j++) {
            System.out.println("sub sequence is :" + j + "   main loop is :"
                    + i);
        }
        bShouldSub = false;
        this.notify();
    }

    public synchronized void main(int i) {
        while (bShouldSub) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int j = 1; j <= 100; j++) {
            System.out.println("main sequence is :" + j + "   main loop is :"
                    + i);
        }
        bShouldSub = true;
        this.notify();
    }
}

7. 介绍Collection框架的结构。

Collection是集合类的上级接口,包括List、Set、Queue等。List接口中常用类Vector、ArrayList、LinkedList等。Set接口中常用类HashSet、TreeSet等。

8. ArrayList和Vector有什么区别?

这两个类都实现了List接口(List接口继承了Collection接口),他们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引号取出某个元素,并且其中的数据是允许重复的。

ArrayList与Vector的区别,这主要包括两个方面:

(1)同步性:
Vector是线程安全的,也就是说是它的方法之间是线程同步的,而ArrayList是线程序不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最好是使用Vector,因为不需要我们自己再去考虑和编写线程安全的代码。
(2)数据增长:
ArrayList与Vector都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需要增加ArrayList与Vector的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。Vector默认增长为原来两倍,而ArrayList的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的1.5倍)。ArrayList与Vector都可以设置初始的空间大小,Vector还可以设置增长的空间大小,而ArrayList没有提供设置增长空间的方法。即Vector增长原来的一倍,ArrayList增加原来的0.5倍。

Vector增长策略:

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

ArrayList增长策略:

/**
 * Increases the capacity to ensure that it can hold at least the
 * number of elements specified by the minimum capacity argument.
 *
 * @param minCapacity the desired minimum capacity
 */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

9. Collection框架中实现比较要实现什么接口?

Collection框架中实现比较要实现Comparable 接口和 Comparator 接口。

10. HashMap和Hashtable有什么区别?

Hashtable是同步的,而HashMap不是,所以HashMap更适用于单线程环境,Hashtable则适用于多线程环境。HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,在只有一个线程访问的情况下,效率要高于Hashtable。

package cn.lovecto.model;

import java.util.HashMap;
import java.util.Hashtable;

public class Test {
    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<String, String>();
        map.put(null, "123");
        System.out.println(map.get(null));
        //会报空指针异常
        Hashtable<String, String> table = new Hashtable<String, String>();
        table.put(null, "123");
        System.out.println(table.get(null));
    }
}
赞(2)
未经允许不得转载:LoveCTO » Java面试经典基础问答六

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

热爱技术 追求卓越 精益求精