写点什么

面试腾讯,字节跳动首先要掌握的 Java 多线程,一次帮你全掌握

发布于: 2021 年 01 月 04 日

其实程序是一段静态的代码,它是应用程序执行的脚本。进程就是程序动态的执行过程,它具有动态性,并发性,独立性。线程是进程调度和执行的单位。


进程:每个进程都有独立的代码和数据空间(进程上下文),一个进程包含一个或者多个线程,同时线程是资源分配的最小单位。


线程:同一类线程共享代码和数据空间,并且每个线程有独立运行栈和程序计数器,同时线程是调度的最小单位。


那什么是多进程呢? ,常见的是打开我们自己电脑任务管理器里面就还有多个进程,其实指的是我们的操作系统能同时运行多个任务(微信,QQ 等)。


多线程其实就是一个进程有多条路径在运行。


一、多线程实现的方式


在 Java 中实现多线程有三种方式,第一种方式是继承 Thread 类,第二种方式是实现 Runnable 接口, 第三种是实现 Callable,结合 Future 使用(了解),并发编程中使用,这里不详细说:


第一种实现多线程的方式,继承 Thread 类,代码实现如下:


 
复制代码


  1. public class RabbitDemo extends Thread {

  2. @Override

  3. public void run() {

  4. for (int i = 0; i <10 ; i++) {

  5. System.out.println("兔子跑了:"+i+"步");

  6. } }}public class TortoiseDemo extends Thread {

  7. @Override

  8. public void run() {

  9. for (int i = 0; i <10 ; i++) {

  10. System.out.println("乌龟跑了:"+i+"步");

  11. } }}public class ClientThreadTest {

  12. public static void main(String[] args) {

  13. RabbitDemo rabbitDemo = new RabbitDemo();

  14. TortoiseDemo tortoiseDemo = new TortoiseDemo();

  15. rabbitDemo.start(); tortoiseDemo.start(); for (int i = 0; i < 10; i++) {

  16. System.out.println("main==>" + i);

  17. } }}复制代码


第二个实例实现多个线程同时共享一个成员变量线程的运行状态。


 
复制代码


  1. public class Qp12306 implements Runnable {

  2. private int num=50;

  3. @Override

  4. public void run() {

  5. while (true){

  6. if (num<=0){

  7. break;

  8. } System.out.println(Thread.currentThread().getName()+"抢到票"+num--);

  9. } }}public class ClientQpTest {

  10. public static void main(String[] args) {

  11. Qp12306 qp12306 = new Qp12306();

  12. Thread thread = new Thread(qp12306, "张三");

  13. Thread thread1 = new Thread(qp12306, "李四");

  14. Thread thread2 = new Thread(qp12306, "王五");

  15. thread.start(); thread1.start(); thread2.start(); }}复制代码


第二种方式:实现 Runnable 接口,重写 run 方法,这种方式我们实现多线程常用的方式(避免 Java 单继承的限制)并且该方式采用了静态代理设计模式(参考: 静态代理),代码实例如下 :


 
复制代码


  1. public class RunnableDemo implements Runnable {

  2. @Override

  3. public void run() {

  4. for (int i = 0; i < 10; i++) {

  5. System.out.println("Runnable:" + i);

  6. } }}public class ClientRunableTest {

  7. public static void main(String[] args) {

  8. RunnableDemo runnableDemo = new RunnableDemo();

  9. Thread thread = new Thread(runnableDemo);

  10. thread.start(); }}复制代码


第三方式:实现 Callable 接口,结合 Future 实现多线程,代码实例如下:


 
复制代码


  1. /**

  2. * 使用Callable创建线程 */public class Race implements Callable<Integer> { private String name; private long time; //延时时间

  3. private boolean flag = true;

  4. private int step = 0; //步

  5. @Override public Integer call() throws Exception { while (flag) {

  6. Thread.sleep(time);

  7. step++; } return step;

  8. } public Race(String name, long time) {

  9. this.name = name; this.time = time;

  10. } public void setFlag(boolean flag) { this.flag = flag; }}public class CallableTest { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executorService = Executors.newFixedThreadPool(2);

  11. Race race = new Race("张三", 200);

  12. Race race1 = new Race("李四", 500);

  13. Future<Integer> result = executorService.submit(race); Future<Integer> result1 = executorService.submit(race1); Thread.sleep(3000);

  14. race.setFlag(false);

  15. race1.setFlag(false);

  16. int num1 = result.get(); int num2 = result1.get(); System.out.println("张三-->" + num1 + "步");

  17. System.out.println("李四-->" + num2 + "步");

  18. //停止服务 executorService.shutdownNow(); }}复制代码


如果一个类继承 Thread,则不适合资源共享。但是如果实现了 Runable 接口的话,则很容易的实现资源共享。


总结:


实现 Runnable 接口比继承 Thread 类所具有的优势:


1、适合多个相同的程序代码的线程去处理同一个资源


2、可以避免 java 中的单继承的限制


3、增加程序的健壮性,代码可以被多个线程共享,代码和数据独立


4、线程池只能放入实现 Runable 或 callable 类线程,不能直接放入继承 Thread 的类


main 方法其实也是一个线程。在 java 中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到 CPU 的资源。


在 java 中,每次程序运行至少启动 2 个线程。一个是 main 线程,一个是垃圾收集线程。因为每当使用 java 命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM就是在操作系统中启动了一个进程。


二、线程的状态和方法


线程从创建,运行到结束总是处于五种状态之一:新建状态,就绪状态,运行状态,阻塞状态,死亡状态。 线程共包括以下 5 种状态:


1.新建状态 :线程对象被创建后就进入了新建状态,Thread thread = new Thread();


2.就绪状态(Runnable):也被称之为“可执行状态”,当线程被 new 出来后,其他的线程调用了该对象的 start()方法,即 thread.start(),此时线程位于“可运行线程池”中,只等待获取 CPU 的使用权,随时可以被 CPU 调用。进入就绪状态的进程除 CPU 之外,其他运行所需的资源都已经全部获得。


3.运行状态(Running):线程获取 CPU 权限开始执行。注意:线程只能从就绪状态进入到运行状态。


4.阻塞状态(Bloacked):阻塞状态是线程因为某种原因放弃 CPU 的使用权,暂时停止运行,知道线程进入就绪状态后才能有机会转到运行状态。


阻塞的情况分三种:


(1)、等待阻塞:运行的线程执行 wait()方法,该线程会释放占用的所有资源,JVM 会把该线程放入“等待池中”。进入这个状态后是不能自动唤醒的,必须依靠其他线程调用 notify()或者 notifyAll()方法才能被唤醒。 (2)、同步阻塞:运行的线程在获取对象的(synchronized)同步锁时,若该同步锁被其他线程占用,则 JVM 会把该线程放入“锁池”中。 (3)、其他阻塞:通过调用线程的 sleep()或者 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新回到就绪状态 5. 死亡(Dead):线程执行完了或因异常退出了 run()方法,则该线程结束生命周期。


阻塞线程方法的说明:


wait(), notify(),notifyAll()这三个方法是结合使用的,都属于 Object 中的方法,wait 的作用是当当前线程释放它所持有的锁进入等待状态(释放对象锁),而 notify 和 notifyAll 则是唤醒当前对象上的等待线程。 wait() —— 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。


sleep() 和 yield()方法是属于 Thread 类中的 sleep()的作用是让当前线程休眠(正在执行的线程主动让出 cpu,然后 cpu 就可以去执行其他任务),即当前线程会从“运行状态”进入到阻塞状态”,但仍然保持对象锁,仍然占有该锁。当延时时间过后该线程重新阻塞状态变成就绪状态,从而等待 cpu 的调度执行。 sleep()睡眠时,保持对象锁,仍然占有该锁。 yield()的作用是让步,它能够让当前线程从运行状态进入到就绪状态”,从而让其他等待线程获取执行权,但是不能保证在当前线程调用 yield()之后,其他线程就一定能获得执行权,也有可能是当前线程又回到“运行状态”继续运行。


wait () , sleep()的区别:


1、每个对象都有一个锁来控制同步访问。Synchronized 关键字可以和对象的锁交互,来实现线程的同步。 sleep 方法没有释放锁,而 wait 方法释放了锁,使得其他线程可以使用同步控制块或者方法。 2、 wait,notify 和 notifyAll 只能在同步控制方法或者同步控制块里面使用,而 sleep 可以在任何地方使用 3、 sleep 必须捕获异常,而 wait,notify 和 notifyAll 不需要捕获异常 4、 sleep()睡眠时,保持对象锁,仍然占有该锁,而 wait()释放对象锁;


三、线程的基本信息和优先级


涉及到线程的方法:Thread.currentThread() 当前线程,setName():设置名称,getName():获取名称,isAlive():判断状态 优先级:Java 线程有优先级,优先级高的线程能获取更多的运行机会。 Java 线程的优先级用整数表示,取值范围是 1~10,Thread 类有以下三个静态常量: static int MAX_PRIORITY 线程可以具有的最高优先级,取值为 10。 static int MIN_PRIORITY 线程可以具有的最低优先级,取值为 1。 static int NORM_PRIORITY 分配给线程的默认优先级,取值为 5。


下面代码实现: 基本信息


 
复制代码


  1. public static void main(String[] args) throws InterruptedException {

  2. Rundemo rundemo = new Rundemo();

  3. Thread thread = new Thread(rundemo);

  4. thread.setName("线程"); //设置线程的名称

  5. System.out.println(thread.getName()); //获取当前线性的名称

  6. System.out.println(Thread.currentThread().getName()); //main线程

  7. thread.start();

  8. System.out.println("线程启动后的状态:" + thread.isAlive());

  9. Thread.sleep(10);

  10. rundemo.stop();

  11. Thread.sleep(1000); //停止后休眠一下,再看线程的状态

  12. System.out.println("线程停止后的状态:" + thread.isAlive());

  13. test();

  14. }

  15. 复制代码


优先级


 
复制代码


  1. public static void test() throws InterruptedException {

  2. Rundemo rundemo1 = new Rundemo();

  3. Thread thread1 = new Thread(rundemo1);

  4. thread1.setName("线程thread1");

  5. Rundemo rundemo2 = new Rundemo();

  6. Thread thread2 = new Thread(rundemo2);

  7. thread2.setName("线程thread2");

  8. thread1.setPriority(Thread.MAX_PRIORITY); thread2.setPriority(Thread.MIN_PRIORITY); thread1.start(); thread2.start(); Thread.sleep(1000);

  9. rundemo1.stop(); rundemo2.stop(); }复制代码


四、线程的同步和死锁问题


同步方法:synchronized 同步代码块:synchronized(引用类型|this|类.class){ } 线程同步是为了多个线程同时访问一份资源确保数据的正确,不会造成数据的桩读,死锁是线程间相互等待锁锁造成的.


下面代码实现:多个线程同时访问一份资源使用 synchronized


 
复制代码


  1. public class Qp12306 implements Runnable {

  2. private int num = 10;

  3. private boolean flag = true;

  4. @Override

  5. public synchronized void run() {

  6. while (true) {

  7. test(); } } public synchronized void test() {

  8. if (num <= 0) {

  9. flag = false;

  10. return;

  11. } System.out.println(Thread.currentThread().getName() + "抢到票" + num--);

  12. }}public class ClientQpTest {

  13. public static void main(String[] args) {

  14. Qp12306 qp12306 = new Qp12306();

  15. Thread thread = new Thread(qp12306, "张三");

  16. Thread thread1 = new Thread(qp12306, "李四");

  17. Thread thread2 = new Thread(qp12306, "王五");

  18. thread.start(); thread1.start(); thread2.start(); }}复制代码


运行结果



synchronized 静态代码块


 
复制代码


  1. public class Client {

  2. public static void main(String[] args) throws InterruptedException {

  3. JvmThread thread1 = new JvmThread(100);

  4. JvmThread thread2 = new JvmThread(500);

  5. thread1.start(); thread2.start(); }}class JvmThread extends Thread{

  6. private long time;

  7. public JvmThread() {

  8. } public JvmThread(long time) {

  9. this.time =time;

  10. } @Override

  11. public void run() {

  12. System.out.println(Thread.currentThread().getName()+"-->创建:"+Jvm.getInstance(time));

  13. }}/**

  14. * 单例设计模式

  15. * 确保一个类只有一个对象

  16. * 懒汉式

  17. * 1、构造器私有化,避免外部直接创建对象

  18. * 2、声明一个私有的静态变量

  19. * 3、创建一个对外的公共的静态方法 访问该变量,如果变量没有对象,创建该对象

  20. */

  21. class Jvm {

  22. //声明一个私有的静态变量

  23. private static Jvm instance =null;

  24. //构造器私有化,避免外部直接创建对象

  25. private Jvm(){

  26. }

  27. //创建一个对外的公共的静态方法 访问该变量,如果变量没有对象,创建该对象

  28. public static Jvm getInstance(long time){

  29. if(null==instance){

  30. synchronized(Jvm.class){

  31. if(null==instance ){

  32. try {

  33. Thread.sleep(time);

  34. } catch (InterruptedException e) {

  35. e.printStackTrace();

  36. }

  37. instance =new Jvm();

  38. }

  39. }

  40. }

  41. return instance;

  42. }

  43. public static Jvm getInstance3(long time){

  44. synchronized(Jvm.class){

  45. if(null==instance ){

  46. try {

  47. Thread.sleep(time);

  48. } catch (InterruptedException e) {

  49. e.printStackTrace();

  50. }

  51. instance =new Jvm();

  52. }

  53. return instance;

  54. }

  55. }

  56.  

  57. }

  58. 复制代码


五、生产者消费者模式


生产者和消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一个存储空间,如下图所示,生产者向空间里存放数据,而消费者取用数据,如果不加以协调可能会出现以下情况: 存储空间已满,而生产者占用着它,消费者等着生产者让出空间从而去除产品,生产者等着消费者消费产品,从而向空间中添加产品。互相等待,从而发生死锁.


具体实现代码如下:


 
复制代码


  1. /**

  2. * 生产者

  3. */

  4. public class ProduceRu implements Runnable {

  5. private Movie movie;

  6. public ProduceRu(Movie movie) {

  7. this.movie = movie;

  8. } @Override

  9. public void run() {

  10. for (int i = 0; i < 10; i++) {

  11. if (0 == i % 2) {

  12. movie.proTest("向往的生活");

  13. } else {

  14. movie.proTest("好声音");

  15. } } }}/**

  16. *消费者

  17. */

  18. public class ConsumeRu implements Runnable {

  19. private Movie movie;

  20. public ConsumeRu(Movie movie) {

  21. this.movie = movie;

  22. } @Override

  23. public void run() {

  24. for(int i=0;i<10;i++){

  25. movie.conTest(); } }}/**

  26. * 生产者消费者模式共同访问同一份资源

  27. * wait() 等等,释放锁,sleep()不释放锁

  28. * notify()/notifyAll():唤醒

  29. */

  30. public class Movie {

  31. private String pic;

  32. //flag--->true 生产者生产,消费者等待,生产完后通知消费

  33. //flag--->false 消费者消费,生产者等待,消费完通知生产

  34. private boolean flag = true;

  35. /**

  36. * 生产者生产,消费者等待,生产完后通知消费

  37. * @param pic

  38. */

  39. public synchronized void proTest(String pic) {

  40. if (!flag) {

  41. try {

  42. this.wait();

  43. } catch (InterruptedException e) {

  44. e.printStackTrace();

  45. }

  46. }

  47. try {

  48. Thread.sleep(1000);

  49. } catch (InterruptedException e) {

  50. e.printStackTrace();

  51. }

  52. System.out.println("生产了:" + pic);

  53. this.pic = pic;

  54. this.notify();

  55. this.flag = false;

  56. }

  57. /**

  58. * 消费者消费,生产者等待,消费完通知生产

  59. */

  60. public synchronized void conTest() {

  61. if (flag) {

  62. try {

  63. this.wait();

  64. } catch (InterruptedException e) {

  65. e.printStackTrace();

  66. }

  67. }

  68. System.out.println("消费者:" + pic);

  69. this.notifyAll();

  70. this.flag = true;

  71. }

  72. }

  73. public class ClientTest {

  74. public static void main(String[] args) {

  75. Movie movie=new Movie();

  76. new Thread(new ProduceRu(movie)).start();

  77. new Thread(new ConsumeRu(movie)).start();

  78. }

  79. }

  80. 复制代码


六、任务调度


1.Thread 实现方法 这是最常见的,创建一个 thread,然后让它在 while 循环里一直运行着,通过 sleep 方法来达到定时任务的效果。这样可以快速简单的实现,代码如下:


 
复制代码


  1. public class ThreadTest {

  2. public static void main(String[] args) {

  3. Runnable runnable = new Runnable() {

  4. @Override

  5. public void run() {

  6. try {

  7. System.out.println("任务开始了");

  8. while (true) {

  9. Thread.sleep(1000);

  10. System.out.println("hello");

  11. } } catch (InterruptedException e) {

  12. e.printStackTrace(); } } }; Thread thread = new Thread(runnable);

  13. thread.start(); }}复制代码


2.TimeTask 实现方法 Thread 方法优点就是简单,但缺少了灵活性,TimeTask 实现方法最主要的两个优点是:可以控制启动和取消任务的时间、第一次执行任务时可以指定想要 delay 的时间。 实现的过程中 Timer 用于调度任务,TimeTask 用户具体的实现,是线程安全的,代码如下:


 
复制代码


  1. public class TimeTest {

  2. public static void main(String[] args) {

  3. Timer timer=new Timer();

  4. timer.schedule(new TimerTask() {

  5. @Override

  6. public void run() {

  7. System.out.println("任务运行开始.......");

  8. } },new Date(),1000);

  9. }}复制代码


3.ScheduledExecutorService 实现方法 ScheduledExecutorService 是从 Java java.util.concurrent 里 相比于上两个方法,它有以下好处: 相比于 Timer 的单线程,它是通过线程池的方式来执行任务的可以很灵活的去设定第一次执行任务 delay 时间 具体代码如下:


方法说明: ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) 创建并执行在给定的初始延迟之后首先启用的定期动作,随后在一个执行的终止和下一个执行的开始之间给定的延迟。 如果任务的执行遇到异常,则后续的执行被抑制。 否则,任务将仅通过取消或终止执行人终止。 参数 command - 要执行的任务 initialDelay - 延迟第一次执行的时间 (就是第一次指定定时延时多久),代码里我延时 10 秒 delay - 一个执行终止与下一个执行的开始之间的延迟 unit - initialDelay 和 delay 参数的时间单位


 
复制代码


  1. public class ScheduledExecutorServiceTest {

  2. public static void main(String[] args) {

  3. ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

  4. scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

  5. @Override

  6. public void run() {

  7. System.out.println("任务!!!!");

  8. } },10000,1000, TimeUnit.MILLISECONDS);

  9. }}复制代码


读者福利


对于大厂面试,我最后想要强调的一点就是心态真的很重要,是决定你在面试过程中发挥的关键,若不能正常发挥,很可能就因为一个小失误与 offer 失之交臂,所以一定要重视起来。另外提醒一点,充分复习,是消除你紧张的心理状态的关键,但你复习充分了,自然面试过程中就要有底气得多。


我这边为大家准备了近几年来各大厂的 Java 面试资料,均可以免费提供,希望大家金九银十面试顺利,拿下自己心仪的 offer!


如果你有需要的话,麻烦一键三连+评论,然后添加 VX(tkzl6666)即可免费领取






其实程序是一段静态的代码,它是应用程序执行的脚本。进程就是程序动态的执行过程,它具有动态性,并发性,独立性。线程是进程调度和执行的单位。


进程:每个进程都有独立的代码和数据空间(进程上下文),一个进程包含一个或者多个线程,同时线程是资源分配的最小单位。


线程:同一类线程共享代码和数据空间,并且每个线程有独立运行栈和程序计数器,同时线程是调度的最小单位。


那什么是多进程呢? ,常见的是打开我们自己电脑任务管理器里面就还有多个进程,其实指的是我们的操作系统能同时运行多个任务(微信,QQ 等)。


多线程其实就是一个进程有多条路径在运行。


一、多线程实现的方式


在 Java 中实现多线程有三种方式,第一种方式是继承 Thread 类,第二种方式是实现 Runnable 接口, 第三种是实现 Callable,结合 Future 使用(了解),并发编程中使用,这里不详细说:


第一种实现多线程的方式,继承 Thread 类,代码实现如下:


 
复制代码


  1. public class RabbitDemo extends Thread {

  2. @Override

  3. public void run() {

  4. for (int i = 0; i <10 ; i++) {

  5. System.out.println("兔子跑了:"+i+"步");

  6. } }}public class TortoiseDemo extends Thread {

  7. @Override

  8. public void run() {

  9. for (int i = 0; i <10 ; i++) {

  10. System.out.println("乌龟跑了:"+i+"步");

  11. } }}public class ClientThreadTest {

  12. public static void main(String[] args) {

  13. RabbitDemo rabbitDemo = new RabbitDemo();

  14. TortoiseDemo tortoiseDemo = new TortoiseDemo();

  15. rabbitDemo.start(); tortoiseDemo.start(); for (int i = 0; i < 10; i++) {

  16. System.out.println("main==>" + i);

  17. } }}复制代码


第二个实例实现多个线程同时共享一个成员变量线程的运行状态。


 
复制代码


  1. public class Qp12306 implements Runnable {

  2. private int num=50;

  3. @Override

  4. public void run() {

  5. while (true){

  6. if (num<=0){

  7. break;

  8. } System.out.println(Thread.currentThread().getName()+"抢到票"+num--);

  9. } }}public class ClientQpTest {

  10. public static void main(String[] args) {

  11. Qp12306 qp12306 = new Qp12306();

  12. Thread thread = new Thread(qp12306, "张三");

  13. Thread thread1 = new Thread(qp12306, "李四");

  14. Thread thread2 = new Thread(qp12306, "王五");

  15. thread.start(); thread1.start(); thread2.start(); }}复制代码


第二种方式:实现 Runnable 接口,重写 run 方法,这种方式我们实现多线程常用的方式(避免 Java 单继承的限制)并且该方式采用了静态代理设计模式(参考: 静态代理),代码实例如下 :


 
复制代码


  1. public class RunnableDemo implements Runnable {

  2. @Override

  3. public void run() {

  4. for (int i = 0; i < 10; i++) {

  5. System.out.println("Runnable:" + i);

  6. } }}public class ClientRunableTest {

  7. public static void main(String[] args) {

  8. RunnableDemo runnableDemo = new RunnableDemo();

  9. Thread thread = new Thread(runnableDemo);

  10. thread.start(); }}复制代码


第三方式:实现 Callable 接口,结合 Future 实现多线程,代码实例如下:


 
复制代码


  1. /**

  2. * 使用Callable创建线程 */public class Race implements Callable<Integer> { private String name; private long time; //延时时间

  3. private boolean flag = true;

  4. private int step = 0; //步

  5. @Override public Integer call() throws Exception { while (flag) {

  6. Thread.sleep(time);

  7. step++; } return step;

  8. } public Race(String name, long time) {

  9. this.name = name; this.time = time;

  10. } public void setFlag(boolean flag) { this.flag = flag; }}public class CallableTest { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executorService = Executors.newFixedThreadPool(2);

  11. Race race = new Race("张三", 200);

  12. Race race1 = new Race("李四", 500);

  13. Future<Integer> result = executorService.submit(race); Future<Integer> result1 = executorService.submit(race1); Thread.sleep(3000);

  14. race.setFlag(false);

  15. race1.setFlag(false);

  16. int num1 = result.get(); int num2 = result1.get(); System.out.println("张三-->" + num1 + "步");

  17. System.out.println("李四-->" + num2 + "步");

  18. //停止服务 executorService.shutdownNow(); }}复制代码


如果一个类继承 Thread,则不适合资源共享。但是如果实现了 Runable 接口的话,则很容易的实现资源共享。


总结:


实现 Runnable 接口比继承 Thread 类所具有的优势:


1、适合多个相同的程序代码的线程去处理同一个资源


2、可以避免 java 中的单继承的限制


3、增加程序的健壮性,代码可以被多个线程共享,代码和数据独立


4、线程池只能放入实现 Runable 或 callable 类线程,不能直接放入继承 Thread 的类


main 方法其实也是一个线程。在 java 中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到 CPU 的资源。


在 java 中,每次程序运行至少启动 2 个线程。一个是 main 线程,一个是垃圾收集线程。因为每当使用 java 命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM就是在操作系统中启动了一个进程。


二、线程的状态和方法


线程从创建,运行到结束总是处于五种状态之一:新建状态,就绪状态,运行状态,阻塞状态,死亡状态。 线程共包括以下 5 种状态:


1.新建状态 :线程对象被创建后就进入了新建状态,Thread thread = new Thread();


2.就绪状态(Runnable):也被称之为“可执行状态”,当线程被 new 出来后,其他的线程调用了该对象的 start()方法,即 thread.start(),此时线程位于“可运行线程池”中,只等待获取 CPU 的使用权,随时可以被 CPU 调用。进入就绪状态的进程除 CPU 之外,其他运行所需的资源都已经全部获得。


3.运行状态(Running):线程获取 CPU 权限开始执行。注意:线程只能从就绪状态进入到运行状态。


4.阻塞状态(Bloacked):阻塞状态是线程因为某种原因放弃 CPU 的使用权,暂时停止运行,知道线程进入就绪状态后才能有机会转到运行状态。


阻塞的情况分三种:


(1)、等待阻塞:运行的线程执行 wait()方法,该线程会释放占用的所有资源,JVM 会把该线程放入“等待池中”。进入这个状态后是不能自动唤醒的,必须依靠其他线程调用 notify()或者 notifyAll()方法才能被唤醒。 (2)、同步阻塞:运行的线程在获取对象的(synchronized)同步锁时,若该同步锁被其他线程占用,则 JVM 会把该线程放入“锁池”中。 (3)、其他阻塞:通过调用线程的 sleep()或者 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新回到就绪状态 5. 死亡(Dead):线程执行完了或因异常退出了 run()方法,则该线程结束生命周期。


阻塞线程方法的说明:


wait(), notify(),notifyAll()这三个方法是结合使用的,都属于 Object 中的方法,wait 的作用是当当前线程释放它所持有的锁进入等待状态(释放对象锁),而 notify 和 notifyAll 则是唤醒当前对象上的等待线程。 wait() —— 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。


sleep() 和 yield()方法是属于 Thread 类中的 sleep()的作用是让当前线程休眠(正在执行的线程主动让出 cpu,然后 cpu 就可以去执行其他任务),即当前线程会从“运行状态”进入到阻塞状态”,但仍然保持对象锁,仍然占有该锁。当延时时间过后该线程重新阻塞状态变成就绪状态,从而等待 cpu 的调度执行。 sleep()睡眠时,保持对象锁,仍然占有该锁。 yield()的作用是让步,它能够让当前线程从运行状态进入到就绪状态”,从而让其他等待线程获取执行权,但是不能保证在当前线程调用 yield()之后,其他线程就一定能获得执行权,也有可能是当前线程又回到“运行状态”继续运行。


wait () , sleep()的区别:


1、每个对象都有一个锁来控制同步访问。Synchronized 关键字可以和对象的锁交互,来实现线程的同步。 sleep 方法没有释放锁,而 wait 方法释放了锁,使得其他线程可以使用同步控制块或者方法。 2、 wait,notify 和 notifyAll 只能在同步控制方法或者同步控制块里面使用,而 sleep 可以在任何地方使用 3、 sleep 必须捕获异常,而 wait,notify 和 notifyAll 不需要捕获异常 4、 sleep()睡眠时,保持对象锁,仍然占有该锁,而 wait()释放对象锁;


三、线程的基本信息和优先级


涉及到线程的方法:Thread.currentThread() 当前线程,setName():设置名称,getName():获取名称,isAlive():判断状态 优先级:Java 线程有优先级,优先级高的线程能获取更多的运行机会。 Java 线程的优先级用整数表示,取值范围是 1~10,Thread 类有以下三个静态常量: static int MAX_PRIORITY 线程可以具有的最高优先级,取值为 10。 static int MIN_PRIORITY 线程可以具有的最低优先级,取值为 1。 static int NORM_PRIORITY 分配给线程的默认优先级,取值为 5。


下面代码实现: 基本信息


 
复制代码


  1. public static void main(String[] args) throws InterruptedException {

  2. Rundemo rundemo = new Rundemo();

  3. Thread thread = new Thread(rundemo);

  4. thread.setName("线程"); //设置线程的名称

  5. System.out.println(thread.getName()); //获取当前线性的名称

  6. System.out.println(Thread.currentThread().getName()); //main线程

  7. thread.start();

  8. System.out.println("线程启动后的状态:" + thread.isAlive());

  9. Thread.sleep(10);

  10. rundemo.stop();

  11. Thread.sleep(1000); //停止后休眠一下,再看线程的状态

  12. System.out.println("线程停止后的状态:" + thread.isAlive());

  13. test();

  14. }

  15. 复制代码


优先级


 
复制代码


  1. public static void test() throws InterruptedException {

  2. Rundemo rundemo1 = new Rundemo();

  3. Thread thread1 = new Thread(rundemo1);

  4. thread1.setName("线程thread1");

  5. Rundemo rundemo2 = new Rundemo();

  6. Thread thread2 = new Thread(rundemo2);

  7. thread2.setName("线程thread2");

  8. thread1.setPriority(Thread.MAX_PRIORITY); thread2.setPriority(Thread.MIN_PRIORITY); thread1.start(); thread2.start(); Thread.sleep(1000);

  9. rundemo1.stop(); rundemo2.stop(); }复制代码


四、线程的同步和死锁问题


同步方法:synchronized 同步代码块:synchronized(引用类型|this|类.class){ } 线程同步是为了多个线程同时访问一份资源确保数据的正确,不会造成数据的桩读,死锁是线程间相互等待锁锁造成的.


下面代码实现:多个线程同时访问一份资源使用 synchronized


 
复制代码


  1. public class Qp12306 implements Runnable {

  2. private int num = 10;

  3. private boolean flag = true;

  4. @Override

  5. public synchronized void run() {

  6. while (true) {

  7. test(); } } public synchronized void test() {

  8. if (num <= 0) {

  9. flag = false;

  10. return;

  11. } System.out.println(Thread.currentThread().getName() + "抢到票" + num--);

  12. }}public class ClientQpTest {

  13. public static void main(String[] args) {

  14. Qp12306 qp12306 = new Qp12306();

  15. Thread thread = new Thread(qp12306, "张三");

  16. Thread thread1 = new Thread(qp12306, "李四");

  17. Thread thread2 = new Thread(qp12306, "王五");

  18. thread.start(); thread1.start(); thread2.start(); }}复制代码


运行结果



synchronized 静态代码块


 
复制代码


  1. public class Client {

  2. public static void main(String[] args) throws InterruptedException {

  3. JvmThread thread1 = new JvmThread(100);

  4. JvmThread thread2 = new JvmThread(500);

  5. thread1.start(); thread2.start(); }}class JvmThread extends Thread{

  6. private long time;

  7. public JvmThread() {

  8. } public JvmThread(long time) {

  9. this.time =time;

  10. } @Override

  11. public void run() {

  12. System.out.println(Thread.currentThread().getName()+"-->创建:"+Jvm.getInstance(time));

  13. }}/**

  14. * 单例设计模式

  15. * 确保一个类只有一个对象

  16. * 懒汉式

  17. * 1、构造器私有化,避免外部直接创建对象

  18. * 2、声明一个私有的静态变量

  19. * 3、创建一个对外的公共的静态方法 访问该变量,如果变量没有对象,创建该对象

  20. */

  21. class Jvm {

  22. //声明一个私有的静态变量

  23. private static Jvm instance =null;

  24. //构造器私有化,避免外部直接创建对象

  25. private Jvm(){

  26. }

  27. //创建一个对外的公共的静态方法 访问该变量,如果变量没有对象,创建该对象

  28. public static Jvm getInstance(long time){

  29. if(null==instance){

  30. synchronized(Jvm.class){

  31. if(null==instance ){

  32. try {

  33. Thread.sleep(time);

  34. } catch (InterruptedException e) {

  35. e.printStackTrace();

  36. }

  37. instance =new Jvm();

  38. }

  39. }

  40. }

  41. return instance;

  42. }

  43. public static Jvm getInstance3(long time){

  44. synchronized(Jvm.class){

  45. if(null==instance ){

  46. try {

  47. Thread.sleep(time);

  48. } catch (InterruptedException e) {

  49. e.printStackTrace();

  50. }

  51. instance =new Jvm();

  52. }

  53. return instance;

  54. }

  55. }

  56.  

  57. }

  58. 复制代码


五、生产者消费者模式


生产者和消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一个存储空间,如下图所示,生产者向空间里存放数据,而消费者取用数据,如果不加以协调可能会出现以下情况: 存储空间已满,而生产者占用着它,消费者等着生产者让出空间从而去除产品,生产者等着消费者消费产品,从而向空间中添加产品。互相等待,从而发生死锁.


具体实现代码如下:


 
复制代码


  1. /**

  2. * 生产者

  3. */

  4. public class ProduceRu implements Runnable {

  5. private Movie movie;

  6. public ProduceRu(Movie movie) {

  7. this.movie = movie;

  8. } @Override

  9. public void run() {

  10. for (int i = 0; i < 10; i++) {

  11. if (0 == i % 2) {

  12. movie.proTest("向往的生活");

  13. } else {

  14. movie.proTest("好声音");

  15. } } }}/**

  16. *消费者

  17. */

  18. public class ConsumeRu implements Runnable {

  19. private Movie movie;

  20. public ConsumeRu(Movie movie) {

  21. this.movie = movie;

  22. } @Override

  23. public void run() {

  24. for(int i=0;i<10;i++){

  25. movie.conTest(); } }}/**

  26. * 生产者消费者模式共同访问同一份资源

  27. * wait() 等等,释放锁,sleep()不释放锁

  28. * notify()/notifyAll():唤醒

  29. */

  30. public class Movie {

  31. private String pic;

  32. //flag--->true 生产者生产,消费者等待,生产完后通知消费

  33. //flag--->false 消费者消费,生产者等待,消费完通知生产

  34. private boolean flag = true;

  35. /**

  36. * 生产者生产,消费者等待,生产完后通知消费

  37. * @param pic

  38. */

  39. public synchronized void proTest(String pic) {

  40. if (!flag) {

  41. try {

  42. this.wait();

  43. } catch (InterruptedException e) {

  44. e.printStackTrace();

  45. }

  46. }

  47. try {

  48. Thread.sleep(1000);

  49. } catch (InterruptedException e) {

  50. e.printStackTrace();

  51. }

  52. System.out.println("生产了:" + pic);

  53. this.pic = pic;

  54. this.notify();

  55. this.flag = false;

  56. }

  57. /**

  58. * 消费者消费,生产者等待,消费完通知生产

  59. */

  60. public synchronized void conTest() {

  61. if (flag) {

  62. try {

  63. this.wait();

  64. } catch (InterruptedException e) {

  65. e.printStackTrace();

  66. }

  67. }

  68. System.out.println("消费者:" + pic);

  69. this.notifyAll();

  70. this.flag = true;

  71. }

  72. }

  73. public class ClientTest {

  74. public static void main(String[] args) {

  75. Movie movie=new Movie();

  76. new Thread(new ProduceRu(movie)).start();

  77. new Thread(new ConsumeRu(movie)).start();

  78. }

  79. }

  80. 复制代码


六、任务调度


1.Thread 实现方法 这是最常见的,创建一个 thread,然后让它在 while 循环里一直运行着,通过 sleep 方法来达到定时任务的效果。这样可以快速简单的实现,代码如下:


 
复制代码


  1. public class ThreadTest {

  2. public static void main(String[] args) {

  3. Runnable runnable = new Runnable() {

  4. @Override

  5. public void run() {

  6. try {

  7. System.out.println("任务开始了");

  8. while (true) {

  9. Thread.sleep(1000);

  10. System.out.println("hello");

  11. } } catch (InterruptedException e) {

  12. e.printStackTrace(); } } }; Thread thread = new Thread(runnable);

  13. thread.start(); }}复制代码


2.TimeTask 实现方法 Thread 方法优点就是简单,但缺少了灵活性,TimeTask 实现方法最主要的两个优点是:可以控制启动和取消任务的时间、第一次执行任务时可以指定想要 delay 的时间。 实现的过程中 Timer 用于调度任务,TimeTask 用户具体的实现,是线程安全的,代码如下:


 
复制代码


  1. public class TimeTest {

  2. public static void main(String[] args) {

  3. Timer timer=new Timer();

  4. timer.schedule(new TimerTask() {

  5. @Override

  6. public void run() {

  7. System.out.println("任务运行开始.......");

  8. } },new Date(),1000);

  9. }}复制代码


3.ScheduledExecutorService 实现方法 ScheduledExecutorService 是从 Java java.util.concurrent 里 相比于上两个方法,它有以下好处: 相比于 Timer 的单线程,它是通过线程池的方式来执行任务的可以很灵活的去设定第一次执行任务 delay 时间 具体代码如下:


方法说明: ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) 创建并执行在给定的初始延迟之后首先启用的定期动作,随后在一个执行的终止和下一个执行的开始之间给定的延迟。 如果任务的执行遇到异常,则后续的执行被抑制。 否则,任务将仅通过取消或终止执行人终止。 参数 command - 要执行的任务 initialDelay - 延迟第一次执行的时间 (就是第一次指定定时延时多久),代码里我延时 10 秒 delay - 一个执行终止与下一个执行的开始之间的延迟 unit - initialDelay 和 delay 参数的时间单位


 
复制代码


  1. public class ScheduledExecutorServiceTest {

  2. public static void main(String[] args) {

  3. ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

  4. scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

  5. @Override

  6. public void run() {

  7. System.out.println("任务!!!!");

  8. } },10000,1000, TimeUnit.MILLISECONDS);

  9. }}复制代码


读者福利


对于大厂面试,我最后想要强调的一点就是心态真的很重要,是决定你在面试过程中发挥的关键,若不能正常发挥,很可能就因为一个小失误与 offer 失之交臂,所以一定要重视起来。另外提醒一点,充分复习,是消除你紧张的心理状态的关键,但你复习充分了,自然面试过程中就要有底气得多。


我这边为大家准备了近几年来各大厂的 Java 面试资料,均可以免费提供,希望大家金九银十面试顺利,拿下自己心仪的 offer!


如果你有需要的话,麻烦一键三连+评论,然后添加 VX(tkzl6666)即可免费领取






用户头像

添加我的微信:tkzl6666 获取文中资料 2020.09.19 加入

添加我的微信:tkzl6666 获取文中资料

评论 (1 条评论)

发布
用户头像
面试腾讯,字节跳动首先要掌握的 Java 多线程,一次帮你全掌握
2021 年 01 月 04 日 14:07
回复
没有更多了
面试腾讯,字节跳动首先要掌握的Java多线程,一次帮你全掌握