Java面试-线程篇

什么是线程?

  • 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
  • 程序员可以通过它进行多处理器编程,可以使用多线程对运算密集型任务提速。比如一个线程完成一个任务要100毫秒,那么用十个线程完成则约只需10毫秒。
  • Java应用程序执行的main()方法默认为主线程
  • Java虚拟机给每条线程分配独立的JVM栈空间,以免相互干扰

线程和进程有什么区别?

  • 线程是进程的子集,一个进程可以有很多线程,每条线程可以并行执行不同的任务。
  • 不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。每个线程都拥有单独的栈内存用来存储本地数据。

如何在Java中实现线程?

  • 一:实现Runable接口,实现run方法
/**
 * Created by Administrator on 2018/6/13 0013.
 * 实现Runnable接口方式
 */
public class ThreadByRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":" + new Date());
    }
}
/**
 * Created by Administrator on 2018/6/13 0013.
 * 测试
 */
public class ThreadTest {
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new Thread(new ThreadByRunnable()).start();
        }
    }
}

-----------------结果输出-----------------

Thread-1:Wed Jun 13 08:58:54 CST 2018
Thread-2:Wed Jun 13 08:58:54 CST 2018
Thread-0:Wed Jun 13 08:58:54 CST 2018
Process finished with exit code 0

  • 二:继承Thread类,重写run方法
/**
 * Created by Administrator on 2018/6/5 0005.
 * 继承Thread方式
 */
public class ThreadByThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "::" + new Date());
    }
}
/**
 * Created by Administrator on 2018/6/13 0013.
 * 测试
 */
public class ThreadTest {
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new ThreadByThread().start();
        }
    }
}

----------结果输出--------------

Thread-2::Wed Jun 13 09:03:44 CST 2018
Thread-0::Wed Jun 13 09:03:44 CST 2018
Thread-1::Wed Jun 13 09:03:44 CST 2018
Process finished with exit code 0

  • 三:使用ThreadPoolExecutor(线程池)
/**
 * Created by Administrator on 2018/6/13 0013.
 * 测试
 */
public class ThreadTest {
    public static void main(String[] args) {
        /**使用线程池性能更优
         * */
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            /** 下面的实现Runnable接口还是继承Thread类是一样的*/
            executorService.execute(new ThreadByRunnable());
            /*executorService.execute(new ThreadByThread());*/
        }
    }
}

-------------------结果输出----------------

pool-1-thread-3:Wed Jun 13 09:05:35 CST 2018
pool-1-thread-5:Wed Jun 13 09:05:35 CST 2018
pool-1-thread-4:Wed Jun 13 09:05:35 CST 2018
pool-1-thread-2:Wed Jun 13 09:05:35 CST 2018
pool-1-thread-1:Wed Jun 13 09:05:35 CST 2018
Process finished with exit code 0

  • 四:使用Callable
  1. Callable接口时JDK1.5开始新增的,Runnable从JDK1.0开始就有,区别在于Callable的call方法有返回值,且可以抛出异常,而Runnable的run方法没有
/**
 * Created by Administrator on 2018/6/13 0013.
 * 使用Callable方式
 */
public class ThreadByCallable implements Callable {
    @Override
    public Object call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"::"+new Date());
        return "success";
    }
}
/**
 * Created by Administrator on 2018/6/13 0013.
 * 测试
 */
public class ThreadTest {
    public static void main(String[] args) {
        /**使用线程池性能更优
         * */
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            executorService.submit(new ThreadByCallable());
        }
    }
}

------------结果输出---------------

pool-1-thread-2::Wed Jun 13 09:18:55 CST 2018
pool-1-thread-1::Wed Jun 13 09:18:55 CST 2018
pool-1-thread-3::Wed Jun 13 09:18:55 CST 2018
pool-1-thread-4::Wed Jun 13 09:18:55 CST 2018
pool-1-thread-5::Wed Jun 13 09:18:55 CST 2018

Process finished with exit code -1

用Runnable还是Thread?

  • Java不支持类的多继承,但允许实现多个接口,所以如果需要继承其他类,则实现Runnable接口会更好。

Thread 类中的start() 和 run() 方法有什么区别?

  • start()方法被用来启动新创建的线程,使线程处于可运行状态,start()内部会调用run()方法
  • 而如果直接调用run()方法,则只会在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。

Runnable和Callable有什么不同?

  • Runnable和Callable都代表要在不同线程中执行的任务。
  • Runnable从JDK1.0开始就有了,Callable是在JDK1.5增加的。
  • 主要区别是Callable的 call() 方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。
  • Callable可以返回装载有计算结果的Future对象。可以参考《线程池理论 2》

Java中如何停止一个线程?

  • Java提供了很丰富的API,但没有为停止线程提供API。
  • JDK 1.0本来有一些像stop(), suspend() 和 resume()的控制方法,但是由于潜在的死锁威胁因此在后续的JDK版本中他们被弃用了
  • 之后Java API的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。
  • 当Runnable的run() 或者Callable的call() 方法执行完的时候线程就会自动结束。
  • 虽然没有提供停止线程的方法,但是Thread提供了中断线程的方法(interrupt)
/**
 * Created by Administrator on 2018/6/5 0005.
 * 继承Thread方式
 */
public class ThreadByThread extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "::" + i + " ->" + new Date());
                /**i=3,且线程名为"pool-1-thread-2"时 强制中断当前线程*/
                if (i == 3 && Thread.currentThread().getName().equals("pool-1-thread-2")) {
                    ThreadByThread.currentThread().interrupt();
                }
                Thread.sleep(1000 + new Random().nextInt(3000));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println(Thread.currentThread().getName() + " 线程强制中断退出" + " ->" + new Date());
        }
    }
}
/**
 * Created by Administrator on 2018/6/13 0013.
 * 测试
 */
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 2; i++) {
            executorService.execute(new ThreadByThread());
            Thread.sleep(500+new Random().nextInt(500));
        }
    }
}

----------------结果输出--------------------

pool-1-thread-1::0 ->Thu Jun 14 15:31:15 CST 2018
pool-1-thread-2::0 ->Thu Jun 14 15:31:15 CST 2018
pool-1-thread-1::1 ->Thu Jun 14 15:31:16 CST 2018
pool-1-thread-2::1 ->Thu Jun 14 15:31:18 CST 2018
pool-1-thread-1::2 ->Thu Jun 14 15:31:19 CST 2018
pool-1-thread-1::3 ->Thu Jun 14 15:31:21 CST 2018
pool-1-thread-2::2 ->Thu Jun 14 15:31:22 CST 2018
pool-1-thread-2::3 ->Thu Jun 14 15:31:23 CST 2018
java.lang.InterruptedException: sleep interrupted
pool-1-thread-2 线程强制中断退出 ->Thu Jun 14 15:31:23 CST 2018
at java.lang.Thread.sleep(Native Method)
at test.thread.ThreadByThread.run(ThreadByThread.java:20)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
pool-1-thread-1::4 ->Thu Jun 14 15:31:23 CST 2018
pool-1-thread-1::5 ->Thu Jun 14 15:31:26 CST 2018
Process finished with exit code -1

 一个线程运行时发生异常会怎样?

  • 简单的说,如果异常没有被捕获该线程将会停止执行
  • Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。

 如何在两个线程间共享数据?

  • 一可以使用静态成员变量实现,可以参考《volatile 关键字》中的例子
  • 二可以通过共享对象来实现线程间数据共享
  • 二使用像阻塞队列这样并发的数据结构,可以参考《BlockingQueue 理论》中的例子

共享对象实现数据共享

/**
 * Created by Administrator on 2018/6/15 0015.
 * 需要在线程间共享的数据可以封装成POJO实体
 */
public class User {
    /**
     * 用户信用卡账户余额
     */
    private Integer accountBalance;

    public Integer getAccountBalance() {
        return accountBalance;
    }

    public void setAccountBalance(Integer accountBalance) {
        this.accountBalance = accountBalance;
    }
}
/**
 * Created by Administrator on 2018/6/5 0005.
 * 继承Thread方式
 */
public class ThreadByThread extends Thread {
    /**
     * 共享对象,初始化传参
     */
    private User user;

    public ThreadByThread(User user) {
        this.user = user;
    }

    @Override
    public void run() {
        /**
         * 这里如果不做线程同步,就会涉及到Java内存模型中脏读的问题,导致某些线程工作内容中的数据与-
         * 主内存中的数据不一致,特别是高并发时尤其明显
         */
        synchronized (ThreadByThread.class) {
            try {
                /**
                 * 每个线程都对用户放入账户余额做 扣钱 与 存钱操作
                 * 且扣钱、存钱次数相同、额度相同,所以无论多少个线程操作,账户余额都应该不变
                 */
                Random random = new Random();
                int ran = 1000;
                for (int i = 0; i < 4; i++) {
                    /**每两次一个轮回做额度相同的存钱与扣钱操作*/
                    if (i % 2 == 0) {
                        ran = random.nextInt(8000);
                        user.setAccountBalance(user.getAccountBalance() - ran);
                        System.out.println(Thread.currentThread().getName() + " 0-> accountBalance = " + user.getAccountBalance() + " " + new Date());
                    } else {
                        user.setAccountBalance(user.getAccountBalance() + ran);
                        System.out.println(Thread.currentThread().getName() + " 1-> accountBalance = " + user.getAccountBalance() + " " + new Date());
                    }
                    TimeUnit.SECONDS.sleep(1 + new Random().nextInt(2));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
/**
 * Created by Administrator on 2018/6/13 0013.
 * 测试
 */
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        /**采用线程池*/
        ExecutorService executorService = Executors.newCachedThreadPool();
        /**创建需要在各个线程之间共享的数据对象*/
        User user = new User();
        user.setAccountBalance(10000);
        for (int i = 0; i < 3; i++) {
            executorService.execute(new ThreadByThread(user));
            TimeUnit.SECONDS.sleep(1 + new Random().nextInt(2));
        }
    }
}

-----------------------结果输出------------------------

pool-1-thread-1 0-> accountBalance = 8864 Fri Jun 15 11:15:14 CST 2018
pool-1-thread-1 1-> accountBalance = 10000 Fri Jun 15 11:15:15 CST 2018
pool-1-thread-1 0-> accountBalance = 7558 Fri Jun 15 11:15:16 CST 2018
pool-1-thread-1 1-> accountBalance = 10000 Fri Jun 15 11:15:17 CST 2018
pool-1-thread-3 0-> accountBalance = 5772 Fri Jun 15 11:15:19 CST 2018
pool-1-thread-3 1-> accountBalance = 10000 Fri Jun 15 11:15:20 CST 2018
pool-1-thread-3 0-> accountBalance = 4896 Fri Jun 15 11:15:22 CST 2018
pool-1-thread-3 1-> accountBalance = 10000 Fri Jun 15 11:15:24 CST 2018
pool-1-thread-2 0-> accountBalance = 2037 Fri Jun 15 11:15:25 CST 2018
pool-1-thread-2 1-> accountBalance = 10000 Fri Jun 15 11:15:27 CST 2018
pool-1-thread-2 0-> accountBalance = 4646 Fri Jun 15 11:15:29 CST 2018
pool-1-thread-2 1-> accountBalance = 10000 Fri Jun 15 11:15:31 CST 2018
Process finished with exit code 0

多线程开发心得

给线程起名称

  • 给线程起一个和它要完成的任务相关的名字,这样可以方便找bug或追踪
  • 所有的主要框架甚至JDK都遵循这个最佳实践。

普通线程调用

/**
 * Created by Administrator on 2018/6/15 0015.
 * 任务
 */
public class MyThread extends Thread {

    @Override
    public synchronized void run() {
        try {
            System.out.println(new Date() + " 任务开始: " + Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(1 + new Random().nextInt(3));
            System.out.println(new Date() + " 任务结束:" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * Created by Administrator on 2018/6/15 0015.
 * 测试
 */
public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread;
        for (int i = 0; i < 5; i++) {
            myThread = new MyThread();
            /**直接使用ThreadsetName方法即可命名*/
            myThread.setName("武当梯云纵线程" + i);
            myThread.start();
        }
    }
}

---------------结果输出--------------------

Fri Jun 15 14:46:45 CST 2018 任务开始:武当梯云纵线程1
Fri Jun 15 14:46:45 CST 2018 任务开始:武当梯云纵线程0
Fri Jun 15 14:46:45 CST 2018 任务开始:武当梯云纵线程4
Fri Jun 15 14:46:45 CST 2018 任务开始:武当梯云纵线程3
Fri Jun 15 14:46:45 CST 2018 任务开始:武当梯云纵线程2
Fri Jun 15 14:46:46 CST 2018 任务结束:武当梯云纵线程0
Fri Jun 15 14:46:47 CST 2018 任务结束:武当梯云纵线程2
Fri Jun 15 14:46:47 CST 2018 任务结束:武当梯云纵线程3
Fri Jun 15 14:46:47 CST 2018 任务结束:武当梯云纵线程1
Fri Jun 15 14:46:48 CST 2018 任务结束:武当梯云纵线程4
Process finished with exit code 0

线程池调用

/**
 * Created by Administrator on 2018/6/15 0015.
 * 任务
 */
public class MyThread extends Thread {
    private String threadName;

    public MyThread(String threadName) {
        this.threadName = threadName;
    }

    @Override
    public synchronized void run() {
        try {
            /**为当前线程设置名称*/
            Thread.currentThread().setName(threadName);
            System.out.println(new Date() + " 任务开始:" + Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(1 + new Random().nextInt(3));
            System.out.println(new Date() + " 任务结束:" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
/**
 * Created by Administrator on 2018/6/15 0015.
 * 测试
 */
public class Test {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        MyThread myThread;
        for (int i = 0; i < 5; i++) {
            myThread = new MyThread("崆峒七伤拳线程" + i);
            /**线程池方式时,池中的线程是动态生成和管理的
             * 所以在这里做myThread.setName是没用的
             * 可以将名称传递过去,在run方法中进行设置
             * */
            executorService.execute(myThread);
            TimeUnit.SECONDS.sleep(1 + new Random().nextInt(1));
        }
    }
}

---------------------结果输出-----------------

Fri Jun 15 14:52:58 CST 2018 任务开始:崆峒七伤拳线程0
Fri Jun 15 14:52:59 CST 2018 任务开始:崆峒七伤拳线程1
Fri Jun 15 14:53:00 CST 2018 任务开始:崆峒七伤拳线程2
Fri Jun 15 14:53:00 CST 2018 任务结束:崆峒七伤拳线程0
Fri Jun 15 14:53:01 CST 2018 任务开始:崆峒七伤拳线程3
Fri Jun 15 14:53:01 CST 2018 任务结束:崆峒七伤拳线程1
Fri Jun 15 14:53:02 CST 2018 任务开始:崆峒七伤拳线程4
Fri Jun 15 14:53:03 CST 2018 任务结束:崆峒七伤拳线程2
Fri Jun 15 14:53:04 CST 2018 任务结束:崆峒七伤拳线程3
Fri Jun 15 14:53:04 CST 2018 任务结束:崆峒七伤拳线程4
Process finished with exit code 0

















猜你喜欢

转载自blog.csdn.net/wangmx1993328/article/details/80650882
今日推荐