Java 多线程

简介: Java 多线程

多线程实现方式

Thread类

MyThread类继承了Thread类

1.  MyThread thread=  new MyThread1("窗口1");
2.  thread.start();

Runnable接口

自定义一个MyRunnable类来实现Runnable接口,在MyRunnable类中重写run()方法

,创建Thread对象,并把MyRunnable对象作为Tread类构造方法的参数传递进去。

        MyRunnable myrunnable = new MyRunnable();
        Thread thread = new Thread(myrunnable, "线程01");
        thread.start();

lambda转换为Runnable功能接口(interface)的子类的实例,然后将其传递给overloaded to take a Runnable object的Thread构造函数。

new Thread(() -> System.out.println("Lol")).start();

Callable接口

自定义一个MyThread类来实现Callable接口,在MyThread类中重写call()方法,创建FutureTask,Thread对象,并把MyCallable对象作为FutureTask类构造方法的参数传递进去,把FutureTask对象传递给Thread对象。可以获取到线程的执行结果。

public class MyThread implements Callable {
    @Override
    public Object call() throws Exception {
        return 100;
    }
}
        MyThread myThread=new MyThread();
        FutureTask futureTask=new FutureTask(myThread);
        Thread thread=new Thread(futureTask);
        thread.start();
        try {
            Object o = futureTask.get();
            System.out.println(o);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }

方法介绍


178fd314fb0a160e97395d8c527dbb56.png

线程生命周期

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过 新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。尤其是当线程启动以后,它不可能一直"霸占"着CPU独自运行,所以CPU需要在多条线程之间切换,于是 线程状态也会多次在运行、阻塞之间切换。


新建状态(NEW):线程已创建,尚未调用start()方法启动之前。

运行状态(RUNNABLE):线程对象被创建后,调用该对象的start()方法,并获取CPU权限进行执行。

阻塞状态(BLOCKED):线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。

等待状态(WAITING ):等待状态。正在等待另一个线程执行特定动作来唤醒该线程的状态。

超时等待状态(TIME_WAITING):有明确结束时间的等待状态。

终止状态(TERMINATED ):当线程结束完成之后就会变成此状态。


线程的操作方法


start:使该线程开始执行,Java虚拟机底层调用该线程的start0( )方法;


run:调用线程对象run方法。start底层会创建新的线程,run是一个简单的方法调用,不会启动新线程。


sleep:在指定的毫秒数内让当前正在执行的线程休眠;醒来后进入就绪状态。


interrupt:中断线程,但并没有真正结束线程,所以一般用于中断正在休眠线程。


yield:线程的礼让。yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。


join:线程的插队


守护线程

线程分为用户线程和守护线程。虚拟机必须确保用户线程执行完毕。虚拟机在非守护线程都结束时,守护线程会陆续结束。

线程同步

线程安全


什么是线程安全性

当多个线程访问某个类时,不管运行时环境采用 何种调度方式 或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类就是线程安全的。

线程安全性的三个体现

原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作(Atomic、CAS算法、synchronized、Lock)

可见性:一个主内存的线程如果进行了修改,可以及时被其他线程观察到(synchronized、volatile)

有序性:如果两个线程不能从 happens-before原则 观察出来,那么就不能观察他们的有序性,虚拟机可以随意的对他们进行重排序,导致其观察观察结果杂乱无序(happens-before原则)


线程同步机制

解决线程并发问题的方法是线程同步,线程同步就是让线程排队,就是操作共享资源要有先后顺序,一个线程操作完之后,另一个线程才能操作或者读取。

使用synchronized关键字,同步方法或者同步代码块。需要唯一的同步监视器。

买票问题


public class MyThread1 extends Thread{
    public MyThread1(String name) {
        super(name);
    }
    static int ticket=0;
    static Lock lock=new ReentrantLock();
    @Override
    public void run() {
       while (true){
           lock.lock();
           if(ticket==100)
               break;
           else {
               try {
                   Thread.sleep(10);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
               ticket++;
               System.out.println(getName()+"在卖第"+ticket+"张票");
               lock.unlock();
           }
       }
    }
}
        MyThread1 thread=  new MyThread1("窗口1");
        MyThread1 thread1=new MyThread1("窗口2");
        MyThread1 thread2=new MyThread1("窗口3");
        thread.start();
        thread1.start();
        thread2.start();

注:

Volatile关键字的作用主要有如下两个:

1.线程的可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。

2. 顺序一致性:禁止指令重排序。


死锁


多个线程各自占有一个资源,并且相互等待其他线程占有的资源才能运行,从而导致另个或者多个线程都在等待对方释放资源,都停止了执行。某一个同步代码块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。


Lock 锁也称同步锁,java.util.concurrent.locks.Lock 机制提供了⽐ synchronized 代码块和 synchronized ⽅法更⼴泛的锁定操作,同步代码块 / 同步⽅法具有的功能 Lock 都有,除此之外更强⼤,更体现⾯向对象。创建对象 Lock lock = new ReentrantLock() ,加锁与释放锁⽅法如下:

public void lock() :加同步锁

public void unlock() :释放同步锁


synchronized和Lock的对比:

Lock是显式锁(手动开启和关闭锁,别忘记关闭),synchronized是隐式锁,除了作用域就自动释放。

Lock只是代码块锁(执行体放在开启锁和关闭锁中间),synchronized有代码块锁和方法锁。

使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)。

优先使用顺序:Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)。


线程池

经常创建和销毁线程,消耗特别大的资源,比如并发的情况下的线程,对性能影响很大。线程池就是问题为了解决这个问题,提前创建好多个线程,放在线程池中,使用时直接获取,使用完放回线程池中,可以避免频繁的创建、销毁,实现重复利用。

       ExecutorService executorService = Executors.newCachedThreadPool();
        MyRunnable  myThread2=new MyRunnable();
        executorService.submit(myThread2);


阻塞队列

他也是队列的一种,那么他肯定是一个先进先出(FIFO)的数据结构。与普通队列不同的是,他支持两个附加操作,即阻塞添加阻塞删除方法。BlockingQueue是实现了Queue接口,一般使用两种如下。

ArrayBlockingQueue 由数组构成的有界阻塞队列


LinkedBlockingQueue 由链表构成的有界阻塞队列


阻塞添加:当阻塞队列是满时,往队列里添加元素的操作将被阻塞。


阻塞移除:当阻塞队列是空时,从队列中获取元素/删除元素的操作将被阻塞。


生产

add、offer、put这3个方法都是往队列尾部添加元素,区别如下:

add:不会阻塞,添加成功时返回true,不响应中断,当队列已满导致添加失败时抛出IllegalStateException。

offer:不会阻塞,添加成功时返回true,因队列已满导致添加失败时返回false,不响应中断。

put:会阻塞会响应中断。

消费

take、poll方法能获取队列头部第1个元素,区别如下:

take:会响应中断,会一直阻塞直到取得元素或当前线程中断。

poll:会响应中断,会阻塞,阻塞时间参照方法里参数timeout.timeUnit,当阻塞时间到了还没取得元素会返回null


综合练习(leetcode)

给你一个类:public class Foo {

public void first() { print("first"); }

public void second() { print("second"); }

public void third() { print("third"); }

}

三个不同的线程 A、B、C 将会共用一个 Foo 实例。


线程 A 将会调用 first() 方法

线程 B 将会调用 second() 方法

线程 C 将会调用 third() 方法

请设计修改程序,以确保 second() 方法在 first() 方法之后被执行,third() 方法在 second() 方法之后被执行。


输入:nums = [1,2,3]

输出:"firstsecondthird"

解释:

有三个线程会被异步启动。输入 [1,2,3] 表示线程 A 将会调用 first() 方法,线程 B 将会调用 second() 方法,线程 C 将会调用 third() 方法。正确的输出是 "firstsecondthird"。


引用leetcode精选代码

public class Foo {
    private volatile int flag = 1;
    //创建一把锁
    private final Object object = new Object();
    public Foo() {
    }
    public void first(Runnable printFirst) throws InterruptedException {
        synchronized (object) {
            while (flag != 1){
                //如果当前标志不是1,那就阻塞当前线程,释放锁,等待被唤醒再重新执行
                //被唤醒的线程需要重新竞争锁对象,获得锁的线程可以从wait处继续往下执行
                object.wait();
            } 
            printFirst.run();
            flag = 2;
            object.notifyAll();
        }
    }
    public void second(Runnable printSecond) throws InterruptedException {
        synchronized (object) {
            while (flag != 2){
                //如果当前标志不是2,那就阻塞当前线程,释放锁,等待被唤醒再重新执行
                //被唤醒的线程需要重新竞争锁对象,获得锁的线程可以从wait处继续往下执行
                object.wait();
            } 
            printSecond.run();
            flag = 3;
            object.notifyAll();
        }
    }
    public void third(Runnable printThird) throws InterruptedException {
        synchronized (object) {
            while (flag != 3){
                //如果当前标志不是3,那就阻塞当前线程,释放锁,等待被唤醒再重新执行
                //被唤醒的线程需要重新竞争锁对象,获得锁的线程可以从wait处继续往下执行
                object.wait();
            } 
        }
        printThird.run();
    }
}
相关文章
|
3天前
|
Java 测试技术
Java多线程的一些基本例子
【5月更文挑战第17天】Java多线程允许并发执行任务。示例1展示创建并启动两个`MyThread`对象,各自独立打印"Hello World"。示例2的`CounterExample`中,两个线程(IncrementThread和DecrementThread)同步地增加和减少共享计数器,确保最终计数为零。这些例子展示了Java线程的基本用法,包括线程同步,还有如Executor框架和线程池等更复杂的用例。
10 0
|
4天前
|
缓存 安全 Java
7张图带你轻松理解Java 线程安全,java缓存机制面试
7张图带你轻松理解Java 线程安全,java缓存机制面试
|
1天前
|
Java
Java一分钟之-并发编程:线程间通信(Phaser, CyclicBarrier, Semaphore)
【5月更文挑战第19天】Java并发编程中,Phaser、CyclicBarrier和Semaphore是三种强大的同步工具。Phaser用于阶段性任务协调,支持动态注册;CyclicBarrier允许线程同步执行,适合循环任务;Semaphore控制资源访问线程数,常用于限流和资源池管理。了解其使用场景、常见问题及避免策略,结合代码示例,能有效提升并发程序效率。注意异常处理和资源管理,以防止并发问题。
23 2
|
1天前
|
安全 Java 容器
Java一分钟之-并发编程:线程安全的集合类
【5月更文挑战第19天】Java提供线程安全集合类以解决并发环境中的数据一致性问题。例如,Vector是线程安全但效率低;可以使用Collections.synchronizedXxx将ArrayList或HashMap同步;ConcurrentHashMap是高效线程安全的映射;CopyOnWriteArrayList和CopyOnWriteArraySet适合读多写少场景;LinkedBlockingQueue是生产者-消费者模型中的线程安全队列。注意,过度同步可能影响性能,应尽量减少共享状态并利用并发工具类。
16 2
|
1天前
|
Java 程序员 调度
Java中的多线程编程:基础知识与实践
【5月更文挑战第19天】多线程编程是Java中的一个重要概念,它允许程序员在同一时间执行多个任务。本文将介绍Java多线程的基础知识,包括线程的创建、启动和管理,以及如何通过多线程提高程序的性能和响应性。
|
2天前
|
Java
深入理解Java并发编程:线程池的应用与优化
【5月更文挑战第18天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将了解线程池的基本概念,应用场景,以及如何优化线程池的性能。通过实例分析,我们将看到线程池如何提高系统性能,减少资源消耗,并提高系统的响应速度。
12 5
|
2天前
|
消息中间件 安全 Java
理解Java中的多线程编程
【5月更文挑战第18天】本文介绍了Java中的多线程编程,包括线程和多线程的基本概念。Java通过继承Thread类或实现Runnable接口来创建线程,此外还支持使用线程池(如ExecutorService和Executors)进行更高效的管理。多线程编程需要注意线程安全、性能优化和线程间通信,以避免数据竞争、死锁等问题,并确保程序高效运行。
|
2天前
|
存储 Java
【Java】实现一个简单的线程池
,如果被消耗完了就说明在规定时间内获取不到任务,直接return结束线程。
11 0
|
2天前
|
安全 Java 容器
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第18天】随着多核处理器的普及,并发编程变得越来越重要。Java提供了丰富的并发编程工具,如synchronized关键字、显式锁Lock、原子类、并发容器等。本文将深入探讨Java并发编程的核心概念,包括线程安全、死锁、资源竞争等,并分享一些性能优化的技巧。
|
2天前
|
安全 Java 开发者
Java中的多线程编程:理解与实践
【5月更文挑战第18天】在现代软件开发中,多线程编程是提高程序性能和响应速度的重要手段。Java作为一种广泛使用的编程语言,其内置的多线程支持使得开发者能够轻松地实现并行处理。本文将深入探讨Java多线程的基本概念、实现方式以及常见的并发问题,并通过实例代码演示如何高效地使用多线程技术。通过阅读本文,读者将对Java多线程编程有一个全面的认识,并能够在实际开发中灵活运用。