1. 创建线程的四种方式

在Java中,有四种常见的方式可以创建和启动线程:

  1. 继承Thread类

    • 创建一个类继承自Thread类,然后重写run()方法。调用start()方法启动线程。

    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Thread is running...");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            MyThread thread = new MyThread();
            thread.start();  // 启动线程
        }
    }
  1. 实现Runnable接口

    • 创建一个实现Runnable接口的类,然后实现run()方法。将Runnable对象传递给Thread对象,并调用start()方法启动线程。

    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Thread is running...");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Thread thread = new Thread(new MyRunnable());
            thread.start();  // 启动线程
        }
    }

在Java中,Callable接口是一个功能更强的接口,相比Runnable接口,它可以返回结果,并且可以抛出受检异常。要使用Callable接口创建并执行线程,通常与FutureExecutorService一起使用。以下是实现Callable接口的步骤:

  1. 实现Callable接口:

    • 创建一个类实现Callable接口,并实现call()方法。call()方法可以返回一个结果,并且允许抛出异常。

    import java.util.concurrent.Callable;
    
    class MyCallable implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            // 线程执行的任务
            int sum = 0;
            for (int i = 1; i <= 10; i++) {
                sum += i;
            }
            return sum; // 返回结果
        }
    }
  1. 使用ExecutorService执行Callable任务:

    • 使用ExecutorService来执行Callable任务,并获取返回的Future对象。通过Future对象的get()方法,可以获取call()方法的返回值。

    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class Main {
        public static void main(String[] args) {
            // 创建一个可重用固定线程池
            ExecutorService executor = Executors.newFixedThreadPool(1);
            
            // 提交Callable任务并获取Future对象
            Future<Integer> future = executor.submit(new MyCallable());
            
            try {
                // 获取线程执行的结果
                Integer result = future.get();
                System.out.println("Sum: " + result);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            } finally {
                // 关闭ExecutorService
                executor.shutdown();
            }
        }
    }

解释:

  • Callable接口:类似于Runnable,但Callablecall()方法允许返回一个结果或抛出异常。

  • ExecutorService:用于管理线程池的接口,submit()方法可以提交一个Callable任务。

  • Future对象:表示异步计算的结果,get()方法可以获取Callable的返回值。如果任务尚未完成,get()将阻塞直到任务完成。

使用场景:

Callable接口特别适用于需要在线程中执行一些计算或任务,并且希望获取结果的场景。它可以返回任意类型的结果,并且可以在执行任务时处理异常。

2. runnable callable 有什么区别

1. Runnable 接口run方法没有返回值;Callable接口call方法有返回值,是个泛

型,和Future、FutureTask配合可以用来获取异步执行的结果

2. Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻

塞主进程的继续往下执行,如果不调用不会阻塞。

3. Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常

只能在内部消化,不能继续上抛

3. 线程包括哪些状态,状态之间是如何变化的

在Java中,线程的生命周期包括以下几种状态,并且这些状态之间可以通过特定的事件或操作进行转换。这些状态和它们的转换过程如下:

  1. 新建状态(New)

    • 线程对象被创建,但还未调用start()方法。此时线程处于新建状态。

   Thread thread = new Thread(() -> {
       // 线程任务
   });
  1. 就绪状态(Runnable)

    • 当调用start()方法后,线程进入就绪状态。此时线程已经准备好等待CPU调度,但还未开始执行。

   thread.start();
  1. 运行状态(Running)

    • 当线程获得CPU时间片并开始执行其run()方法时,线程进入运行状态。在这期间,线程执行它的任务。

   public void run() {
       // 线程正在执行的任务
   }
  1. 阻塞状态(Blocked)

    • 当线程等待某个锁资源时(比如试图进入一个被其他线程占用的同步代码块),线程会进入阻塞状态。此时线程处于等待状态,无法执行。

   synchronized (lock) {
       // 线程需要锁资源才能执行的任务
   }
  1. 等待状态(Waiting)

    • 线程进入等待状态,通常是由于调用了Object.wait()方法、Thread.join()方法或LockSupport.park()方法,线程在等待其他线程显式唤醒。

   synchronized (lock) {
       lock.wait();  // 线程等待某个条件
   }
  1. 限时等待状态(Timed Waiting)

    • 线程在调用了有超时时间的等待方法时会进入限时等待状态。例如,调用Thread.sleep(long millis)Object.wait(long timeout)Thread.join(long millis)等方法。

   Thread.sleep(1000);  // 线程休眠1秒钟
  1. 终止状态(Terminated)

    • 当线程的run()方法执行完

绘制Java线程状态

stateDiagram-v2 [*] --> New : 创建线程 New --> Runnable : 调用 start() Runnable --> Running : 调度获取CPU时间片 Running --> Runnable : yield() 让出CPU时间片 Running --> Blocked : 等待获取锁资源 Blocked --> Runnable : 获得锁资源 Running --> Waiting : 调用 wait(), join(), LockSupport.park() Waiting --> Runnable : notify(), notifyAll(), join 线程结束 Running --> Timed_Waiting : sleep(), wait(timeout), join(timeout) Timed_Waiting --> Runnable : 超时或被唤醒 Running --> Terminated : run()结束或抛出异常 Waiting --> Timed_Waiting : wait(timeout), join(timeout)

New:表示线程被创建后的初始状态。

Runnable:表示线程就绪,等待CPU调度。

Running:表示线程正在运行。

Blocked:表示线程被阻塞,等待锁资源。

Waiting:表示线程处于等待状态,等待其他线程的通知。

Timed_Waiting:表示线程在有限时间内处于等待状态。

Terminated:表示线程已经终止。

4. 在 java wait sleep 方法的不同

共同点

  • wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态

不同点

  • 方法归属不同

sleep(long) 是 Thread 的静态方法,而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有

  • 醒来时机不同

执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去它们都可以被打断唤醒

  • 锁特性不同(重点)

wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制,wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃cpu,但你们还可以用)而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃cpu,你们也用不了)

下面是一个简单的Java代码示例,展示了wait()sleep()的使用方式及它们的不同之处。

  1. wait() 示例

这个示例展示了线程间的通信,一个线程在等待条件满足(通过wait()),而另一个线程在合适的时机通知它继续执行(通过notify())。

class WaitAndNotifyExample {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 1: Waiting for notification...");
                try {
                    lock.wait();  // 线程1等待,释放锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 1: Notified and resumed!");
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 2: Doing some work...");
                try {
                    Thread.sleep(2000);  // 模拟工作,睡眠2秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 2: Finished work, now notifying...");
                lock.notify();  // 通知等待的线程
            }
        });

        thread1.start();
        thread2.start();
    }
}

运行结果:

Thread 1: Waiting for notification...
Thread 2: Doing some work...
Thread 2: Finished work, now notifying...
Thread 1: Notified and resumed!

解释:

  • thread1:调用wait()方法,进入等待状态并释放锁,直到被通知。

  • thread2:在完成模拟的工作后调用notify()方法,通知thread1恢复执行。

  1. sleep() 示例

这个示例展示了线程在执行过程中暂停一段时间,然后继续执行。

class SleepExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("Thread: Doing some work...");
            try {
                Thread.sleep(3000);  // 线程睡眠3秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread: Woke up after sleeping!");
        });

        thread.start();
    }
}

运行结果:

Thread: Doing some work...
Thread: Woke up after sleeping!

解释:

  • thread:调用sleep(3000)方法,线程暂停3秒,然后继续执行其余的任务。 区别总结:

  • wait()示例中,线程thread1等待另一个线程thread2的通知(通过notify()),并在等待期间释放了锁。

  • sleep()示例中,线程只是暂停执行了一段时间,但并未释放任何锁。

Synchronized 关键字的底层原理

synchronized 底层使用的JVM级别中的Monitor 来决定当前线程是否获得了锁,如果某一个线程获得了锁,在没有释放锁之前,其他线程是不能或得到锁的。synchronized 属于悲观锁。

monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因