什么是线程?
- 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
- 程序员可以通过它进行多处理器编程,可以使用多线程对运算密集型任务提速。比如一个线程完成一个任务要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
- 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(); /**直接使用Thread的setName方法即可命名*/ 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