美文网首页
java多线程基础

java多线程基础

作者: 437760b195ab | 来源:发表于2020-09-11 23:56 被阅读0次

进程与线程

  • 进程是系统中正在运行的一个程序
  • 线程是操作系统能够进行运算调度的最小单位

线程和进程有什么区别

  • 线程是进程的子集,一个进程可以有很多线程
  • 每个进程都有自己的内存空间,可执行代码和唯一进程标识符
  • 不同的进程使用不同的内存空间(进程自己的堆栈),而所有的线程共享一片相同的内存空间

Java如何创建线程

  1. 继承Thread类
  2. 实现Runnable接口并重写run()方法
  3. 实现Callable接口,重写call(),利用FutureTask包装Callable,并作为task传入Thread构造函数
  4. 利用线程池

继承Thread类

package javaThread;

class FirstThread extends Thread {
    public void run() {
        System.out.println("First thread");
    }
}

public class Main {

    public static void main(String[] args) {
        Thread t1 = new FirstThread();
        t1.start();
    }
}

实现Runnable接口并重写run()方法

package javaThread;

class SecondThread implements Runnable {

    @Override
    public void run() {
        System.out.println("Second thread");
    }
}

public class Main {

    public static void main(String[] args) {
        Thread t2 = new Thread(new SecondThread());
        t2.start();
    }
}

实现Callable接口,重写call(),利用FutureTask包装Callable,并作为task传入Thread构造函数

package javaThread;

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

public class ThirdThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1:使用FutureTask + runnable
        FutureTask<String> runnableTask = new FutureTask<String>(new Runnable() {
            @Override
            public void run() {
                int r = 0;
                for (int i = 0; i < 100; i++) {
                    r += I;
                }
            }
        }, "runnable task complete");
        Thread t1 = new Thread(runnableTask);
        t1.start();
        String result1 = runnableTask.get();
        // 获取runnable task完成后通知,返回自己传入的值(runnable task complete)
        System.out.println(result1);

        // 2:使用FutureTask + callable
        FutureTask<Integer> callableTask = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int r = 0;
                for (int i = 0; i < 100; i++) {
                    r += I;
                }
                return r;
            }
        });
        Thread t2 = new Thread(callableTask);
        t2.start();
        // 获取callable task的返回值,返回计算的结果(4950)
        int result2 = callableTask.get();
        System.out.println("callable task complete, result is:" + result2);
    }
}

利用线程池

package javaThread;

import java.util.concurrent.*;

public class FourthThread {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(20));
        // 1:向线程池提交一个runnable任务
        Future<String> f = pool.submit(new Runnable() {
            @Override
            public void run() {
                int r = 0;
                for (int i = 0; i < 100; i++) {
                    r += I;
                }
            }
        }, "runnable task complete");
        // 获取runnable任务完成后通知,返回自己传入的值(runnable task complete)
        System.out.println(f.get());

        // 2:向线程池提交一个callable任务
        Future<Integer> t = pool.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int r = 0;
                for (int i = 0; i < 100; i++) {
                    r += I;
                }
                return r;
            }
        });

        // 获取callable任务的返回值,返回计算的结果(4950)
        int result = (int) t.get();
        System.out.println("callable task complete, result is:" + result);
        pool.shutdown(); // 关闭线程池
    }
}

启动线程

  • 调用线程的对象的start()方法

start()和run()区别

  • start()方法被用来启动新创建的线程,内部调用了run()方法
  • 调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程

线程协作通信的方式(阻塞和唤醒)

  • suspend/resume(已经被弃用)
  • wait/notify
  • park/unpark

suspend/resume

线程被挂起以后不会释放锁,有顺序调用的问题.容易死锁

  1. 情况1:suspend挂起后没有释放锁
package threadCommunication;

/**
 * 死锁情况1
 * suspend挂起的时候不会释放锁
 * 当线程suspend的时候持有锁,而resume的时候需要同一把锁的情况下,
 * 导致resume无法获取锁,从而无法唤醒线程,产生死锁
 */
public class SuspendResume {
    public static Object cake = null;

    public void suspendResumeDeadLock() throws InterruptedException {
        Thread consumerThread = new Thread(() -> {
            while (cake == null) { //使用while防止伪唤醒,而不是if
                System.out.println("1、没有蛋糕了,消费者进入等待");
                // 当前线程拿到锁,然后挂起
                synchronized (this) {
                    Thread.currentThread().suspend();
                }
            }
            System.out.println("2、买到蛋糕,回家");
        });
        consumerThread.start();

        // 2秒后生产一个蛋糕
        Thread.sleep(2000L);
        cake = new Object();
        // 争取到锁以后,再恢复consumerThread
        synchronized (this) {
            consumerThread.resume();
        }
        System.out.println("3、通知消费者");
    }

    public static void main(String[] args) throws InterruptedException {
        new SuspendResume().deadLock();
    }
}
  1. 情况2: resume比suspend先执行
package threadCommunication;

/**
 * 死锁情况2
 * 如果线程在suspend之前执行了一个耗时操作,导致程序先执行了resume然后再执行suspend。
 * 那么这个suspend后面将没有对应的resume将它唤醒,程序将一直处于挂起状态
 */
public class SuspendResume {
    public static Object cake = null;

    public void deadLock() throws InterruptedException {
        Thread consumerThread = new Thread(() -> {
            while (cake == null) {  //使用while防止伪唤醒,而不是if
                System.out.println("1、没有蛋糕了,消费者进入等待");
                // 模拟一个耗时操作
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Thread.currentThread().suspend();
            }
            System.out.println("2、买到蛋糕,回家");
        });
        consumerThread.start()

        // 2秒后生产一个蛋糕
        Thread.sleep(2000L);
        cake = new Object();
        Thread.currentThread().resume();
        System.out.println("3、通知消费者");
    }

    public static void main(String[] args) throws InterruptedException {
        new SuspendResume().deadLock();
    }
}

wait/notify

wait方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前对象持有的锁
notify/notifyAll唤醒一个或者所有等待这个对象锁的线程
必须在同步代码块里调用
wait自动解锁但是对调用顺序由要求,如果在notify调用之后才调用wait,线程将永远处于WAITING状态,导致死锁

package threadCommunication;

/**
 * notify调用之后才调用wait,线程永远处于WAITING状态.导致死锁
 */
public class WaitNotify {
    public static Object cake = null;

    public void deadLock() throws InterruptedException {
        new Thread(() -> {
            while (cake == null) {  //使用while防止伪唤醒,而不是if
                // 模拟一个耗时操作
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (this) {
                    System.out.println("1、没有蛋糕了,消费者进入等待");
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("2、买到蛋糕,回家");
        }).start();

        // 2秒后生产一个蛋糕
        Thread.sleep(2000L);
        cake = new Object();
        synchronized (this) {
            this.notifyAll();
            System.out.println("3、通知消费者");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new WaitNotify().deadLock();
    }
}

park/unpark

许可机制,不会释放锁
park方法等待“许可”,许可存在则运行
unpark方法为指定的线程提供“许可”
不要求park和unpark方法的调用顺序.
多次调用unpark之后,再调用park,线程会直接运行,但不会叠加.

package threadCommunication;

import java.util.concurrent.locks.LockSupport;

/**
 * park/unpark死锁
 * 
 */
public class ParkUnpark {

    public static Object cake = null;

    public void deadLock() throws InterruptedException {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            while (cake == null) {  //使用while防止伪唤醒,而不是if
                System.out.println("1、没有蛋糕了,消费者进入等待");
                // 当前线程拿到锁,然后挂起
                synchronized (this) {
                    LockSupport.park();
                }
            }
            System.out.println("2、买到蛋糕,回家");
        });
        consumerThread.start();
        // 2秒之后,生产一个蛋糕
        Thread.sleep(2000L);
        cake = new Object();
        // 争取到锁以后,再恢复consumerThread
        synchronized (this) {
            LockSupport.unpark(consumerThread);
        }
        System.out.println("3、通知消费者");
    }

    public static void main(String[] args) throws InterruptedException {
        new ParkUnpark().deadLock();
    }
}

三种方式的比较

方式 是否释放锁 是否有顺序要求
suspend/resume 不释放
wait/notify 释放
park/unpark 不释放 没有

线程中断

中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作
使用interrupt()方法中断线程
当阻塞方法收到中断请求的时候就会抛出InterruptedException异常.
方法在抛出InterruptedException之前,Java虚拟机会先将该线程的中断标识位 清除,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false

interrupt()和isInterrupted()

  • interrupt中断目标线程,interrupt并不意味着必然停止目标线程正在进行的工作,它仅仅是传递了请求中断的消息,线程自己会在下一个方便的时刻中断
  • isInterrupted()判断线程是否中断, 它会清除当前线程的中断状态,并返回之前的值

结束线程

  1. 使用stop()强行终止线程(被弃用,有线程安全问题)
  2. 使用interrupt()中断线程
  3. 使用退出标志使线程正常退出

使用stop()强行终止线程

package javaThread;

/**
 * stop()终止线程,线程安全性问题
 * 
 */
class StopThread extends Thread {
    private int i = 0, j = 0;

    @Override
    public void run() {
        synchronized (this) {
            // 增加同步锁,确保线程安全
            ++i;
            try {
                // 休眠10秒,模拟耗时操作
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ++j;
        }
    }

    /**
     * 打印i和j
     */
    public void print() {
        System.out.println("i=" + i + " j=" + j);
    }
}

public class StopThreadTest {
    public static void main(String[] args) throws InterruptedException {
        StopThread thread = new StopThread();
        thread.start();
        // 休眠1秒,确保i变量自增成功
        Thread.sleep(1000);
        thread.stop(); // 错误的终止
        while (thread.isAlive()) {
            // 确保线程已经终止
        } // 输出结果
        thread.print();
    }
}

执行结果:
i=1 j=0

使用interrupt()中断线程

package javaThread;

/**
 * 使用interrupt()正确中断线程
 */
class StopThread extends Thread {
    private int i = 0, j = 0;

    @Override
    public void run() {
        synchronized (this) {
            // 增加同步锁,确保线程安全
            ++i;
            try {
                // 休眠10秒,模拟耗时操作
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ++j;
        }
    }

    /**
     * 打印i和j
     */
    public void print() {
        System.out.println("i=" + i + " j=" + j);
    }
}

public class StopThreadTest {
    public static void main(String[] args) throws InterruptedException {
        StopThread thread = new StopThread();
        thread.start();
        // 休眠1秒,确保i变量自增成功
        Thread.sleep(1000);
        thread.interrupt(); // 正确终止
        while (thread.isAlive()) {
            // 确保线程已经终止
        } // 输出结果
        thread.print();
    }
}

执行结果:
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at javaThread.StopThread.run(stopThreadTest.java:16)
i=1 j=1

使用退出标志使线程正常退出

package javaThread;

/**
 * 使用退出标志使线程正常退出
 */
class StopThread extends Thread {
    private int i = 0, j = 0;

    @Override
    public void run() {
        synchronized (this) {
            // 增加同步锁,确保线程安全
            ++i;
            try {
                // 休眠10秒,模拟耗时操作
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ++j;
        }
    }

    /**
     * 打印i和j
     */
    public void print() {
        System.out.println("i=" + i + " j=" + j);
    }
}

public class StopThreadTest {
    public volatile static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            try {
                while (flag) { // 判断是否运行
                    System.out.println("运行中");
                    Thread.sleep(1000L);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        // 3秒之后,将状态标志改为False,代表不继续运行
        Thread.sleep(3000L);
        flag = false;
        System.out.println("程序运行结束");
    }
}

执行结果:
运行中
运行中
运行中
程序运行结束

线程状态

线程状态定义

  1. New: 尚未启动的线程的线程状态
  2. Runnable: 可运行线程的线程状态,等待CPU调度
  3. Block: 线程阻塞等待监视器锁定的线程状态
  4. Wait: 等待线程的线程状态
  5. Timed Waiting: 具有指定等待时间的等待线程的线程下状态
  6. Terminated: 终止线程的线程状态,线程正常完成执行或者出现异常

线程状态切换

image.png
package javaThread;

/**
 * 第一种状态切换: 新建 -> 运行 -> 终止
 */
public class threadStatus {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("第一种状态切换: 新建 -> 运行 -> 终止");
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程当前状态:" + Thread.currentThread().getState().toString());
                System.out.println("线程执行了");
            }
        });
        System.out.println("没调用start方法,线程当前状态:" + t.getState().toString());
        t.start();
        Thread.sleep(2000L); // 等待线程执行结束,再看状态
        System.out.println("等待两秒,再看线程当前状态:" + t.getState().toString());
    }
}

执行结果:
第一种状态切换: 新建 -> 运行 -> 终止
没调用start方法,线程当前状态:NEW
线程当前状态:RUNNABLE
线程执行了
等待两秒,再看线程当前状态:TERMINATED

package javaThread;

/**
* 第二种状态切换:新建 -> 运行 -> 等待 -> 运行 -> 终止
*/
public class ThreadStatus {

   public static void main(String[] args) throws InterruptedException {
       System.out.println("第二种状态切换:新建 -> 运行 -> 等待 -> 运行 -> 终止(sleep方式)");
       Thread t = new Thread(new Runnable() {
           @Override
           public void run() {
               try {// 将线程2移动到等待状态,1500后自动唤醒
                   Thread.sleep(1500);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println("线程当前状态:" + Thread.currentThread().getState().toString());
               System.out.println("线程执行了");
           }
       });
       System.out.println("没调用start方法,线程当前状态:" + t.getState().toString());
       t.start();
       System.out.println("调用start方法,线程当前状态:" + t.getState().toString());
       Thread.sleep(200L); // 等待200毫秒,再看状态
       System.out.println("等待200毫秒,再看线程当前状态:" + t.getState().toString());
       Thread.sleep(3000L); // 再等待3秒,让线程执行完毕,再看状态
       System.out.println("等待3秒,再看线程当前状态:" + t.getState().toString());
   }
}

执行结果:
第二种状态切换:新建 -> 运行 -> 等待 -> 运行 -> 终止(sleep方式)
没调用start方法,线程当前状态:NEW
调用start方法,线程当前状态:RUNNABLE
等待200毫秒,再看线程当前状态:TIMED_WAITING
线程当前状态:RUNNABLE
线程执行了
等待3秒,再看线程当前状态:TERMINATED

package javaThread;

/**
 * 第三种状态切换:新建 -> 运行 -> 阻塞 -> 运行 -> 终止
 */
public class ThreadStatus {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("第三种状态切换:新建 -> 运行 -> 阻塞 -> 运行 -> 终止");
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (ThreadStatus.class) {
                    System.out.println("线程当前状态:" + Thread.currentThread().getState().toString());
                    System.out.println("线程执行了");
                }
            }
        });
        synchronized (threadStatus.class) {
            System.out.println("没调用start方法,线程当前状态:" + t.getState().toString());
            t.start();
            System.out.println("调用start方法,线程当前状态:" + t.getState().toString());
            Thread.sleep(200L); // 等待200毫秒,再看状态
            System.out.println("等待200毫秒,再看线程当前状态:" + t.getState().toString());
        }
        Thread.sleep(3000L); // 再等待3秒,让线程执行完毕,再看状态
        System.out.println("等待3秒,让线程抢到锁,再看线程当前状态:" + t.getState().toString());
    }
}

执行结果:
第三种状态切换:新建 -> 运行 -> 阻塞 -> 运行 -> 终止
没调用start方法,线程当前状态:NEW
调用start方法,线程当前状态:RUNNABLE
等待200毫秒,再看线程当前状态:BLOCKED
线程当前状态:RUNNABLE
线程执行了
等待3秒,让线程抢到锁,再看线程当前状态:TERMINATED

多线程的优缺点

优点

  • 能利用更多的处理器核心
  • 更快的响应时间
  • 更好的编程模型

缺点

  • 活跃度风险(死锁,活锁,饥饿)
  • 性能风险:不一定更快(上下文切换,内存同步,阻塞)

相关文章

  • android 多线程 — 线程的面试题和答案

    这里都是我从各个地方找来的资料,鸣谢: Java多线程干货系列—(一)Java多线程基础 JAVA多线程和并发基础...

  • 技术体系

    一,java核心 java基础,jvm,算法,多线程,设计模式 Java基础:java基础相关,全栈java基础 ...

  • Java多线程目录

    Java多线程目录 Java多线程1 线程基础Java多线程2 多个线程之间共享数据Java多线程3 原子性操作类...

  • Java基础

    Java基础 集合基础 集合框架 多线程基础 多线程框架 反射 代理 集合基础 ArrayList LinkedL...

  • java多线程相关

    (一) 基础篇 01.Java多线程系列--“基础篇”01之 基本概念 02.Java多线程系列--“基础篇”02...

  • Java多线程高级特性(JDK8)

    [TOC] 一、Java多线程 1.Java多线程基础知识 Java 给多线程编程提供了内置的支持。一条线程指的是...

  • Java多线程系列目录(共43篇)-转

    最近,在研究Java多线程的内容目录,将其内容逐步整理并发布。 (一) 基础篇 Java多线程系列--“基础篇”0...

  • Android中的多线程

    1. Java多线程基础 Java多线程,线程同步,线程通讯 2. Android常用线程 HandlerThre...

  • Java架构师阅读书单

    一、内功心法 Java基础: 《Java核心技术》《Java编程思想》《Effective Java》 多线程...

  • java学习路线

    javaSE java基础语法 java文件操作 java网络操作 java多线程 java数据库操作 java ...

网友评论

      本文标题:java多线程基础

      本文链接:https://www.haomeiwen.com/subject/zurpektx.html