多线程,线程同步和锁

  • 1:线程的创建
    • 1-1:继承Thred类
    • 1-2:实现Runnable接口
    • 1-3:实现Callable<>接口,有返回值的线程
  • 2:线程的其他操作
    • 2-1:线程停止
    • 2-2:线程礼让yield
    • 2-3:线程强制执行join
    • 2-4:查看线程状态
    • 2-5:线程优先级priority
    • 2-6:守护线程deamon
  • 4:并发与锁
    • 4-1:初识并发问题
    • 4-2:synchronized
    • 4-3:死锁
  • 5:线程通信wait和notify()
    • 5-1:管程法
    • 5-2:信号灯法
  • 6:线程池
    • 6-1:线程池例子
    • 6-2:自定义线程池(手动创建线程池,效果会更好)

 

1:线程的创建

1-1:继承Thred类

package com.lingaolu;

public class CreateThread extends Thread{

    @Override
    public void run() {
        System.out.println("子线程执行");
    }

    public static void main(String[] args) {
        System.out.println(1);
        new CreateThread().start();
        System.out.println(2);
    }
}

多线程,线程同步和锁_Java进阶

1-2:实现Runnable接口

package com.lingaolu;

public class CreateThread implements Runnable{

    @Override
    public void run() {
        System.out.println("子线程执行");
    }

    public static void main(String[] args) {
        System.out.println(1);
        new Thread(new CreateThread()).start();
        System.out.println(2);
    }
}

        或者使用lambda表达式

package com.lingaolu;

public class CreateThread{

    public static void main(String[] args) {
        System.out.println(1);
        new Thread(()->System.out.println("子线程执行")).start();
        System.out.println(2);
    }
}

1-3:实现Callable<>接口,有返回值的线程

package com.lingaolu;

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

public class CreateThread implements Callable<String> {
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println(1);
        FutureTask<String> futureTask = new FutureTask<>(new CreateThread());
        new Thread(futureTask).start();
        // 这个get方法是阻塞的
        System.out.println(futureTask.get());
        System.out.println(2);
    }

    @Override
    public String call(){
        System.out.println("子线程执行");
        return "OK";
    }
}

多线程,线程同步和锁_并发_02

2:线程的其他操作

        线程有5个状态,如下
多线程,线程同步和锁_多线程_03

2-1:线程停止

        我们不推荐线程使用**interrupt(),stop(),destroy()**等方法停止线程,而是自己写一个标记位来作为线程的终止变量

package com.lingaolu;


public class CreateThread implements Runnable{

    private boolean flag = true;
    
    @Override
    public void run() {
        while (flag){
            System.out.println("线程执行");
        }
    }

    // 调用方法,改变标志位的值来停止线程
    public void stop(){
        this.flag = false;
    }

}

2-2:线程礼让yield

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让cpu重新调度,礼让不一定成功,看cpu心情
package com.lingaolu;


public class CreateThread implements Runnable{
    
    public static void main(String[] args) {
        System.out.println(1);
        new Thread(new CreateThread(),"线程1").start();
        new Thread(new CreateThread(),"线程2").start();
        System.out.println(2);
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程执行开始");
        // 线程礼让
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"线程执结束");
    }

}

多线程,线程同步和锁_线程池_04

2-3:线程强制执行join

        join可以让线程强制执行

package com.lingaolu;


public class CreateThread implements Runnable{

    public static void main(String[] args) throws InterruptedException {
        System.out.println(1);
        Thread thread1 = new Thread(new CreateThread());
        thread1.start();
        for (int i = 1; i <= 5; i++) {
            if(i==3){
                // 当i=3时,强制让thread1执行
                thread1.join();
            }
            System.out.println("主线程"+i);
        }
        System.out.println(2);
    }

    @Override
    public void run() {
        System.out.println("子线程执行......");
    }

}

多线程,线程同步和锁_并发_05

2-4:查看线程状态

package com.lingaolu;

import lombok.SneakyThrows;

public class CreateThread implements Runnable{

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new CreateThread());
        Thread.State state = thread1.getState();
        System.out.println("线程创建状态"+state);
        thread1.start();
        state = thread1.getState();
        System.out.println("线程启动状态"+state);
        while(state != Thread.State.TERMINATED){
            if(state == Thread.State.TIMED_WAITING){
                System.out.println("线程等待状态"+state);
            }
            Thread.sleep(100);
            state = thread1.getState();
        }
        state = thread1.getState();
        System.out.println("线程结束状态"+state);
    }

    @SneakyThrows
    @Override
    public void run() {
        Thread.sleep(100);
        System.out.println("子线程执行状态......"+Thread.currentThread().getState());
        System.out.println("子线程执行......");
    }
}

        运行结果
多线程,线程同步和锁_线程池_06

2-5:线程优先级priority

        Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行

  • 但是优先级高的只是意味着获得调度的高概率,并不是每次都会比优先级低的先调度,这个得看CPU的调度
package com.lingaolu;

import lombok.SneakyThrows;

public class CreateThread implements Runnable{

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new CreateThread(),"t1");
        Thread t2 = new Thread(new CreateThread(),"t2");
        Thread t3 = new Thread(new CreateThread(),"t3");
        Thread t4 = new Thread(new CreateThread(),"t4");
        Thread t5 = new Thread(new CreateThread(),"t5");
        t1.setPriority(1);
        t1.start();
        t2.setPriority(5);
        t2.start();
        t3.setPriority(Thread.MAX_PRIORITY);
        t3.start();
        t4.setPriority(2);
        t4.start();
        t5.setPriority(6);
        t5.start();
        System.out.println("主线程优先级为"+Thread.currentThread().getPriority());

    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"子线程执行......优先级为"+Thread.currentThread().getPriority());
    }
    
}

多线程,线程同步和锁_线程池_07

2-6:守护线程deamon

  • 线程分为用户线程守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线层执行完毕,比如:监控内存,垃圾回收等等
package com.lingaolu;

public class CreateThread{

    public static void main(String[] args) throws InterruptedException {
        Thread girlFriendThread = new Thread(new GirlFriend());
        // 守护主线程,主线程完毕,girlFriendThread也就完毕
        girlFriendThread.setDaemon(true);
        girlFriendThread.setPriority(10);
        girlFriendThread.start();
    }

}

class I implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("我一直守护女朋友");
        }
    }
}

class GirlFriend implements Runnable{
    @Override
    public void run() {
        Thread thread = new Thread(new I());
        // 默认false,false为正常线程,true为守护线程,守护上级线程
        thread.setDaemon(true);
        thread.start();
        for (int i = 1; i <= 30; i++) {
            System.out.println("女朋友线程执行"+i);
        }
    }
}

多线程,线程同步和锁_线程池_08

4:并发与锁

4-1:初识并发问题

        3个线程同时抢20张票,更详细的用法这里有介绍

package com.lingaolu;

import lombok.SneakyThrows;

public class CreateThread implements Runnable{

    private Boolean flag = true;
    private static int num = 20;

    public static void main(String[] args) {
        CreateThread createThread = new CreateThread();
        new Thread(createThread,"111线程").start();
        new Thread(createThread,"222线程").start();
        new Thread(createThread,"333线程").start();
    }
    
    @SneakyThrows
    @Override
    public void  run() {
        while (flag) {
            if (num > 0) {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+"抢了第"+num--+"张票");
            } else {
                flag = false;
            }
        }


    }
}

        从结果可以看出,不仅有线程抢到了相同的票,还有负的票,这就是线程安全问题
多线程,线程同步和锁_锁_09

4-2:synchronized

        synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。

  • 缺陷:若将一个大的方法申明为synchronized将会影响效率
  • 方法里面需要修改内容才需要锁,锁太多,浪费资源
package com.lingaolu;

import lombok.SneakyThrows;

public class CreateThread implements Runnable{

    private Boolean flag = true;
    private static int num = 20;
    private Object obj = new Object();

    public static void main(String[] args) {
        CreateThread createThread = new CreateThread();
        new Thread(createThread,"111线程").start();
        new Thread(createThread,"222线程").start();
        new Thread(createThread,"333线程").start();
    }

    @SneakyThrows
    @Override
    public void  run() {
        while (flag) {
            synchronized (obj) {
                if (num > 0) {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + "抢了第" + num-- + "张票");
                } else {
                    flag = false;
                }
            }
        }


    }
}

4-3:死锁

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

package com.lingaolu;

import lombok.Data;
import lombok.SneakyThrows;

@Data
public class CreateThread implements Runnable{
    private static Object obj1 = new Object();
    private static Object obj2 = new Object();

    private int num;

    public CreateThread(int num) {
        this.num = num;
    }

    public static void main(String[] args) {
        new Thread(new CreateThread(1),"111线程").start();
        new Thread(new CreateThread(2),"222线程").start();
    }

    @SneakyThrows
    @Override
    public void  run() {
        if(num==1){
            synchronized (obj1) {
                System.out.println(Thread.currentThread().getName()+"=====对象1");
                Thread.sleep(1000);
                synchronized (obj2) {
                    System.out.println(Thread.currentThread().getName()+"=====对象2");
                    Thread.sleep(1000);
                }
            }
        }else {
            synchronized (obj2) {
                System.out.println(Thread.currentThread().getName()+"=====对象2");
                Thread.sleep(1000);
                synchronized (obj1) {
                    System.out.println(Thread.currentThread().getName()+"=====对象1");
                    Thread.sleep(1000);
                }
            }
        }
    }
}

        发生死锁,if和else的代码块里面都互相等待对方的锁
多线程,线程同步和锁_并发_10
        死锁解决,代码块不同时拥有2个对象锁以上,要分开

package com.lingaolu;

import lombok.Data;
import lombok.SneakyThrows;

@Data
public class CreateThread implements Runnable{
    private static Object obj1 = new Object();
    private static Object obj2 = new Object();

    private int num;

    public CreateThread(int num) {
        this.num = num;
    }

    public static void main(String[] args) {
        new Thread(new CreateThread(1),"111线程").start();
        new Thread(new CreateThread(2),"222线程").start();
    }

    @SneakyThrows
    @Override
    public void  run() {
        if(num==1){
            synchronized (obj1) {
                System.out.println(Thread.currentThread().getName()+"=====对象1");
                Thread.sleep(1000);
            }
            // 同步代码块锁分开
            synchronized (obj2) {
                System.out.println(Thread.currentThread().getName()+"=====对象2");
                Thread.sleep(1000);
            }
        }else {
            synchronized (obj2) {
                System.out.println(Thread.currentThread().getName()+"=====对象2");
                Thread.sleep(1000);
            }
            // 同步代码块锁分开
            synchronized (obj1) {
                System.out.println(Thread.currentThread().getName()+"=====对象1");
                Thread.sleep(1000);
            }
        }
    }
}

        成功解决
多线程,线程同步和锁_锁_11

5:线程通信wait和notify()

5-1:管程法

        并发协作模型“生产者/消费者模式”

  • 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
  • 消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
  • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”
  • 生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
package com.lingaolu;

import lombok.Data;

@Data
public class CreateThread {
    public static void main(String[] args) {
        DemandPool demandPool = new DemandPool();
        new Productor(demandPool).start();
        new programmer(demandPool).start();
    }
}

// 生产者,产品
class Productor extends Thread{
    DemandPool demandPool;
    public Productor(DemandPool demandPool){
        this.demandPool = demandPool;
    }
    // 添加需求
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            demandPool.push(new Demand(i));
        }
    }
}

// 消费者,程序员
class programmer extends Thread{
    DemandPool demandPool;
    public programmer(DemandPool demandPool){
        this.demandPool = demandPool;
    }

    // 做需求
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            demandPool.pop();
        }
    }
}

// 产品,需求
class Demand{
    // 需求编号
    int id;

    public Demand(int id) {
        this.id = id;
    }
}

// 需求池
class DemandPool{
    // 需要一个容器大小
    Demand[] demands = new Demand[5];
    // 容器计数器
    int count = 0;

    // 产品加需求
    public synchronized void push(Demand demand){
        // 如果需求池满了,就需要等待程序员做需求
        if(count == demands.length){
            // 通知程序员做需求,产品等待加需求
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 如果没有满,产品就加需求
        System.out.println("产品添加了第"+demand.id+"个需求");
        demands[count] = demand;
        count++;
        // 可以通知程序员做需求
        this.notifyAll();

    }

    // 程序员做需求
    public synchronized Demand pop(){
        // 判断是否有需求
        if(count==0){
            // 等待产品的需求
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 可以做需求
        count--;
        Demand demand = demands[count];
        System.out.println("程序员做了第"+demand.id+"个需求");
        // 通知产品加需求
        this.notifyAll();
        return demand;
    }
}

        结果
多线程,线程同步和锁_Java进阶_12

5-2:信号灯法

        信号灯法就是使用一个标记位,判断标记位来等待启动,我们举个例子,产品 出什么需要,程序员就做什么需求

package com.lingaolu;

import lombok.Data;

@Data
public class CreateThread {
    public static void main(String[] args) {
        Demand demand = new Demand();
        new Productor(demand).start();
        new programmer(demand).start();
    }
}

// 生产者,产品
class Productor extends Thread{
    Demand demand;
    public Productor(Demand demand){
        this.demand = demand;
    }
    // 添加需求
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            this.demand.push(i);
        }
    }
}

// 消费者,程序员
class programmer extends Thread{
    Demand demand;
    public programmer(Demand demand){
        this.demand = demand;
    }

    // 做需求
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            this.demand.pop();
        }
    }
}

// 需求
class Demand{
    private  int id;
    private Boolean flag = true;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Demand() {
    }

    // 产品加需求
    public synchronized void push(int id){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.setId(id);
        System.out.println("产品添加了第"+id+"个需求");
        // 通知程序员做需求
        this.notifyAll();
        this.flag = !this.flag;
    }

    // 程序员做需求
    public synchronized void pop(){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("程序员做了第"+id+"个需求");
        // 通知产品加需求
        this.notifyAll();
        this.flag = !this.flag;
    }
}

        结果
多线程,线程同步和锁_线程池_13

6:线程池

6-1:线程池例子

        JDK5.0起提供了线程池相关API:ExecutorService和Executors

ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

  • void execute(Runnable command):执行任务命令。没有返回值,一般用来执行Runnable
  • Future submit(Callable task):执行任务命令。有返回值,一般用来执行Callable
  • voidshutdown():关闭连接池

Executors:工具类,线程池的工厂,用于创建并返回不同类型的线程池

比较重要的几个类:

  • ExecutorService: 真正的线程池接口。
  • ScheduledExecutorService: 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
  • ThreadPoolExecutor: ExecutorService的默认实现。
  • ScheduledThreadPoolExecutor: 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现
package com.lingaolu;

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

public class CreateThread implements Runnable {
    public static void main(String[] args) {
        // 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        // 执行线程
        executorService.execute(new CreateThread());
        executorService.execute(()->System.out.println("线程1执行"));
        executorService.execute(()->System.out.println("线程2执行"));
        executorService.execute(()->System.out.println("线程3执行"));
        // 关闭连接
        executorService.shutdown();
    }

    @Override
    public void run() {
        System.out.println("线程执行");
    }
}

        结果
多线程,线程同步和锁_并发_14

6-2:自定义线程池(手动创建线程池,效果会更好)

        线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的
多线程,线程同步和锁_多线程_15

package com.lingaolu;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class CreateThread{
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                4,                   // 核心线程数
                8,              // 最大线程数
                5,                 // 存活时间
                TimeUnit.SECONDS,                  // 秒
                new LinkedBlockingDeque<>(300), // 队列大小300
                new MyThreadFactory(new ThreadGroup("myThreadGroup"),"my-thread"),  // 线程工厂
                new ThreadPoolExecutor.AbortPolicy()    // 拒绝策略(默认),所谓拒绝策略,是指将任务添加到线程池中时,线程池拒绝该任务所采取的相应策略
        );
        while (true){
            Thread.sleep(1000);
            threadPoolExecutor.execute(()->{
                Thread thread = Thread.currentThread();
                System.out.println(thread.getName());
            });
        }
    }

    static class MyThreadFactory implements ThreadFactory{
        private AtomicInteger number = new AtomicInteger(0);
        private ThreadGroup group;
        private String namePrefix;
        public MyThreadFactory(ThreadGroup group, String namePrefix) {
            this.group = group;
            this.namePrefix = namePrefix;
        }
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(group,r,namePrefix+"-"+number.getAndIncrement());
            if (t.isDaemon()){
                t.setDaemon(false);
            }
            if (t.getPriority() != Thread.NORM_PRIORITY){
                t.setPriority(Thread.NORM_PRIORITY);
            }
            return t;
        }
    }

}

        定的核心线程数是4个
多线程,线程同步和锁_线程池_16