Java 多线程

Java 多线程

进程和线程

什么是进程?

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 window 当前运行的进程(.exe文件的运行)。

什么是线程?

线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

什么是多线程?

多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行,也就是交替运行。多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行。

为什么多线程是必要的?

开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。

为什么提倡多线程而不是多进程?

线程就是轻量级进程,是程序执行的最小单位。使用多线程而不是用多进程去进行并发程序的设计,是因为线程间的切换和调度的成本远远小于进程。

创建多线程

1. 继承Thread类

public class ThreadCreateDemo{
public static void main(String[] args){
Mythread thread = new MyThread();
thread.start(); // 该方法调用多次,出现IllegalThreadStateException
}
}

class MyThread extends Thread{
@Override
public void run(){
super.run();
System.out.println("通过继承Thread创建的线程!");
}
}

2.实现Runnable接口(推荐)

public class ThreadCreateDemo {
public static void main(String[] args) {
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}

class MyRunnable implements Runnable{
public void run(){
System.out.println("通过Runnable创建的线程!")
}
}

上述两种创建方式,工作时性质一样。但是建议使用实现Runable接口方式。解决单继承的局限性。

实例变量和线程安全

不共享数据的情况下

public class MyThread extends Thread {

private int count = 5;

public MyThread(String name) {
super();
this.setName(name);
}

@Override
public void run() {
super.run();
while (count > 0) {
count--;
System.out.println("由 " + MyThread.currentThread().getName() + " 计算,count=" + count);
}
}

public static void main(String[] args) {
MyThread a = new MyThread("A");
MyThread b = new MyThread("B");
MyThread c = new MyThread("C");
a.start();
b.start();
c.start();
}
}

结果:

在这里插入图片描述

可以看出每个线程都有一个属于自己的实例变量count,它们之间互不影响。我们再来看看另一种情况。

共享数据的情况

public class SharedVariableThread extends Thread {
private int count = 5;

@Override
public void run() {
super.run();
count--;
System.out.println("由 " + SharedVariableThread.currentThread().getName() + " 计算,count=" + count);
}

public static void main(String[] args) {

SharedVariableThread mythread = new SharedVariableThread();
// 下列线程都是通过mythread对象创建的
Thread a = new Thread(mythread, "A");
Thread b = new Thread(mythread, "B");
Thread c = new Thread(mythread, "C");
Thread d = new Thread(mythread, "D");
Thread e = new Thread(mythread, "E");
a.start();
b.start();
c.start();
d.start();
e.start();
}
}

结果:

在这里插入图片描述

可以看出这里已经出现了错误。

如何解决

利用 synchronized 关键字(保证任意时刻只能有一个线程执行该方法)

同步锁(synchronized)

synchronized修饰实例方法

这样的含义是锁住当前实例对象,多个线程运行该方法,只有一个线程能够获得该实例的锁,执行方法。

public synchronized void function(){
System.out.println("function同步锁中");
}

synchronized修饰静态方法

这样的含义是锁住当前类对象,此时与创建的对象无关,只有一个线程能够该类对象的锁。

public static synchronized void staticFunction(){
System.out.println("staticFunction同步锁中");
}

synchronized锁住代码块

对指定的对象加锁,那么多个线程中,只有一个线程能够获得该对象的锁。

Object obj = new Object();
synchronized(obj){
System.out.println("我在obj对象锁中");
}

停止线程

终止正在运行的线程方法有三种:

  1. 使用退出表示,是线程正常的执行完run方法终止.
public class ThreadVariableStopDemo {
public static void main(String[] args) throws InterruptedException {
VariableStopThread thread = new VariableStopThread("thread_1");
thread.start();
Thread.sleep(10);
thread.interrupt();
}
}

class VariableStopThread extends Thread {

public VariableStopThread(String name) {
super(name);
}

public void run() {
System.out.println(Thread.currentThread().getName() + ":线程开始运行!");
while(!isInterrupted()) {
System.out.println("" + (i++));
}
System.out.println("我停止了! timer:" + System.currentTimeMillis());
}
}

结果:

img

  1. 使用interrupt方法,使线程异常,线程进行捕获或抛异常,正常执行完run方法终止.
public class ThreadInterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new InterruptThread("thread_1");
thread.start();
Thread.sleep(1);
System.out.println(thread.getName() + "线程设置:interrupt");
thread.interrupt();
}
}

class InterruptThread extends Thread {

public InterruptThread(String name) {
super(name);
}

@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始!");
for(int i =0; i < 1000; i++) {
try {
Thread.sleep(0);
System.out.println("" + (i + 1));
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "线程捕获异常,退出循环!");
break;
}
}
System.out.println(Thread.currentThread().getName() + "线程结束!");
}
}

结果:

img

线程优先级

线程优先级范围为1-10,API提供等级分为:

低(MIN_PRIORITY = 1),

中(NORM_PRIORITY=5),

高(MAX_PRIORITY=10)。

public class ThreadPriorityDemo {
public static void main(String[] args) {
Thread thread = new ThreadPriority("thread_1<<<<");
Thread thread_1 = new ThreadPriority(">>>thread_2");
thread_1.setPriority(Thread.MIN_PRIORITY); //<设置线程优先级
thread.setPriority(Thread.MAX_PRIORITY);
thread_1.start();
thread.start();
}
}

class ThreadPriority extends Thread {
public ThreadPriority(String name) {
super(name);
}

@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("" + Thread.currentThread().getName() + ",number:" + i + ",Priority:" + Thread.currentThread().getPriority());
}
}
}

结果:

img

运行的很给力,以下体现了两个问题:①线程运行顺序与代码执行顺序无关。②线程优先级具有随机性,不是优先级高的就先完成。

守护线程

用户线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程

守护线程:运行在后台,为其他前台线程服务.也可以说守护线程是JVM中非守护线程的 “佣人”。

特点:一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作

应用:数据库连接池中的检测线程,JVM虚拟机启动后的检测线程

最常见的守护线程:垃圾回收线程

守护线程顾名思义是一个线程守护另一个线程【此线程为非守护线程】,故守护的线程称为守护线程,被守护的线程称为非守护线程。作用是为其他线程运行提供便利服务。

public class DaemonThreadDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new DaemonThread();
thread.setDaemon(true);
thread.start();
System.out.println("" + Thread.currentThread().getName() + "停止运行!" );
}
}

class DaemonThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("DaemonThread 正在运行!");
}
}
}

结果:

img

从上图可以看出,主线程停止DaemonThread线程也相应的停止了,但不是立即停止。

线程让步

线程让步【yield方法】让当前线程释放CPU资源,让其他线程抢占。

public class ThreadYieldDemo {
public static void main(String[] args) {
Thread thread = new ThreadYield();
thread.start();
}
}
class ThreadYield extends Thread {
@Override
public void run() {
long time_start = System.currentTimeMillis();
for(int i = 0; i < 500000; i++) {
Math.random();
// Thread.yield();
}
long time_end = System.currentTimeMillis();
System.out.println("用时:" + (time_end - time_start));
}
}

不让步:

img

让步:

img

从以上两图可以看出,线程的让步操作比不让步耗时长。

join()方法

Java对Thread的Join方法解释:等待当前线程终止。

public class TestJoin {

public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
ThreadTest t1=new ThreadTest("A");
ThreadTest t2=new ThreadTest("B");
t1.start();
t1.join();
t2.start();
}
}

class ThreadTest extends Thread {
private String name;
public ThreadTest(String name){
this.name=name;
}
public void run(){
for(int i=1;i<=5;i++){
System.out.println(name+"-"+i);
}
}
}

结果:

A-1
A-2
A-3
A-4
A-5
B-1
B-2
B-3
B-4
B-5

显然,使用t1.join()之后,B线程需要等A线程执行完毕之后才能执行。

sleep()方法

public class Multi extends Thread{
public void run() {
for(int i=1; i<1000; i++) {
try {
Thread.sleep(500);
}catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
public static void main(String[] args) {
Multi t1 = new Multi();
Multi t2 = new Multi();
t1.start();
t2.start();
}
}

结果:

t1: 1
t2: 1
t1: 2
t2: 2
t1: 3
……

这是因为Sleep()使得当前线程进入阻塞状态,系统便调用了另一线程,循环往复,便出现了上面的输出结果。

wait()和notify()

java的wait/notify的通知机制可以用来实现线程间通信。

wait表示线程的等待,调用该方法会导致线程阻塞,直至另一线程调用notify或notifyAll方法才可另其继续执行。

public class ThreadTest {

static final Object obj = new Object();

private static boolean flag = false;

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

Thread consume = new Thread(new Consume(), "Consume");
Thread produce = new Thread(new Produce(), "Produce");
consume.start();
Thread.sleep(1000);
produce.start();

try {
produce.join();
consume.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

// 生产者线程
static class Produce implements Runnable {

@Override
public void run() {

synchronized (obj) {
System.out.println("进入生产者线程");
System.out.println("生产");
try {
TimeUnit.MILLISECONDS.sleep(2000); //模拟生产过程
flag = true;
obj.notify(); //通知消费者
TimeUnit.MILLISECONDS.sleep(1000); //模拟其他耗时操作
System.out.println("退出生产者线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

//消费者线程
static class Consume implements Runnable {

@Override
public void run() {
synchronized (obj) {
System.out.println("进入消费者线程");
System.out.println("wait flag 1:" + flag);
while (!flag) { //判断条件是否满足,若不满足则等待
try {
System.out.println("还没生产,进入等待");
obj.wait();
System.out.println("结束等待");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("wait flag 2:" + flag);
System.out.println("消费");
System.out.println("退出消费者线程");
}

}
}
}

结果:

进入消费者线程

wait flag 1:false

还没生产,进入等待

进入生产者线程

生产

退出生产者线程

结束等待

wait flag 2:true

消费

退出消费者线程

在示例中没有体现但很重要的是,wait/notify方法的调用必须处在该对象的锁(Monitor)中,也即,在调用这些方法时首先需要获得该对象的锁。否则会抛出IllegalMonitorStateException异常。

从输出结果来看,在生产者调用notify()后,消费者并没有立即被唤醒,而是等到生产者退出同步块后才唤醒执行。(这点其实也好理解,synchronized同步方法(块)同一时刻只允许一个线程在里面,生产者不退出,消费者也进不去)

注意,消费者被唤醒后是从wait()方法(被阻塞的地方)后面执行,而不是重新从同步块开头。

线程的生命周期

img

  • 新建状态:

    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 就绪状态:

    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  • 运行状态:

    如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态:

    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
  • 死亡状态:

    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

参考

https://www.jianshu.com/p/d901b25e0d4a

https://blog.csdn.net/qq_34337272/article/details/79640870#__10

https://www.runoob.com/java/java-multithreading.html

https://blog.csdn.net/u013425438/article/details/80205693

https://www.cnblogs.com/hqinglau/p/10053564.html

https://blog.csdn.net/jianiuqi/article/details/53448849

https://blog.csdn.net/wthfeng/article/details/78762343

https://baijiahao.baidu.com/s?id=1630613830514012483&wfr=spider&for=pc

Author: pangzibo243
Link: https://litianbo243.github.io/2019/10/12/Java-多线程/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
支付宝打赏
微信打赏