java多线程基础

2020-09-11  本文已影响0人  437760b195ab

进程与线程

线程和进程有什么区别

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()和run()区别

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

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()

结束线程

  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

多线程的优缺点

优点

缺点

上一篇 下一篇

猜你喜欢

热点阅读