Java基础--多线程

Updated on with 0 views and 0 comments

一、线程的状态

image

  • 创建(NEW) 创建但是未调用start方法
  • 可运行(RUNNABLE) 可能正在运行,也可能正在等待CPU时间片
  • 阻塞(BLOCKED) 等待获取一个锁
  • 等待(WAITING) 处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒
  • 超时等待(TIMED_WAITING) 处于这种状态的线程不会被分配CPU执行时间,无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒
  • 终止(TERMINATED) 线程运行结束

1.1 新建(NEW)

创建后尚未启动。

1.2 可运行(RUNNABLE)

可能正在运行,也可能正在等待 CPU 时间片。

包含了操作系统线程状态中的 Running 和 Ready。

1.3 阻塞(BLOCKED)

等待获取一个排它锁,如果其他线程释放了锁就会结束此状态

1.4 无限期等待(WAITING)

等待其它线程显式地唤醒,否则不会被分配 CPU 时间片

进入等待方法退出等待方法
Object.wait()Object.nofity或Object.nofityAll
Thread.join()被调用的线程执行完毕
LockSupport.park()方法

1.5 限期等待(TIME_WAITING)

阻塞是等待其他线程施放锁,是被动的,等待是线程主动的行为

进入等待方法退出等待方法
Object.wait(timeout)Object.notify或Object.notifyAll
Thread.join(millis)时间结束或者被调用的线程执行完
LockSupport.parkNanos()
LockSuppert.parkUntil()

1.6 死亡(TERMINATED)

线程执行完毕或因产生异常而结束

二、线程创建方式

  • 实现Runnable接口
  • 实现Callable接口
  • 继承Thread类

实现Runnable和Callable接口最后还是需要通过Thread来调用

2.1 实现Runnable接口

实现runnable接口,重写run方法

class RunTask implements Runnable{
    @Override
    public void run() {
        System.out.println("你好");
    }
}
    public static void main(String[] args){
        RunTask runTask = new RunTask();
        Thread thread = new Thread(runTask);
        thread.start();
    }

2.2 实现Callable接口

Callable接口可以有返回值,通过FuTureTask封装

class RunTask implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("你好");
        Thread.sleep(2000);
        return "ok ok";
    }
}
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        RunTask runTask = new RunTask();
        FutureTask<String> futureTask = new FutureTask<>(runTask);
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println(futureTask.get());
    }

2.3 继承Thread类

继承了Thread类后,还需要重写run方法

class RunTask extends Thread {
    @Override
    public void run(){
        System.out.println("111");
    }
}
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        RunTask runTask = new RunTask();
        runTask.start();
    }

2.4 实现接口和继承Thread

  • Java不允许多继承,如果要继承其他类,就用接口实现线程
  • 继承Thread类开销过大,而实现接口则只需要实现对应方法

三、线程池与守护线程

3.1 Executors创建线程池

  • CachedThreadPool 一个任务一个线程
  • FixedThreadPool 所有任务通过指定数量的线程运行
  • SingleThreadExecutor 所有任务通过一个线程执行,即大小为1的FixedThreadPool

3.2 Deamon守护线程

守护线程在主线程结束时,自己也会结束

通过Thread的setDeamon实现

class RunTask implements Runnable {
    @Override
    public void run(){
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("111");
        }
    }
}
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        RunTask runTask = new RunTask();
        Thread thread = new Thread(runTask);
        thread.setDaemon(true);
        thread.start();
        Thread.sleep(2000);
        System.out.println("主线程结束");
    }

四、线程中断

4.1 interrupt()

调用interrupt()方法来中断线程,如果线程处于阻塞、限期等待或者无限期等待,就会抛出InterruptException,从而提前结束线程,但是不能中断IO阻塞和synchronized锁阻塞

class RunTask implements Runnable {
    @Override
    public void run(){
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("111");
        }
    }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
        RunTask runTask = new RunTask();
        Thread thread = new Thread(runTask);
        thread.setDaemon(true);
        thread.start();
        thread.interrupt();
    }

代码执行就会报错 java.lang.InterruptedException: sleep interrupted

4.2 interrupted()

该方法可以判断当前线程是否被中断,可以在线程中调用这个方法,然后再主线程中调用interrupt()来中断线程

class RunTask extends Thread {
    @Override
    public void run(){
        while(!interrupted()){
            System.out.println("111");
        }
    }
}
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        RunTask runTask = new RunTask();
        Thread thread = new Thread(runTask);
        thread.setDaemon(true);
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }

4.3 Executors线程中断

Executors.shutdown()会等线程执行结束之后再关闭,但是如果调用shutdownNow()就会调用每个线程的interrupt()方法

在ThreadPoolExecotor中,shutdownNow方法回调用一个interruptWorkers方法,在该方法中,回去循环中断每个线程

image.png

image.png

image.png

五、线程互斥同步

5.1 synchronized

5.1.1 同步代码块

    public void func(){
        synchronized (this){
  
        }
    }

作用域同一个对象,如果调用两个对象上的同步代码块,就不会同步

定义一个同步代码块如下

class RunTask{
    public void func(){
        synchronized (this){
            for(int i=0;i<10;i++){
                System.out.println(i);
            }
        }
    }
}

在main方法中创建他的对象示例,并在线程池中运行

public class Info{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        RunTask rt = new RunTask();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> rt.func());
        executorService.execute(() -> rt.func());
    }
}

此时输出结果为0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9,可见两个线程存在同步关系

然后我们在建立两个示例对象在执行线程池

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        RunTask rt = new RunTask();
        RunTask rt1 = new RunTask();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> rt.func());
        executorService.execute(() -> rt1.func());
    }

此时输出结果为0 1 2 3 4 5 6 7 0 1 2 3 4 5 8 9 6 7 8 9,可见此时线程未同步

5.1.2 同步方法

作用跟synchronized同步代码块一致

    public synchronized void func(){
        // todo
    }

5.1.3 同步一个类

作用于整个类,若两个线程调用同一个类的不同对象实例,也会进行同步操作

class RunTask{
    public  void func(){
        synchronized (RunTask.class){
            for(int i=0;i<10;i++){
                System.out.print(i+" ");
            }
        }
    }
}
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        RunTask rt = new RunTask();
        RunTask rt1 = new RunTask();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> rt.func());
        executorService.execute(() -> rt1.func());
    }

输出结果为0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9

5.1.4 同步一个静态方法

与作用于类一致

    public synchronized static void func(){
        // todo
    }

5.2 ReentrantLock

ReenTrantLock是java.lang.concurrent中的锁

class RunTask{
    private  ReentrantLock lock = new ReentrantLock();
    public  void func(){
        try{
            lock.lock();
            for(int i=0;i<10;i++){
                System.out.print(i+" ");
            }
        }finally {
            lock.unlock();
        }
    }
}
public class Info{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        RunTask rt = new RunTask();
        RunTask rt1 = new RunTask();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> rt.func());
        executorService.execute(() -> rt1.func());
    }
}

输出结果0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9

5.3 比较

  • synchronized是jvm实现,ReentrantLock由jdk实现
  • 二者性能基本相同
  • ReentrantLock锁等待时可以中断,synchronized不行
  • synchronized中的锁是非公平锁,ReentrantLock默认也是非公平锁,但是可以设置为公平锁
  • 一个ReentrantLock可以绑定多个Condition对象

六、线程交互

6.1 join()

在一个线程中调用另一个线程的join方法,会把当前线程挂起,直到目标线程结束

6.2 wait()、nofity()、notifyAll()

wait() 使得线程等待某个条件满足,线程在等待时会被挂起,使用 wait() 挂起期间,线程会释放锁,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。

class WaitNotifyExample {
    static final Object lock = new Object();
    public synchronized void before() {
        synchronized(lock){
            System.out.println("before");
            try {
                Thread.sleep(1500);
                lock.notifyAll();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public  void after() {
        synchronized(lock){
            try {
                lock.wait();
                System.out.println("after");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
        ExecutorService executorService = Executors.newCachedThreadPool();
        WaitNotifyExample example = new WaitNotifyExample();
        executorService.execute(() -> example.after());
        executorService.execute(() -> example.before());
        executorService.shutdown();

执行结果为before after

6.3 wait() 和 sleep()

  • wait是Object的方法,sleep是Thread的方法
  • wait会施放锁,sleep不会

6.4 await()、signal()、signalAll()

java.utin.concurrent.Condition可以通过await使线程等待,其他线程调用signal和signalAll唤醒线程,await可以指定等待的条件

class AWaitSignalExample {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    public void before() {
        lock.lock();
        try{
            System.out.println("before");
            condition.signal();
        }finally {
            lock.unlock();
        }

    }

    public  void after() {
        lock.lock();
        try {
            condition.await();
            System.out.println("after");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        AWaitSignalExample example = new AWaitSignalExample();
        executorService.execute(() -> example.after());
        executorService.execute(() -> example.before());
        executorService.shutdown();
    }

运行结果before after

6.5 sleep()和yield()

  • Thread.sleep方法会休眠当前正在执行的线程,不会施放锁,且必须捕获InterruptedException异常
  • Thread.yield()会让出CPU执行权给相同或更高优先级的线程,当前线程和其他线程会来竞争CPU时间片,当前线程执行yield后,任然有可能获取CPU时间片

标题:Java基础--多线程
作者:wenyl
地址:http://www.wenyoulong.com/articles/2023/06/20/1687251896886.html