Java 多线程

命运对每个人都是一样的,不一样的是各自的努力和付出不同,付出的越多,努力的越多,得到的回报也越多,在你累的时候请看一下身边比你成功却还比你更努力的人,这样,你就会更有动力。

导读:本篇文章讲解 Java 多线程,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

多线程实现方式

Thread类

MyThread类继承了Thread类

 MyThread thread=  new MyThread1("窗口1");
 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);
        }

方法介绍

Java 多线程

线程生命周期

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过 新建(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();
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/143051.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!