基本概念

线程与进程

线程:系统资源分配的最小单位,使用独立的数据空间
进程:程序执行的最小单位,线程共享进程的数据空间

进程与线程的一个简单解释 - 阮一峰的网络日志

并发与并行

并发:同一时间段,多个任务都在执行 (单位时间内不一定同时执行);
并行:同单位时间内多个任务同时执行

并发的好处与坏处

JVM的内存模型与JMM(Java Memory Model)

JMM定义了程序中变量的访问规则。所有的共享变量都存储在主内存中,每个线程有自己的工作内存,工作内存保存的是共享变量的副本。线程对变量的读写操作必须在自己的工作内存中进行,而不能直接读写主内存中的变量
image.png

线程的基本操作

线程的创建方式

  1. 实现Runnable接口
  1. // 创建任务
  2. Runnable task = () -> {
  3. ...
  4. }
  5. // 运行任务
  6. new Thread(task).start()
  1. 继承Thread类
  1. class Task extends Thread{
  2. public void run(){
  3. ...
  4. }
  5. }
  6. Task task = new Taskl();
  7. task.start();

线程的状态(Thread.State)


from 《Java 并发编程艺术》

Thread.run() 和 Thread.start()的区别

  • Thread.run()
  1. public void run() {
  2. if (target != null) {
  3. // target的类型是Runnable,这里调用的是Runnable的run方法
  4. target.run();
  5. }
  6. }
  • Thread.start()
  1. public synchronized void start() {
  2. // 如果线程的状态不是0(not yet started),则会抛出运行异常,即一个线程不能够多次调用start方法
  3. if (threadStatus != 0)
  4. throw new IllegalThreadStateException();
  5. group.add(this);
  6. boolean started = false;
  7. try {
  8. start0();
  9. started = true;
  10. } finally {
  11. try {
  12. if (!started) {
  13. group.threadStartFailed(this);
  14. }
  15. } catch (Throwable ignore) {
  16. /* do nothing. If start0 threw a Throwable then
  17. it will be passed up the call stack */
  18. }
  19. }
  20. }

直接调用run方法是串行执行对象的run方法体.调用start方法是开启一个线程并使得线程处于RUNNABLE状态,当分配到时间片后就会执行run方法体.

wait(等待) and notify(通知)

  1. @Test
  2. public void waitAndNotify() {
  3. final String flag = "";
  4. // 线程1等待直到被唤醒后打印消息
  5. Runnable task1 = () -> {
  6. try {
  7. // 去掉synchronized,抛出java.lang.IllegalMonitorStateException
  8. synchronized (flag) {
  9. flag.wait();
  10. }
  11. System.out.println("I am notified!!");
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. };
  16. // 线程2等待2s后唤起线程1
  17. Runnable task2 = () -> {
  18. try {
  19. Thread.sleep(2000);
  20. // 去掉synchronized,抛出java.lang.IllegalMonitorStateException
  21. synchronized (flag) {
  22. flag.notify();
  23. }
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. };
  28. // 启动
  29. Thread t1 = new Thread(task1);
  30. Thread t2 = new Thread(task2);
  31. t1.start();
  32. t2.start();
  33. // 下面这段代码在junit测试中是必须的,如果是在main中则没有必要
  34. try {
  35. // 将线程1加入到当前线程,即当前测试线程会阻塞等待线程1运行完毕
  36. t1.join();
  37. } catch (InterruptedException e) {
  38. e.printStackTrace();
  39. }
  40. }
  • wait和notify是Object中的方法
  • wait和notify执行之前都需要获得目标对象的监视器(使用synchronized),执行后会释放监视器.
  • wait和sleep的区别和联系:
    • 联系:两者都可以暂停线程的执行
    • 区别:
      1. sleep没有释放锁,而wait释放了锁;
      2. wait方法被调用后,线程不会自动苏醒,需要别的线程调用同一对象上的notify或notifyAll方法.而sleep执行完成后,线程会自动苏醒;
      3. 所以,wait常被用于线程之间的通信.sleep常被用于暂停执行

suspend(挂起) and resume(继续执行)

  • suspend 的线程必须等到resume才能够继续执行,另外,suspend的线程在挂起时不会释放锁资源,因此可能会造成死锁(一直占用锁资源不释放)
  • 这两个方法被废弃了
  1. @Test
  2. public void suspendAndResume() throws InterruptedException {
  3. Object obj = new Object();
  4. Runnable task = () -> {
  5. synchronized(obj){
  6. Thread thread = Thread.currentThread();
  7. System.out.println(thread.getName() + " will be suspended!");
  8. thread.suspend();
  9. System.out.println(thread.getName() + " was resumed!");
  10. }
  11. };
  12. Thread t1 = new Thread(task, "task1");
  13. t1.start();
  14. // 如果注释掉part1则可能会造成死锁.由于resume先于suspend被调用,也就是一直处于被挂起状态
  15. // 记为part1 >>>
  16. try {
  17. Thread.sleep(2000);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. // <<<
  22. t1.resume();
  23. t1.join();
  24. }

join and yield

  • join是谁加入时?谁等待?
  1. public static void main(String[] args){
  2. int count = 0;
  3. Thread t = new Thread(
  4. () -> {
  5. for(int i = 0; i < 1000; i++){
  6. count++;
  7. }
  8. }
  9. );
  10. t.join(); // 将t这一线程加入到main(当前线程),所以main线程等待t完成
  11. }

stop and interrupt

  1. stop和interrupt的不同:Thread.interrupt()被调用时不会使目标线程立即退出,只是通知目标线程中断,也就是设置中断标志位。而Thread.stop()是立即终止线程(操作过程中立即终止可能会造成数据不一致)。
  2. Thread.sleep过程如果中断会抛出异常,并且它会清除中断标志(所以如有需要应重新加上中断标志)
  3. 常用方法
  • void interrupt()
  • boolean isInterrupted() 判断是否被中断,不会清除标志位
  • static boolean interrupted() 判断是否被中断,并清除中断标志位
  1. stop方法被废弃了
  1. @Test
  2. public void stopAndInterrupt() throws InterruptedException {
  3. @ToString
  4. class People {
  5. String name = "001";
  6. String id = "001";
  7. }
  8. People p1 = new People();
  9. People p2 = new People();
  10. Runnable t1 = () -> {
  11. Thread thread = Thread.currentThread();
  12. while(!thread.isInterrupted()){
  13. System.out.println(thread.getName());
  14. p1.name = "002";
  15. try {
  16. Thread.sleep(3000);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. p1.id = "002";
  21. }
  22. };
  23. Runnable t2 = () -> {
  24. Thread thread = Thread.currentThread();
  25. while(!thread.isInterrupted()){
  26. System.out.println(thread.getName());
  27. p2.name = "002";
  28. try {
  29. Thread.sleep(3000);
  30. } catch (InterruptedException e) {
  31. // Thread.sleep由于抛出异常它会清除中断标志,所以需要重新加上
  32. thread.interrupt();
  33. }
  34. p2.id = "002";
  35. }
  36. };
  37. /** 使用stop暴力停止线程:数据会不一致 **/
  38. Thread thread1 = new Thread(t1, "thread1");
  39. thread1.start();
  40. Thread.currentThread().sleep(1000);
  41. thread1.stop();
  42. System.out.println(p1);
  43. /** 使用interrupt中断线程 **/
  44. Thread thread2 = new Thread(t2, "thread2");
  45. thread2.start();
  46. Thread.currentThread().sleep(1000);
  47. thread2.interrupt();
  48. thread2.join(); // 必须阻塞,否则也不能获得正确的结果(因为thread2还没有修改 )
  49. System.out.println(p2);
  50. }

结果

  1. thread1
  2. People(name=002, id=001)
  3. thread2
  4. People(name=002, id=002)