一、线程简介

进程:应用程序执行实力,有独立内存空间和系统资源。
线程:cpu调度和分派的基本单位,进程中执行运算最小的单位,可以独立顺序控制流程
多线程:在一个进程中同时运行了多个线程,完成不同的工作

Win10查看线程:
https://jingyan.baidu.com/article/851fbc37a337023e1f15ab0e.html

二、线程的三种创建方式

1、继承Thread类

2、实现Runable接口(重点)

3、实现Callable接口(了解即可)

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

/**
 * @ClassName: ThreadNew
 * @Description: 回顾总结线程池的创建
 * @author: xiaofei
 */
public class ThreadNew {

    public static void main(String[] args) {

        new MyThread().start();

        new Thread(new MyRunnable()).start();


        FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable());
        new Thread(futureTask).start();

        try {
            // 获取返回值
            Integer integer = futureTask.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 继承Thread类
 */
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread");
    }
}

/**
 * 实现Runnable接口
 */
class MyRunnable implements Runnable {

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

/**
 * 实现Callable接口
 */
class MyCallable implements Callable {

    @Override
    public Integer call() throws Exception {
        System.out.println("MyCallable");
        return 100;
    }
}

小结:
继承Thread类:

子类继承Thread类具备多线程能力
启动线程:子类对象.start()
不建议使用:避免OOP单继承的局限性

实现Runnbale接口:

实现接口Runnable具有多线程能力
启动线程:new Thread(目标对象).start()
推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多线程使用

实现Callable接口:

可以自定义返回值
可以抛出异常
实现比较复杂

三、停止线程

建议正常停止-->利用次数
建议使用标志位-->设置一个标志位
不要使用stop或者destroy
/**
 * 测试停止线程
 * 1.建议正常停止-->利用次数
 * 2.建议使用标志位-->设置一个标志位
 * 3.不要使用stop或者destroy
 *
 * @author xiaofei
 * @create 2020-03-02 下午 3:06
 */
public class TestStopThread implements Runnable {

    // 1.设置一个标志位
    private boolean flag = true;

    @Override
    public void run() {
        int i = 0;
        while (flag) {
            System.out.println("线程在运行中" + i++);
        }
    }

    // 设置公开方法停止线程
    public void stop() {
        this.flag = false;
    }

    public static void main(String[] args) {
        TestStopThread testStopThread = new TestStopThread();
        new Thread(testStopThread).start();
        for (int i = 0; i < 100; i++) {
            System.out.println("main" + i);
            if (i == 20) {
                // 调用stop方法切换标志位,让线程停止
                testStopThread.stop();
                System.out.println("线程已经停止。。。");
            }
        }
    }
}

四、线程休眠

sleep(时间:单位(毫秒))指定当前线程阻塞的毫秒数;
sleep存在异常InterruptedException;
sleep时间达到后线程则进入就绪状态;
sleep可以模拟网络延时,倒计时等;
每一个对象都有一个锁,sleep不会释放锁;

五、线程礼让

礼让线程,让当前的正在执行的线程暂停,但不会阻塞;
将线程从运行状态转为就绪状态;
**让CPU重新调度,礼让不一定成功!看CPU心情**
Thread.yield();

六、线程强制执行

Join合并线程,带此线程执行完成以后,再执行其他线程,其他线程会阻塞;
可以想象为插队
Thread thread = new Thread(线程名称);
thread.join();// 插队

七、线程状态观测

线程状态可以处于以下状态之一:

NEW-->尚未启动的线程处于此状态
RUNNABLE-->在Java虚拟机中执行的线程处于此状态
BLOCKED-->被阻塞等待监视器锁定的线程处于此状态
WAITING-->正在等待另一个线程执行特定动作的线程处于此状态
TIMED WAITING-->正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
TERMINATED-->已退出的线程处于此状态
一个线程可以给定时间点处于一个状态,这些状态是不反应任何操作系统线程状态的虚拟机状态。
Thread thread = new Thread(()->{});
Thread.State state = thread.getState();

八、线程的优先级

线程的优先级用数字表示,范围从 1~10.
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;

使用以下方式改变或者获取优先级:

getPriority() .setPriority(int xxx);

九、守护线程

线程分为**用户线程**和**守护线程**
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕
如 后台记录操作日志,监控内存,垃圾回收等待...
Thread thread = new Thread(god);
// 默认false: 是用户进程 正常的线程都是用户线程
thread.setDaemon(true);

十、线程同步

由于同一个进程的多个线程共享一块存储空间,在带来方便的同时,也带来了访问冲突问题,为保证数据在方法中被
访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,
使用后释放锁即可。

同步方法:

public synchronized void method(int args){}

缺陷:若将一个大的方法申明为synchronized将会影响效率

同步块:

synchronized (Obj){}

Obj: 同步监视器,Obj可以为任何对象,但是推荐使用共享资源作为同步监视器

同步监视器的执行过程:

第一个线程访问时,锁定同步监视器,执行其中代码。
第二个线程访问时,发现同步监视器已被锁定,无法访问。
第一个线程访问完毕时,解锁同步显示器。
第二个线程访问时,发现同步显示器没有锁,然后锁定并访问。

十一、死锁

多个线程各自占用一些共享资源,并且互相的等待其他线程占有的资源才能运行,而倒是两个或者多个线程都在等待对方释放资源,
都停止执行的情形,某一个同步快同时拥有 两个以上对象的锁时,就可能发生死锁问题。

产生死锁的四个必要条件:

1、互斥条件:一个资源每次只能被一个进程使用。
2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3、不削夺条件:进程以获得的资源,在未使用完之前,不能强行剥夺。
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

十二、Lock锁

ReetrantLock类实现了Lock,它拥有synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较
常用的是ReentrantLock,可以显式加锁,释放锁。

class TestLock2 implements Runnable {

    int tickNums = 10;

    // 定义lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                // 加锁
                lock.lock();
                if (tickNums > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(tickNums--);
                } else {
                    break;
                }
            } finally {
                // 一定要放在finally中释放锁,因为有可能在获取锁的时候抛出异常。
                lock.unlock();
            }
        }
    }
}

synchronized与Lock的对比:

Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放。
Lock只能锁代码块,synchronized可以锁代码块和方法。
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性。

十三、线程通信

方法名 作用
wait() 表示线程一直等待,直到其他线程通知,与sleep不同,它会释放锁
wait(long timeout) 置顶等待的毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象所有调用wait()方法的线程,优先级别高的线程优先调度