线程 Thread

线程

普通方法调用和多线程

Process和Thread

  • 程序是指令和数据的有序集合,是静态概念
  • 进程Process是执行程序的一次执行过程,是一个动态的概念,是系统资源分配的单位
  • 通常一个进程中可以包含多个线程,每个进程中至少有一个线程,线程是CPU调度和执行的单位

核心概念

  • 线程是独立的执行路径
  • 程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程(清理)
  • main()称为主线程,是系统的入口,用于执行整个程序
  • 一个进程中,如开辟了多个线程,线程的运行由调度器安排
  • 对一份资源进行操作时,会存在资源抢夺的问题,需要加入并发控制
  • 线程会带来额外的开销,如CPU调度时间,并发控制开销
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

线程创建

Thread类(重点)

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法开启线程
package OOP.Demo;

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class Thread01 extends Thread{
    //创建线程方式一: 继承Thread类,重写run方法,调用start开启线程
    //总结:线程开启不一定立即执行,由CPU调度
    @Override
    public void run() {//重写run方法
        for (int i = 0; i <500 ; i++) {
            System.out.println("这里是线程01");
        }
    }
    public static void main(String[] args) {//main线程,主线程
        //创建线程对象
        Thread01 t1 = new Thread01();
        //调用start();开启线程
        t1.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("这里是main");
        }
    }
}

例子:下载图片测试

package OOP.Demo;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class Thread02 extends Thread{

    private String Url;
    private String path;

    public Thread02(String Url,String path){
        this.Url = Url;
        this.path = path;
    }

    @Override
    public void run() {
        new WebDownloader().downloader(Url,path);
//        WebDownloader webDownloader = new WebDownloader();
//        webDownloader.downloader(Url,path);
        System.out.println("下载了文件在:"+path);
    }


    public static void main(String[] args) {
        Thread02 t1 = new Thread02("https://img2024.cnblogs.com/blog/3475380/202407/3475380-20240701154652288-1747217106.png","1.png");
        Thread02 t2 = new Thread02("https://img2024.cnblogs.com/blog/3475380/202407/3475380-20240701154705043-664732010.png","2.png");
        Thread02 t3 = new Thread02("https://img2024.cnblogs.com/blog/3475380/202407/3475380-20240701154710607-423679454.png","3.png");

        t1.start();
        t2.start();
        t3.start();
    }
}
class WebDownloader{
    public void downloader(String url,String path){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(path));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IOException");
        }
    }
}

Runnable接口(重点)

  • 定义MyRunnable类实现Runnable接口

  • 实现run()方法,编写线程执行体

  • 创建线程对象,调用start()方法启动线程

    推荐使用Runnable对象,因为Java单继承的局限性

package OOP.Demo;

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class Thread03 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <500 ; i++) {
            System.out.println("这里是线程01");
        }
    }
    public static void main(String[] args) {//main线程,主线程
        //创建线程对象
        Thread03 t1 = new Thread03();
        //调用start();开启线程
        new Thread(t1).start();//区别在这

        for (int i = 0; i < 1000; i++) {
            System.out.println("这里是main");
        }
    }
}

例子:购买火车票

package OOP.Demo;

/**
 * @version: java version 1.8
 * @Author: 14
 */

//多个线程同时操作一个对象
//买火车票例
//发现问题:多个线程操作同一个资源,线程不安全,数据紊乱
public class Thread04 implements Runnable{

    //票数
    private int ticketNums = 10;
    @Override
    public void run() {
        while (true) {
            if(ticketNums>0){
                System.out.println(Thread.currentThread().getName()+"->拿到了第"+ticketNums--+"票");
            }else {
                break;
            }
            //模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        Thread04 t4 = new Thread04();

        new Thread(t4,"小红").start();
        new Thread(t4,"小米").start();
        new Thread(t4,"万恶的黄牛").start();
    }
}

image-20240703172401393

例子:龟兔赛跑

image-20240703210445563

package OOP.Demo;

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class Race implements Runnable{

    static String winner = null;
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (Thread.currentThread().getName().equals("兔子") && i%11 == 0) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (winner(i)){
                break;
            }
            System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
        }
    }
    public boolean winner(int steps){

        if(winner != null){
            return true;
        }
        if(steps >= 100){
            winner = Thread.currentThread().getName();
            System.out.println("胜者是:" + winner);
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}

Callable接口

  • 实现Callable接口,需要返回值类型

  • 重写call()方法,需要抛出异常

  • 创建目标对象

  • 创建执行服务:

    ExecutorService ser = Executors.newFixedThreadPool(1);
    
  • 提交执行:

    Future<Boolean> result = ser.submit(t1);
    
  • 获取结果:

    boolean r = result.get();
    
  • 关闭服务:

    ser.shutdownNow();
    

例子:利用Callable改造下载图片案例

package OOP.Demo;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class Thread05 implements Callable<Integer> {

    private String Url;
    private String path;

    public Thread05(String Url,String path){
        this.Url = Url;
        this.path = path;
    }

    @Override
    public Integer call() {
        new WebDownloader().downloader(Url,path);
//        WebDownloader webDownloader = new WebDownloader();
//        webDownloader.downloader(Url,path);
        System.out.println("下载了文件在:"+path);
        return 0;
    }


    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Thread05 t1 = new Thread05("https://img2024.cnblogs.com/blog/3475380/202407/3475380-20240701154652288-1747217106.png","1.png");
        Thread05 t2 = new Thread05("https://img2024.cnblogs.com/blog/3475380/202407/3475380-20240701154705043-664732010.png","2.png");
        Thread05 t3 = new Thread05("https://img2024.cnblogs.com/blog/3475380/202407/3475380-20240701154710607-423679454.png","3.png");

//        t1.start();
//        t2.start();
//        t3.start();
        //创建服务
        ExecutorService ser = Executors.newFixedThreadPool(3);
        //提交执行
        Future<Integer> f1 = ser.submit(t1);
        Future<Integer> f2 = ser.submit(t2);
        Future<Integer> f3 = ser.submit(t3);
        //获取结果
        Integer r1 = f1.get();
        Integer r2 = f2.get();
        Integer r3 = f3.get();
        //打印结果
        System.out.println(r1);
        System.out.println(r2);
        System.out.println(r3);
        //关闭服务
        ser.shutdownNow();
    }

}
//class WebDownloader{
//    public void downloader(String url,String path){
//        try {
//            FileUtils.copyURLToFile(new URL(url),new File(path));
//        } catch (IOException e) {
//            e.printStackTrace();
//            System.out.println("IOException");
//        }
//    }
//}

Lambda表达式

  • 函数式接口:

    任何接口,如果只包含唯一的一个抽象方法,那么它就是函数式接口

    public interface Runnable{
        public abstract void run();
    }
    
  • 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象

package OOP.Demo;

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class LambdaTest {
    //3、静态内部类
    static class ILove1 implements Love{
        @Override
        public void love() {
            System.out.println("I LOVE -->1");
        }
    }

    public static void main(String[] args) {
        //4、局部内部类
        class ILove2 implements Love{
            @Override
            public void love() {
                System.out.println("I LOVE -->2");
            }
        }
        ILove iLove3 = new ILove();
        //5、匿名内部类
        iLove3 = new ILove(){
            @Override
            public void love() {
                System.out.println("I LOVE -->3");
            }
        };
        //6、Lambda表达式
        Love iLove4 = ()-> { System.out.println("I LOVE -->4"); };

        ILove iLove = new ILove();
        ILove1 iLove1 = new ILove1();
        ILove2 iLove2 = new ILove2();
        iLove.love();
        iLove1.love();
        iLove2.love();
        iLove3.love();
        iLove4.love();
    }
}

//1、定义函数式接口
interface Love {
    void love();
}
//2、实现类
class ILove implements Love{
    @Override
    public void love() {
        System.out.println("I LOVE -->0");
    }
}

Lambda简化

总结

  • lambda表达式只有一行代码的情况下,才能简化为一行,如有多行,需要花括号包裹
  • 使用lambda表达式的前提是 接口为函数式接口
  • 多个参数也可以去掉参数类型(要去就都去掉),用括号包裹
package OOP.Demo;

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class LambdaTest {
    public static void main(String[] args) {
        //Lambda表达式
        Love iLove = ()-> { System.out.println("I LOVE -->0"); };
        //Lambda简化
        Love1 iLove1 = a ->  System.out.println("I LOVE -->" + a);

        Love2 iLove2 = (a,b) -> {
            System.out.println("I LOVE -->" + a);
            System.out.println("I LOVE -->" + b);
        };

        iLove.love();
        iLove1.love1(1);
        iLove2.love2(2,3);
    }
}

//1、定义函数式接口
interface Love {
    void love();
}
interface Love1{
    void love1(int a);
}
interface Love2{
    void love2(int a, int b);
}

静态代理模式(线程的底部原理)

总结:

  • 真实对象和代理对象都要实现同一个接口

  • 代理对象必须要代理真实对象

好处:

  • 代理对象可以做很多真实对象做不了的事情

  • 真实对象专注做自己的事情

package OOP.Demo;

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class StaticProxy {
    public static void main(String[] args) {
        Person person = new Person();
        MarryCompany marryCompany = new MarryCompany(person);
        marryCompany.marry();
        //线程启动,也相当与静态代理
        new Thread(()-> System.out.println("这是一个线程")).start();
        new MarryCompany(new Person()).marry();
    }
}

interface Marry{
    void marry();
}
class Person implements Marry{
    @Override
    public void marry() {
        System.out.println("找到漂亮老婆结婚啦!!!");
    }
}
class MarryCompany implements Marry{

    Person person;

    public MarryCompany(Person person) {
        this.person = person;
    }
    @Override
    public void marry() {
        before();
        person.marry();
        after();
    }
    public void before(){
        System.out.println("婚庆人员在布置现场");
    }
    public void after(){
        System.out.println("婚礼结束了,打钱");
    }
}

线程五大状态

  • 创建状态
  • 就绪状态
  • 运行状态
  • 阻塞状态
  • 死亡状态

停止线程

  • 不推荐使用JDK提供的stop()、destroy()方法。(已废弃)
  • 推荐线程自己停下来
  • 使用一个标志位当作终止变量:当flag = false,终止线程运行
package OOP.Demo01;

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class TestStop implements Runnable {

    //设置标志位
    private boolean flag = true;
    @Override
    public void run() {
        int i = 1;
        while (flag) System.out.println("Thread is running  "+ i++);
    }
    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        new Thread(testStop).start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("main thread is running  "+ i);
            if ( i == 900) {
                //调用stop()方法切换标志位,停止线程
                testStop.stop();
                System.out.println("Thread is stopped");
            }
        }
    }
    //设置一个公开的方法停止线程,转换标志位
    public void stop(){
        this.flag = false;
    }
}

线程休眠 Sleep

  • sleep指定当前线程阻塞的毫秒数
  • sleep存在异常InterruptedException;需要抛出
  • sleep时间达到后进入就绪状态
  • sleep可以模拟网络时延,倒计时等
  • 每个对象都有一个锁,sleep不会释放锁
package OOP.Demo01;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @version: java version 1.8
 * @Author: 14
 */
//倒计时/输出系统时间
public class TestSleep01 {
    public static void main(String[] args) {
//        try {
//            tenDown();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        Date date = new Date(System.currentTimeMillis());//获取当前系统时间
        while (true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));//格式化时间
                date = new Date(System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void tenDown() throws InterruptedException {
        int i = 10;
        while (true){
            if (i > 0){
                Thread.sleep(1000);
                System.out.println(i--);
            }else {
                break;
            }
        }
    }
}

线程礼让 Yield

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

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();

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

    }
}
class MyYield implements Runnable{
    @Override
    public void run() {
        System.out.println("Thread "+ Thread.currentThread().getName()+" Start");
        Thread.yield();
        System.out.println("Thread "+ Thread.currentThread().getName()+" End");
    }
}

线程强制执行 Join

  • Join合并线程,待此线程执行完后再执行其他线程,其他线程阻塞
  • 相当于插队
package OOP.Demo01;

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class TestJoin implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 100; i++) {
            System.out.println("大小姐驾到,统统闪开" + i );
        }
    }
    public static void main(String[] args) throws InterruptedException {
        TestJoin t = new TestJoin();
        Thread t1 = new Thread(t);
        t1.start();

        for (int i = 0; i < 500; i++) {
            if (i == 200) t1.join();
            System.out.println("主线程在执行" + i );
        }
    }
}

观测线程状态 State

package OOP.Demo01;

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class TestState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            System.out.println("---------------------");
        });

        Thread.State state = thread.getState();
        System.out.println(state);//NEW
        //开启线程
        thread.start();
        state = thread.getState();
        System.out.println(state);//RUN

        while (state != Thread.State.TERMINATED) {
            Thread.sleep(100);
            state = thread.getState();
            System.out.println(state);
        }
    }
}

线程优先级 Priority

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

  • 线程的优先级用数字表示,范围 1-10

    • Thread.MIN_PRIORITY = 1
    • Thread.NORM_PRIORITY = 5
    • Thread.MAX_PRIORITY = 10
  • 使用以下方法改变和获取优先级

    • setPriority(int xxx)
    • getPriority()

优先级的设定一般放在start()调度前

守护线程 Deamon

  • 线程分为用户线程和守护线程

  • 虚拟机必须确保用户线程执行完毕

  • 虚拟机不用等待守护线程执行完毕

    如:后台记录操作日志、监控内存、垃圾回收等待

package OOP.Demo01;

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class TestDeamon {

    public static void main(String[] args) {
        God god = new God();
        Person person = new Person();

        Thread t1 = new Thread(god);
        t1.setDaemon(true);//默认为false,用户线程
        t1.start();

        new Thread(person).start();
    }
}

class God implements Runnable{
    @Override
    public void run() {
        while(true){
            System.out.println("God  bless you");
        }
    }
}

class Person implements Runnable{
    @Override
    public void run() {
        System.out.println("----I Was Born----");
        for (int i = 0; i < 36500; i++) {
            System.out.println("I have a happy day");
        }
        System.out.println("----Goodbye My Love----");
    }
}

线程同步

  • 现实生活中,都会遇到“同一个资源,多个人想使用”的问题,解决办法是排队,一个一个来
  • 处理多线程问题,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个 对象的等待池 形成队列,等待前面线程使用完毕,下一个线程再使用

队列和锁

线程同步形成条件:队列+锁

  • 为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。

    但是会存在以下问题:

    • 一个线程持有锁会导致其他所有需要此锁的线程挂起
    • 在多线程的竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,synchronized关键字:synchronized方法和synchronized块

    //同步方法
    public synchronized void methon(int args){}
    

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

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

    同步块:synchronized(obj){ }

    • obj被称为同步监视器
      • obj可以是任何对象,但是推荐使用共享资源作为同步监视器
      • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
    • 同步监视器的执行过程
      1. 第一个线程访问,锁定同步监视器,执行其中的代码
      2. 第二个线程访问,发现同步监视器别锁定,无法访问
      3. 第一个线程访问完毕,解锁同步监视器
      4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
  • 方法里需要修改的内容才加锁,锁得太多,浪费资源

例子:取钱

package OOP.syn;

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class UnsafeBank {
    public static void main(String[] args) throws InterruptedException {
        Account account = new Account("零花钱",1000);

        new Drawing(account,300,"小花").start();
        new Drawing(account,800,"小草").start();

    }
}

class Account {
    private String id;
    private int balance ;
    public Account(String id, int balance) {
        this.id = id;
        this.balance = balance;
    }
    public String getId() {
        return id;
    }
    public int getBalance(){
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

    public void setId(String id) {
        this.id = id;
    }
}
class Drawing extends Thread {
    private Account account;//账户
    int drawingMoney;//要取的钱
    int nowMoney;//现在的钱

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    //取钱
    @Override
    public void run() {
        //synchronized块,锁了()里的
        synchronized (account) {
            //判断有没有钱
            if (account.getBalance() - drawingMoney < 0){
                System.out.println(Thread.currentThread().getName()+ "余额不足" );
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            //卡内余额
            account.setBalance(account.getBalance() - drawingMoney);
            System.out.println("当前余额:"+account.getBalance());

            //手里的钱
            nowMoney += drawingMoney;
            System.out.println(this.getName()+"手里的钱:"+nowMoney);
        }

    }
}

例子:买票

package OOP.syn;

/**
 * @version: java version 1.8
 * @Author: 14
 */
//线程不安全,有重复
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket,"小红").start();
        new Thread(buyTicket,"黄牛").start();
        new Thread(buyTicket,"大白").start();
    }
}

class BuyTicket implements Runnable{
    //票
    private int tickets = 10;
    //停止标准
    boolean flag = true;

    @Override
    public void run() {
        while(flag){
            try {
                Thread.sleep(100);
                buy();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    //synchronized 同步方法,锁this
    private synchronized void buy(){
        //判断是否有票
        if(tickets <= 0){
            flag = false;
            return;
        }
        //买票
        System.out.println(Thread.currentThread().getName() + "拿到" + tickets--);
    }
}

例子:List和CopyOnWriteArrayList

package OOP.syn;

import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class UnsafeList {

    public static void main(String[] args) throws InterruptedException {
        ArrayList<String> list = new ArrayList<String>();
        CopyOnWriteArrayList<String> list1 = new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                //synchronized块 锁list
                synchronized (list) {
                    list.add(Thread.currentThread().getName());
                }
                list1.add(Thread.currentThread().getName());
            }).start();
        }
        Thread.sleep(1000);
        System.out.println(list.size());
        System.out.println(list1.size());
    }
}

死锁

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

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class DeadLock {
    public static void main(String[] args) {
        MakeUp g1 = new MakeUp(MakeUp.lipstick,MakeUp.mirror,"花姑娘",0);
        MakeUp g2 = new MakeUp(MakeUp.lipstick,MakeUp.mirror,"黑姑娘",1);

        g1.start();
        g2.start();
    }
}
class Lipstick{

}
class Mirror{

}
class MakeUp extends Thread{

    //需要的资源只有一份,用static
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();
    //选择
    int choice = 0 ;
    //使用化妆品的人
    String name ;
    MakeUp(Lipstick lipstick , Mirror mirror ,String name,int choice){
        super(name);
        this.lipstick = lipstick;
        this.mirror = mirror;
        this.choice = choice;
    }
    @Override
    public void run() {
        //化妆
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //化妆,互相持有对方的锁,拿到对方的资源
    private void makeup() throws InterruptedException {
        if (choice == 0){
            synchronized (mirror){
                System.out.println(this.getName() + "获得mirror的锁");
                sleep(1000);
                synchronized (lipstick){
                    System.out.println(this.getName() + "获得lipstick的锁");
                }
            }
        } else {
            synchronized (lipstick){
                System.out.println(this.getName() + "获得lipstick的锁");
                sleep(1000);
                synchronized (mirror){
                    System.out.println(this.getName() + "获得mirror的锁");
                }
            }
        }
    }
    //破除死锁
//    private void makeup() throws InterruptedException {
//        if (choice == 0){
//            synchronized (mirror){
//                System.out.println(this.getName() + "获得mirror的锁");
//                sleep(1000);
//                }
//            synchronized (lipstick){
//                System.out.println(this.getName() + "获得lipstick的锁");
//            }
//        } else {
//            synchronized (lipstick){
//                System.out.println(this.getName() + "获得lipstick的锁");
//                sleep(1000);
//                }
//            synchronized (mirror){
//                System.out.println(this.getName() + "获得mirror的锁");
//            }
//        }
//    }
}
  • 产生死锁的四个必要条件:

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

    我们只要想办法破除其中任意一个或多个条件就能避免死锁

Lock锁

ReentrantLock 可重入锁

class A{
    private final ReentrantLock lock = new ReentrantLock();
    public void m(){
        try{
            lock.lock();
            ...
            代码块
            ...
        } finally{
            lock.unlock();
            //如果同步代码有异常,要将unlock()写入finally语句块
        }
    }
}

例子:买票

package OOP.syn;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @version: java version 1.8
 * @Author: 14
 */
//线程不安全,有重复
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket,"小红").start();
        new Thread(buyTicket,"黄牛").start();
        new Thread(buyTicket,"大白").start();
    }
}

class BuyTicket implements Runnable{
    //票
    private int tickets = 10;
    //停止标准
    boolean flag = true;
    //定义Lock锁
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while(flag){
            try {
                lock.lock();
                Thread.sleep(100);
                buy();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }finally {
                lock.unlock();
            }
        }
    }
    //synchronized 同步方法,锁this
    private  void buy(){
        //判断是否有票
        if(tickets <= 0){
            flag = false;
            return;
        }
        //买票
        System.out.println(Thread.currentThread().getName() + "拿到" + tickets--);
    }
}

synchronized与Lock对比:

  • Lock是显式锁(手动开启和关闭) synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的拓展性
  • 优先使用顺序:
    • Lock > 同步代码块 > 同步方法

线程通信

生产者消费者问题

  • 假设仓库能存放一些产品,生产者将生产出来的产品放入仓库,消费者从仓库中取走产品消费
  • 如果仓库中产品没有满,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中库存不满
  • 如果仓库中有库存,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中有库存

这是一个线程同步问题,生产者消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。

解决方法一

并发协助模型“生产者/消费者模式” --->管程法

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

/**
 * @version: java version 1.8
 * @Author: 14
 */
//测试:生产者消费者模型 --> 利用缓冲区解决:管程法
//生产者、消费者、产品、缓冲区
public class TestPC {
    public static void main(String[] args) {
        SynContainer synContainer = new SynContainer();

        Producter producter = new Producter(synContainer);
        Consumer consumer = new Consumer(synContainer);

        new Thread(producter).start();
        new Thread(consumer).start();

    }
}
//生产者
class Producter implements Runnable{
    SynContainer container;
    public Producter(SynContainer container){
        this.container = container;
    }

    //生产
    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            try {
                System.out.println("生产了--->第" + i + "只鸡");
                container.push(new Chicken(i));
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
//消费者
class Consumer implements Runnable{
    SynContainer container;
    public Consumer(SynContainer container){
        this.container = container;
    }

    //消费
    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            try {
                System.out.println("消费了--->第" + container.pop().id + "只鸡");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
//产品
class Chicken{
    int id ;
    public Chicken(int id) {
        this.id = id;
    }
}
//缓冲区
class SynContainer {
    //创建容器
    static Chicken[] chickens = new Chicken[10];
    //计数器
    int count = 0;
    //生产者放入产品
    public synchronized void push(Chicken chicken) throws InterruptedException {
        //如果容器满了,等待消费者消费
        if (count == chickens.length) {
            //通知消费者消费
            this.wait();
        }
        //没满,放入产品
        chickens[count] = chicken;
        count++;
        //通知消费者消费
        this.notifyAll();
    }
    //消费者消费产品
    public synchronized Chicken pop() throws InterruptedException {
        //如果没有产品,等待生产者生产
        if (count == 0) {
            this.wait();
        }
        //有库存,消费产品
        count--;
        Chicken chicken = chickens[count];
        //通知生产者生产
        this.notifyAll();
        return chicken;
    }
}

解决方法二

并发协助模型“生产者/消费者模式” --->信号灯法

package OOP.syn;

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class TestPC2 {
    public static void main(String[] args) {
        Pie pie = new Pie();
        Producter1 producter1 = new Producter1(pie);
        Consumer1 consumer1 = new Consumer1(pie);
        new Thread(producter1).start();
        new Thread(consumer1).start();
    }
}
class Producter1 implements Runnable{
    Pie pie ;
    public Producter1(Pie pie) {
        this.pie = pie;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            switch (i%3){
                case 0:
                    pie.product("菠萝");
                    break;
                case 1:
                    pie.product("苹果");
                    break;
                case 2:
                    pie.product("香蕉");
                    break;
            }
        }
    }
}
class Consumer1 implements Runnable{
    Pie pie ;
    public Consumer1(Pie pie) {
        this.pie = pie;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            pie.consume();
        }

    }
}
class Pie{
    String name;
    boolean flag = true;

    //生产
    public synchronized void product(String name){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("制作了一个" + name + "派");
        this.name = name;
        this.flag = !this.flag;
        this.notifyAll();

    }
    //消费
    public synchronized void consume(){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("购买了一个" + name + "派");
        this.flag = !this.flag;
        this.notifyAll();

    }
}

线程池

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能的影响很大

  • 思路:提前创建好对各线程,放入线程池中,使用时直接获取,使用完后放回池中。

    可以避免频繁的创建销毁、实现重复利用 。类似公共交通工具

  • 好处:

    • 提高响应速度(减少了创建新线程的时间)
    • 较低资源消耗(重复利用线程池中线程,不需要每次都创建)
    • 便于线程管理()
      • coreRoolSize:核心池大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时最多存活时间
  • ExecutorService:真正的线程池接口

    • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
    • Futuresubmit(Callabletask):执行任务,有返回值,一般用来执行Callable
    • void shutdown():关闭线程池
  • Executors:工具类、线程池的工厂类,用于创建和返回不同类型的线程池

    ExecutorService service = Executes.newFixedThreadPool(int nums/*要创建的线程数*/);
    service.execute(Thread/*要启动的线程*/);
    service.shutdown();//关闭                                                  
    
package OOP.syn;

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

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class TestPool {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);

        //启动线程
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //关闭线程
        service.shutdown();
    }
}
class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
package OOP.syn;

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

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class TestPool {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);

        //启动线程
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //关闭线程
        service.shutdown();
    }
}
class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
package OOP.syn;

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

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class TestPool {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);

        //启动线程
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //关闭线程
        service.shutdown();
    }
}
class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
package OOP.syn;

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

/**
 * @version: java version 1.8
 * @Author: 14
 */
public class TestPool {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);

        //启动线程
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //关闭线程
        service.shutdown();
    }
}
class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
posted @ 2024-07-03 23:10  十四2001  阅读(2)  评论(0编辑  收藏  举报