Thead 多线程技术总结

1. 概念

  • 程序:
    • 指令和数据的有序集和,其本身没有任何运行的含义,是一个静态的概念
  • 进程:
    • 执行程序的一次执行过程,是一个动态的概念,是系统资源分配的单位
  • 线程:
    • 通常一个进程包含多个线程,当前一个进程至少有一个线程,不然没有存在的意义,线程是CPU电镀和执行的单位

2. 创建线程

方式一:继承Thead类
// 1. 继承Thead类
public class TestThead extends Thread{
    // 2. 重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("多线程执行-----" + i);
        }
    }

    public static void main(String[] args) {
        // 3. 创建实例并执行start()方法
        TestThead testThead = new TestThead();
        testThead.start();

        for (int i = 0; i < 500; i++) {
            System.out.println("主线执行-----" + i);
        }
    }
}
方式二:实现Runnable接口(建议)
  • 方便对象的复用
// 1,实现Runable接口
public class TestTherd implements Runnable{
    // 2,重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("多线程执行 --- " + i);
        }
    }

    public static void main(String[] args) {
        // 3,创建对象实例并使用静态代理执行start方法
        new Thread(new TestTherd()).start();

        for (int i = 0; i < 200; i++) {
            System.out.println("主线程执行 -- " + i);
        }
    }
}
方式三:实现Callable接口
/**
 * @author: mingan.xie
 * @since: 2020/6/23 20:12
 * @email:
 */
// Callable实现多线程
// 1,实现Callable接口,泛型类型与call方法的返回值类型一致
public class TestCallable implements Callable<Boolean> {
		
    // 2,重写call方法
    @Override
    public Boolean call() throws Exception {
        System.out.println("线程执行了");
    }

    public static void main(String[] args) {
        // 3,创建实例
        TestCallable testCallable1 = new TestCallable();
        TestCallable testCallable2 = new TestCallable();
        TestCallable testCallable3 = new TestCallable();

        // 4,创建执行服务
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        // 5,提交执行
        Future<Boolean> future1 = executorService.submit(testCallable1);
        Future<Boolean> future2 = executorService.submit(testCallable2);
        Future<Boolean> future3 = executorService.submit(testCallable3);

        // 6,关闭服务
        executorService.shutdownNow();

    }
}

3. lamda表达式

  • 必须是函数式接口,即只有一个方法的接口

    /**
     * @author: mingan.xie
     * @since: 2020/6/23 21:01
     * @email:
     */
    public class TestLamda {
        public static void main(String[] args) {
            EatRice eatRice = new EatRice() {
                @Override
                public void rice() {
                    System.out.println("匿名内部类吃饭了");
                }
            };
            eatRice.rice();
    
            eatRice = () -> {
                System.out.println("lamda表达式吃饭了");
            };
            eatRice = () -> System.out.println("lamda表达式吃饭了");
            eatRice.rice();
        }
    }
    
    interface  EatRice{
        void rice();
    }
    
    class Me implements EatRice{
    
        @Override
        public void rice() {
            System.out.println("吃饭了");
        }
    }
    

4. 线程的6种状态

  • 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  • 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
    • 调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,此时处于就绪状(ready)。
    • 就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
  • 阻塞(BLOCKED):表示线程阻塞于锁。
  • 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
    • 线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。
  • 定时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
    • 这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
  • 终止(TERMINATED):表示该线程已经执行完毕。

5. 线程的基本操作

停止线程(stop)
  • 建议使用一个标志位来停止线程

    /**
     * @author: mingan.xie
     * @since: 2020/6/25 8:24
     * @email:
     */
    // 停止线程:
        // 不建议手动停止线程,建议使用一个标志位来停止线程
    public class TestStop implements Runnable{
        private boolean flag = true;
        @Override
        public void run() {
            int i = 0;
            while (flag){
                System.out.println("Thrad ---> " + i++);
            }
        }
    
        public void stop(){
            flag = false;
        }
    
        public static void main(String[] args) {
            TestStop testStop = new TestStop();
            new Thread(testStop).start();
    
            for (int i = 0; i < 1000; i++) {
                if (i == 800){
                    testStop.flag = false;
                }
                System.out.println("main ---> " + i);
            }
        }
    }
    
线程休眠(sleep)
  • sleep()指定当前线程阻塞的毫秒数
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一把锁,sleep不会释放锁
线程礼让(yield)
  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让CUP重新调度,礼让不一定成功
/**
 * @author: mingan.xie
 * @since: 2020/6/25 8:52
 * @email:
 */
public class TestYield implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程执行了 ---> 开始");
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + "线程执行了 ---> 结束");
    }

    public static void main(String[] args) {
        TestYield testYield = new TestYield();

        new Thread(new TestYield(),"a").start();
        new Thread(new TestYield(),"b").start();

    }
}
合并线程(join)
  • 将几个并行线程的线程合并为一个单线程执行,让其他线程进入阻塞状态
  • 等当前线程执行完毕之后,在执行其他线程
  • 可以想象成插队
线程优先级(priority)
  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度按照优先级决定应该调度那个线程执行

  • 在调用star方法执行前设置优先级

    /**
     * @author: mingan.xie
     * @since: 2020/6/25 9:20
     * @email:
     */
    public class TestPriority {
        public static void main(String[] args) {
            // 主线程优先级默认为5
            System.out.println(Thread.currentThread().getName() + "main线程执行 ---》" + Thread.currentThread().getPriority());
            MyPriority myPriority = new MyPriority();
    
            Thread threada = new Thread(myPriority, "a");
            threada.setPriority(1);
            threada.start();
            Thread threadb = new Thread(myPriority, "b");
            threadb.setPriority(Thread.NORM_PRIORITY);
            threadb.start();
            Thread threadc = new Thread(myPriority, "c");
            threada.setPriority(8);
            threadc.start();
            Thread threadd = new Thread(myPriority, "d");
            threadd.setPriority(Thread.MAX_PRIORITY);
            threadd.start();
        }
    }
    
    class MyPriority implements Runnable{
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "线程执行 ---》" + Thread.currentThread().getPriority());
        }
    }
    
守护线程(daemon)
  • 线程分为用户线程和守护线程

  • 虚拟机必须再确认用户线程执行完毕后才能关闭,不用等待守护线程执行完毕

  • 常见实例:日志操作,立即回收,内存监控

    /**
     * @author: mingan.xie
     * @since: 2020/6/25 9:36
     * @email:
     */
    public class TestDaemon {
        public static void main(String[] args) {
            Thread thread = new Thread(new Word());
            // 设置线程为守护线程,默认为false(用户线程)
            thread.setDaemon(true);
            thread.start();
    
            // 主线程
            new Thread(new You()).start();
    
        }
    }
    
    class You implements Runnable{
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("你开心的活着 ---> " + i + " 天");
            }
            System.out.println("=============== good byd==============");
        }
    }
    
    class Word implements Runnable{
    
        @Override
        public void run() {
            while (true){
                System.out.println("地球养育着你");
            }
        }
    }
    

6. 线程同步

  • 多个线程访问同一个对象,并且线程还需要修改对象时,就会引起并发问题,这个时候就就需要进行线程同步
  • 线程同步就是一种等待机制,多个需要访问此对象的线程进入这个对象的等待池形成列队,等待前面的线程使用完毕,下一个线程再使用
  • 由于同一个进程的多个线程共享同一块内存区域,为了保证数据的准确性,在访问时加入锁机制
    • 当一个线程获取到对象的排它锁,它就会独占资源,其他线程必须等待
    • 缺点:
      • 一个线程持有锁,回到吃其他需要此对象的线程挂起
      • 在多线程的竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题
      • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
同步方法 synchronized
  • 控制每个对象的访问,每个对象对应一把锁,每个sunchronized方法必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,放到方法结束才会释放锁
同步块
  • synchronized(obj){}
  • obj:同步监视器
    • obj可以使任意对象,但是推荐使用共享资源作为同步监视器
    • 同步方法无需指定同步监视器,因为同步方法的同步监视器就是this,就是对象本身,或者是class
  • 同步监视器的执行过程:
    • 第一个线程被访问,锁定同步监视器,执行其中代码
    • 第二个线程被访问,发现同步监视器被锁定,无法访问
    • 第一个线程访问完毕,解锁同步监视器
    • 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
死锁
  • 多个线程各自占有一些资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对象释放资源,都停止运行
  • 某一个同步块同时拥有两个以上对象的锁时,就有可能发生死锁的问题
  • 产生死锁的四个必要条件:
    • 互斥条件:一个资源每次只能被一个进程使用
    • 请求与保持条件:一个进程因请求资源而阻塞时,对已获取的资源保持不放
    • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
    • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
Lock(锁)
  • 通过显示定义同步锁对象来实现同步,同步锁使用lock兑现充当
  • ReentrantLock 类实现了lock,他拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的Reentrantlock,可以显式加锁,释放锁
/**
 * @author: mingan.xie
 * @since: 2020/6/27 10:28
 * @email:
 */
public class ReenTantLock {
    public static void main(String[] args) {
        Tickets tickets = new Tickets();
        new Thread(tickets,"A").start();
        new Thread(tickets,"B").start();
        new Thread(tickets,"C").start();
    }
}

class Tickets implements Runnable{
    private int num = 10;
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {
                lock.lock();
                Thread.sleep(200);
                if (num > 0){
                    System.out.println(num--);
                }else{
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }
}

7. 线程通信

  • 这是一个线程同步的问题,生产这和消费者共享一个资源,并且生产者和消费者之间相互依赖,互为条件
    • 对于生产者没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费
    • 对于消费者,在消费之后,要通知消费者已经结束消费,需要生产新的产品
    • wait():表示线程处于等待,会释放锁,需要其他线程唤醒
    • notifyAll():唤醒同一个对象上的所有调用wati()方法的线程
管程法
  • 生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

    package com.demo06;
    
    /**
     * @author: mingan.xie
     * @since: 2020/6/27 10:56
     * @email:
     */
    public class TestPC {
        public static void main(String[] args) {
            Buffer buffer = new Buffer();
            new Thread(new Producer(buffer)).start();
            new Thread(new Consumer(buffer)).start();
        }
    }
    
    // 生产者
    class Producer implements Runnable{
        Buffer buffer;
    
        public Producer(Buffer buffer) {
            this.buffer = buffer;
        }
    
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                buffer.push(new Chicken(i));
                System.out.println("生产了第:" + i+"只");
            }
        }
    }
    
    // 消费者
    class Consumer implements Runnable{
        Buffer buffer;
    
    
        public Consumer(Buffer buffer) {
            this.buffer = buffer;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                Chicken pop = buffer.pop();
                System.out.println("消费了第 ===》 "+pop.id+"值");
            }
        }
    }
    
    // 产品
    class Chicken{
        int id;
    
        public Chicken(int id) {
            this.id = id;
        }
    }
    
    // 缓冲区
    class Buffer implements Runnable{
        Chicken[] buffers = new Chicken[10];
        int num = 0;
    
        synchronized void push(Chicken chicken){
            // 如果容器满了,就进入等待状态
            if (num == buffers.length){
                try {
                    this.wait();
                    return;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            // 如果没有满,就添加
            buffers[num] = chicken;
            num++;
            // 通知消费者消费
            this.notifyAll();
        }
    
        synchronized Chicken pop(){
            // 判断是否容器是否空了,就进入等待状态,等生产者生产
            if (num == 0){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 消费
            num--;
            Chicken buffer = buffers[num];
    
            // 唤起生产者生产产品
            this.notifyAll();
            return buffer;
        }
    
        @Override
        public void run() {
        }
    }
    
    
信号灯法
  • 通过一个标识符来判断当前生产消费的状态
package com.demo06;

/**
 * @author: mingan.xie
 * @since: 2020/6/27 11:32
 * @email:
 */
// 线程通信方法:信号灯法
public class TestPC2 {
    public static void main(String[] args) {
        Program program = new Program();
        new Thread(new Perform(program)).start();
        new Thread(new Audience(program)).start();
    }
}

// 生产者
class Perform implements Runnable {
    Program program;

    public Perform(Program program) {
        this.program = program;
    }


    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                program.play("新闻联播");
            } else {
                program.play("花样广告");
            }
        }
    }
}

// 消费者
class Audience implements Runnable {
    Program program;

    public Audience(Program program) {
        this.program = program;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            program.wacth();
        }
    }
}


class Program implements Runnable {
    // 控制状态的切换
    boolean flag = true;
    String view;

    synchronized void play(String view) {
        // flag: t -> 生产 、 f -> 消费
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 生产者生产
        System.out.println("演员生产了" + view);
        this.view = view;
        this.flag = !this.flag;
        // 唤醒消费者消费
        this.notifyAll();
    }

    synchronized void wacth() {
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        // 消费者消费
        System.out.println("消费者观看了:" + view);
        this.flag = !this.flag;
        // 唤醒生产者生产
        this.notifyAll();
    }

    @Override
    public void run() {

    }
}

8. 线程池

  • 背景:
    • 经常创建和销毁,使用量特别大的资源,比如说并发情况下的线程,对性能影响很大
  • 可以避免频繁的创建和销毁,实现重复利用
  • 好处:
    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    • 便于线程管理
package com.demo06;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author: mingan.xie
 * @since: 2020/6/27 12:14
 * @email:
 */
public class TestPool {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();

        ExecutorService service = Executors.newFixedThreadPool(10);
        service.execute(myThread);
        service.execute(myThread);
        service.execute(myThread);
        service.execute(myThread);

        service.shutdown();
    }
}

class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

你可能感兴趣的:(Java,多线程,java,并发编程,多进程,thread)