JAVA多线程介绍

1、什么是多线程

        得益于计算机的时间片机制,每一个应用程序的都可以在一段很小的时间段内执行。相比于单线程串行执行,得不到时间片就停止执行,多线程当中线程1得不到时间片,线程2有可能得到,可以更多的完成任务。

        还有一种场景,单线程要操作IO设备,但是IO设备一般都会有较多的等待,这段时间CPU处于空闲,空闲的CPU资源就被浪费了,如果能把资源让给另外一个需要CPU资源的线程,那就提高了任务完成的速率,让活干的更快。

        线程:拿微信举例,微信相当于一个进程,在微信启动后,向操作系统索要了一些资源的使用权限,线程相当于微信里面的打工人,这些打工人有很多岗位,比如收发消息岗、转账岗、抢红包岗等等,这些具体的工作,微信创建出来很多线程去干。怎么样让这些打打工人又快又好的干活呢,接着往下看。

2、JAVA多线程使用

2.1、创建线程

2.1.1、继承Thread类

        Thread类是JVM中用于管理线程的一个类,每一个线程都有唯一的一个Thread对象与之一一对应

package thread;

public class TestThread extends Thread {
    @Override
    public void run() {
        System.out.println("我是一个线程,我的名字是:"+Thread.currentThread().getName());
    }


    public static void main(String[] args) throws InterruptedException {
        TestThread testThread = new TestThread();
        testThread.start();
        testThread.join();
    }
}
2.1.1.1、Thread类及常见方法

         常见构造方法

方法 说明
Thread() 创建线程对象
Thread(Runnable target) 使用 Runnable 对象创建线程对象
Thread(String name) 创建线程对象,并命名
Thread(Runnable target, String name) 使用 Runnable 对象创建线程对象,并命名
Thread(ThreadGroup group, Runnable target) 线程可以被用来分组管理,分好的组即使线程组

        常见属性

属性 获取方法
ID getId()
名称 getName()
状态 getState()
优先级 getPriority()
是否后台线程 isDaemon()
是否存活 isAlive()
是否被中断 isInterrupted()

 2.1.2、实现Runnable函数式接口

自JAVA诞生,Runnable就已经存在了,如果一个线程类要实现 Runnable 接口,则这个类必须定义一个名为 run 的无参数方法。

实现了 Runnable 接口的类可以通过实例化一个 Thread 实例,并将自身作为目标传递来运行。

Runnable定义: 

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Runnable使用举例: 

package thread;

public class TestThread implements Runnable {

    @Override
    public void run() {
        System.out.println("我是一个线程,我的名字是:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) throws InterruptedException {
        TestThread testThread = new TestThread();
        Thread thread = new Thread(testThread);
        thread.start();
        thread.join();

    }
}

函数式写法,这是JDK1.8之后,对Runnable加上了@FunctionalInterface函数式接口的修饰,可以实现函数式编程,也叫Lambda表达式,想了解Lambda表达式可以参考Lambda、函数式接口、Stream 一次性全给你

package thread;

public class TestThread  {

    public static void main(String[] args) {
        
        Runnable runnable = ()->{
            System.out.println("我是一个线程,我的名字是" + Thread.currentThread().getName());
        };

        Thread thread = new Thread(runnable);
        thread.start();
        
    }
}

注意,是调用新 new 出来的 Thread 实例的start() 方法,不要调用run方法,虽然我们是重写Runnable的 run方法的。调用 run方法并没有创建线程的效果,而是直接在当前线程执行,就和执行一个普通类的普通方法一模一样。

为什么要调用 start()方法呢,我们看看 Thread的 start()方法实现中,其实是调用了一个名称为 start0()的 native 方法,native 方法就不是用 Java 实现的了,而是在 JVM 层面的实现。

这个start0方法的主要逻辑就是启动一个操作系统线程,并和 JVM 线程绑定,开辟一些空间来存储线程状态和上下文的数据,然后执行绑定的 JVM 线程(也就是我们实现了Runnable的类)的 run方法的代码块,从而执行我们自定义的逻辑。

其实Runnable并不是很完美,其不能返回值,不能抛出异常,所以有了Callable,来实现这些功能。

2.1.3、实现Callable函数式接口

Callable定义

@FunctionalInterface
public interface Callable {
    V call() throws Exception;
}

Callable使用举例: 

package thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

public class TestCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable callable = () -> {
            int sum = 0;
            for (int i = 0; i < 5; ++i) {
                sum += i;
                //TimeUnit.SECONDS.sleep(1);
                throw new Exception("业务出现异常");
            }
            return sum;
        };

        FutureTask integerFutureTask = new FutureTask<>(callable);
        Thread thread = new Thread(integerFutureTask);
        thread.start();
        System.out.println("我是main线程");
        Integer result = null;
        try {
            result = integerFutureTask.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }catch (Exception e) {
            System.out.println("处理异常:"+e.getMessage());
            e.printStackTrace();
        }
        System.out.println("最终运行结果为:"+result);
    }
}

 2.1.4、Thread、Runnable、Callable之间的区别

        本质上,Runnable和Callable只是Thread要执行的任务类,并且Runnable和Callable是函数式接口。在java中一个类只能继承一个父类,可以实现多个接口,Runnable和Callable方式的好处是可以规避类的单继承的限制;

        Runnable和Callable之间的区别:

                Callable实现的是call()方法,Runnable实现的是run()方法

                Callable可以通过FutureTask对象获取返回值,而Runnable则没有返回值

                Callable可以抛出异常,而Runnable不支持

                Callable要搭配FutureTask一起使用

2.2、线程控制

2.2.1、中断一个线程

常见的有以下两种方式:

        1、通过共享的标记来沟通

        2、调用interrupe()方法来通知

示例1:

package thread;

public class ThreadDemo {
    private static class MyRunnable implements Runnable {
        public volatile boolean isQuit = false;
        @Override
        public void run() {
            while (!isQuit) {
                System.out.println(Thread.currentThread().getName()
                        + ": 别管我,我忙着转账呢!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()
                    + ": 啊!险些误了大事");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target, "李四");
        System.out.println(Thread.currentThread().getName()
                + ": 让李四开始转账。");
        thread.start();
        Thread.sleep(10 * 1000);
        System.out.println(Thread.currentThread().getName()
                + ": 老板来电话了,得赶紧通知李四对方是个骗子!");
        target.isQuit = true;
    }
}

示例2:

package thread;


public class ThreadDemo {

    private static class MyRunnable implements Runnable {

        @Override
        public void run() {
// 两种方法均可以
//            while (!Thread.interrupted()) {
while (!Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName()
                        + ": 别管我,我忙着转账呢!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println(Thread.currentThread().getName()
                            + ": 有内鬼,终止交易!");
                    break;
                }
            }
            System.out.println(Thread.currentThread().getName()
                    + ": 啊!险些误了大事");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target, "李四");
        System.out.println(Thread.currentThread().getName()
                + ": 让李四开始转账。");
        thread.start();
        Thread.sleep(10 * 1000);
        System.out.println(Thread.currentThread().getName()
                + ": 老板来电话了,得赶紧通知李四对方是个骗子!");
        thread.interrupt();
    }

}

 重点说明下第二种方法:

        1、通过 thread 对象调用 interrupt() 方法通知该线程停止运行

        2、thread 收到通知的方式有两种:

                1、如果线程调用了wait()/join()/sleep()方法而阻塞挂起,则以InterruptedException异常的方式通知,清楚中断标志。

                2、否则,只是内部的中断标识被设置,需要手动判断,在判断上也有区别;thread.interrupted()判断当前线程的中断标志,清除中断标志;thread.currentThread().isInterruptd(),判断指定线程的中断标识是否被限制,不清除中断标志。

方法 说明
public void interrupt()  中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位
public static boolean interrupted() 判断当前线程的中断标志位是否设置,调用后清除标志位
public boolean isInterrupted() 判断对象关联的线程的标志位是否设置,调用后不清除标志位

2.2.2、等待一个线程join

package thread;

import java.util.concurrent.atomic.AtomicInteger;

public class TestJoin {
    public static void main(String[] args) throws InterruptedException {
        //原子整型
        AtomicInteger atomicInteger = new AtomicInteger();
        Thread thread = new Thread(() -> {
            //五万次自增操作
            for (int i = 0; i < 50000; i++) {
                atomicInteger.getAndIncrement();

            }
        });
        Thread thread2 = new Thread(() -> {
            //五万次自增操作
            for (int i = 0; i < 50000; i++) {
                atomicInteger.getAndIncrement();

            }
        });
        //启动线程
        thread.start();
        thread2.start();
        //等待两个线程执行完成
        thread.join();
        thread2.join();
        System.out.println(atomicInteger);
    }

}

想要得到线程的执行结果,就要等待线程执行完毕,join方法可以等待线程执行完毕,这里值得说明的是:Java 中的多线程代码不会随着主线程的退出而退出,在JAVA中,所有的线程都是通过线程对象来创建和管理的,点一个JAVA应用程序启动时,至少有一个主线程在运行,当主线程启动并创建其他线程时,这些线程就成了独立的执行流,可以在主线程退出之后,继续执行,每个线程都有自己的生命周期,他们独立于主线程的运行状态。

        与 Java 不同,C++ 中的多线程代码会随着主线程的退出而退出,这是因为 C++ 标准库对线程的处理方式与 Java 不同。C++ 的线程是基于操作系统的线程模型实现的,而不是像 Java 那样由虚拟机管理。当主线程退出时,C++ 运行时库会通知操作系统关闭所有线程。主线程退出意味着整个进程退出,所有线程都会被终止。

需要注意的是,无论是在 Java 还是在 C++ 中,如果某个线程持有资源(如文件句柄、数据库连接等),而其他线程仍在使用这些资源,那么这些资源并不会自动释放。这需要开发者在编写代码时正确管理资源,并确保适当地关闭或释放资源。

方法 说明
public void join() 等待线程结束
public void join(long millis) 等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos) 同理,但可以更高精度

2.2.3、休眠当前线程

2.2.3.1、sleep
方法 说明
public static void sleep(long millis) throws InterruptedException 休眠当前线程 millis 毫秒
public static void sleep(long millis, int nanos) throws InterruptedException 可以更高精度的休眠
    public class ThreadDemo {
        public static void main(String[] args) throws InterruptedException {
            System.out.println(System.currentTimeMillis());
            Thread.sleep(3 * 1000);
            System.out.println(System.currentTimeMillis());
        }
    }
2.2.3.2、wait 

wait方法使当前线程停止运行。

1、wait()方法是Object类的方法,该方法是将当前线程放入到线程就绪队列中,在wait()方法之后停止运行,直到接到通知或者被中断为止。

2、wait()方法只能在同步方法中或同步代码块中调用,如果调用是没有适当的锁,或抛出异常。

3、wait()方法调用后,当前线程释放锁,知道其他线程调用此线程对象的notify()方法或是notifyAll()方法,当前线程被唤醒进入就绪状态。

4、wait((long timeout))让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或 notifyAll() 方法, 或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。

2.2.3.3、notify方法

使被wait的线程继续运行。

1、方法notify()也要在同步方法或者是同步代码块中被调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。如果有多个线程等待,则有线程规划器随机挑选出一个呈wait状态的线程。

2、在执行完notify()方法之后,当前线程不会马上释放锁,而是要等待当前线程执行完notify()之后的代码,也就是退出同步代码块之后再释放锁。

package thread;

public class TestNotify implements Runnable {
    private boolean flag;
    private Object obj;
    public TestNotify(boolean flag, Object obj) {
        super();
        this.flag = flag;
        this.obj = obj;
    }
    public void waitMethod() {
        synchronized (obj) {
            try {
                while (true) {
                    System.out.println("wait()方法开始.. " +
                            Thread.currentThread().getName());
                    obj.wait();
                    System.out.println("wait()方法结束.. " +
                            Thread.currentThread().getName());
                    return;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public void notifyMethod() {
        synchronized (obj) {
            try {
                System.out.println("notify()方法开始.. " + Thread.currentThread().getName());
                obj.notify();
                System.out.println("notify()方法结束.. " + Thread.currentThread().getName());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    @Override
    public void run() {
        if (flag) {
            this.waitMethod();
        } else {
            this.notifyMethod();
        }
    }


    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        TestNotify waitThread = new TestNotify(true, object);
        TestNotify notifyThread = new TestNotify(false, object);
        Thread thread1 = new Thread(waitThread, "wait线程");
        Thread thread2 = new Thread(notifyThread, "notify线程");
        thread1.start();
        Thread.sleep(1000);
        thread2.start();
        System.out.println("main方法结束!!");
    }
}
2.2.3.4、notifyAll方法

 此方法会唤醒所有的在等待中的线程,使其正常执行

package thread;
class TestNotifyAll implements Runnable {
    private boolean flag;
    private Object obj;
    public TestNotifyAll(boolean flag, Object obj) {
        super();
        this.flag = flag;
        this.obj = obj;
    }
    public void waitMethod() {
        synchronized (obj) {
            try {
                while (true) {
                    System.out.println("wait()方法开始.. " +
                            Thread.currentThread().getName());
                    obj.wait();
                    System.out.println("wait()方法结束.. " +
                            Thread.currentThread().getName());
                    return;
                }
            } catch (Exception e) {e.printStackTrace();
            }
        }
    }
    public void notifyMethod() {
        synchronized (obj) {
            try {
                System.out.println("notifyAll()方法开始.. " +
                        Thread.currentThread().getName());
                obj.notifyAll();
                System.out.println("notifyAll()方法结束.. " +
                        Thread.currentThread().getName());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    @Override
    public void run() {
        if (flag) {
            this.waitMethod();
        } else {
            this.notifyMethod();
        }
    }


    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        TestNotifyAll waitThread1 = new TestNotifyAll(true, object);
        TestNotifyAll waitThread2 = new TestNotifyAll(true, object);
        TestNotifyAll waitThread3 = new TestNotifyAll(true, object);
        TestNotifyAll notifyThread = new TestNotifyAll(false, object);
        Thread thread1 = new Thread(waitThread1, "wait线程A");
        Thread thread2 = new Thread(waitThread2, "wait线程B");
        Thread thread3 = new Thread(waitThread3, "wait线程C");
        Thread thread4 = new Thread(notifyThread, "notify线程");
        thread1.start();
        thread2.start();
        thread3.start();
        Thread.sleep(1000);
        thread4.start();
        System.out.println("main方法结束!!");
    }
}

2.3、线程状态

线程一共有6种状态。

JAVA多线程介绍_第1张图片

 3、线程间同步

线程间同步的含义是线程间对临界资源操作的合理性;那生产者和消费者模型举个例子,生产者生产三个资源,消费者就能消费三个资源,当资源不足时消费者要通知生产者生产资源消费者等待,生产完资源后生产者要通知消费者消费,生产者等待。

3.1、Semaphore信号量

信号量:表示可用资源的数量,本质上就是一个计数器,比如停车场有100个车位,那么就只有100个资源,停进去一辆资源-1,出来一辆资源+1,当资源为0的时候,想进停车场就得等待

方法 说明

semaphore.acquire()

对资源-1

semaphore.release()

对资源+1
package thread;

import java.util.concurrent.Semaphore;

public class TestSemaphore {
    //信号量

    public static Semaphore semaphore = new Semaphore(3);

    public void testSemaphoreFunc()
    {
        System.out.println("我的名字是:"+Thread.currentThread().getName());
        try {
            semaphore.acquire();
            System.out.println("成功获取到一个资源,我的名字是:"+ Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("资源使用完毕,我的名字是:"+ Thread.currentThread().getName());
        semaphore.release();
    }

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

        TestSemaphore testSemaphore = new TestSemaphore();

        for (int i = 0; i < 10; ++i) {
            Thread thread = new Thread(() -> testSemaphore.testSemaphoreFunc());

            thread.start();
            thread.join();
        }

        //定义一个任务
        Runnable runnable = new Runnable() {

            @Override
            public void run() {
                System.out.println("我的ID是:"+Thread.currentThread().getName());
                try {
                    semaphore.acquire();
                    System.out.println("成功获取到一个资源,我的ID是:"+ Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("资源使用完毕,我的ID是:"+ Thread.currentThread().getName());
                semaphore.release();
            }
        };

        for (int i = 0; i < 10; ++i) {
            Thread thread = new Thread(runnable);
            thread.start();
            thread.join();

        }


    }
}

3.2、volatile关键字

修饰的共享变量,可以保证可见性。

可见性的含义:为了提高效率,JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,共享变量在多线程之间不能及时看到改变。

JAVA多线程介绍_第2张图片

将共享变量用volatile修饰,线程对变量做了改变就能及时刷新进主内存中,保证变量可见性。 

3.3、条件变量

java中没有与C++中条件变量一样的工具,但是 Java 提供了等待/通知机制来实现类似的功能。这个机制是通过线程对象的 wait()notify()notifyAll() 方法来实现的。

4、线程间互斥

参考另一篇文章,待续。 

你可能感兴趣的:(JAVA,java,开发语言,软件测试)