5-多线程详解【狂神】

线程简介

1、多任务
看似多个任务都在做,本质上我们大脑在同一时间依旧只做一个事情
2、多线程
普通方法调用:只有主线程一条执行路径
多条执行路径,主线程和子线程并行交替执行


线程实现(重点)

Thread

package Demo01;
//创建线程方式一:继承Thread类,重写run()方法,调用start()方法启动线程
//线程开程不一定按顺序执行,CPU会自动调度
public class TestThread1 extends Thread {
    @Override
    public void run() {
        //run方法线程体
        for(int i=0;i<10;i++) {
            System.out.println("我在看代码" + i);
        }


    }
    public static void main(String[] args) {
        //main线程体
        //怎么开启其它线程?
        TestThread1 t1 = new TestThread1();
        t1.start(); //启动线程
        for(int i=0;i<10;i++) {
            System.out.println("我在学习多线程" + i);
        }
        //执行start()方法后,main线程会继续执行,会交替执行,线程不一定按顺序执行,CPU安排调度
        //我在看代码0
        //我在学习多线程0
        //我在看代码1
        //我在学习多线程1
        //我在看代码2
        //我在学习多线程2
        //我在看代码3
        //我在学习多线程3
        //我在看代码4
        //我在学习多线程4
        //我在看代码5
        //我在学习多线程5
        //我在看代码6
        //我在学习多线程6
        //我在看代码7
        //我在学习多线程7
        //我在看代码8
        //我在学习多线程8
        //我在看代码9
        //我在学习多线程9
    }
}
package Demo01;

import org.apache.commons.io.FileUtils;

//练习Thread类的使用,多线程同步下载图片
public class TestThread2 extends Thread {
    private String url;//图片地址
    private String Name;//图片名称

    public TestThread2(String url, String Name) {
        this.url = url;
        this.Name = Name;
    }

    @Override
    public void run() {

        WebDownloader webDownloader = new WebDownloader();
        webDownloader.download(url, Name);
        System.out.println(Name + "下载完成了!");
    }

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("https://pic.rmb.bdstatic.com/bjh/250208/dump/005c1358a976ac49792dac79961a4440.jpeg", "baidu1.png");
        TestThread2 t2 = new TestThread2("https://pic.rmb.bdstatic.com/bjh/250208/dump/ff923d5da511b800065fa3fc471ebbf4.jpeg", "baidu2.png");
        TestThread2 t3 = new TestThread2("https://pic.rmb.bdstatic.com/bjh/250208/dump/c72a0043dc181fcb3d38eb95ae461825.jpeg", "baidu3.png");
        t1.start();
        t2.start();
        t3.start();
    }
}
//先导入commons-io依赖,用于下载图片。可以到apache官网下载。
//下载器
class WebDownloader {
    //下载方法
    public void download(String url, String Name) {
        try {
            //下载图片
            FileUtils.copyURLToFile(new java.net.URL(url), new java.io.File(Name));
            System.out.println(Name + "下载完成!");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(Name + "下载失败!");
        }

    }
}

实现Runnable

package Demo01;
//创建线程方式2:实现Runnable接口,重写run()方法,执行线程需要丢入Runnable接口实现类,调用start()方法启动线程。
public class TestThread3 implements Runnable {
    @Override
    public void run() {
        //run方法线程体
        for(int i=0;i<10;i++) {
            System.out.println("我在看代码" + i);
        }


    }
    public static void main(String[] args) {
        //创建runnable接口实现类的实例
        TestThread3 t1 = new TestThread3();
        //创建线程,并传入Runnable接口实现类的实例
        Thread thread = new Thread(t1);
        thread.start();
        //简写成一行代码:Thread thread = new Thread(new TestThread3());
        //启动线程
        for (int i = 0; i < 10; i++) {
            System.out.println("我在学习多线程" + i);
        }
    }
}

总结,推荐Runnable接口

初识并发问题

package Demo01;
//多个线程同时操作同一个对象
//买火车票的例子
//多个线程同时操作同一个对象,线程不安全,数据紊乱,也可能会出现一个人拿到重复的票
public class TestThread4 implements Runnable {
    //票数
    private int tickets = 100;
    @Override
    public void run() {
        while(true){
            if(tickets <= 0){
                        break;
            }
            System.out.println(Thread.currentThread().getName() + "拿到了第"+ tickets-- + "张票");//tickets--表示票数减一
        }

    }

    public static void main(String[] args) {
        TestThread4 ticket = new TestThread4();
        new Thread(ticket, "小明").start();//此处用了简写的方式
        new Thread(ticket, "老师").start();
        new Thread(ticket, "黄牛").start();

    }
}

实现Callable接口(了解即可)

package Demo01;
//线程创建三:实现Callable接口
//可以定义返回值 
//可以抛出异常
//但是步骤复杂,

import org.apache.commons.io.FileUtils;

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

public class TestCallable implements Callable<Boolean> {
    private String url;//图片地址
    private String Name;//图片名称

    public TestCallable(String url, String Name) {
        this.url = url;
        this.Name = Name;
    }

    @Override
    public Boolean call() {

        WebDownloader1 webDownloader = new WebDownloader1();
        webDownloader.download(url, Name);
        System.out.println(Name + "下载完成了!");
        return true;
    }

    public static void main(String[] args) throws Exception {
        TestCallable t1 = new TestCallable("https://pic.rmb.bdstatic.com/bjh/250208/dump/005c1358a976ac49792dac79961a4440.jpeg", "baidu1.png");
        TestCallable t2 = new TestCallable("https://pic.rmb.bdstatic.com/bjh/250208/dump/ff923d5da511b800065fa3fc471ebbf4.jpeg", "baidu2.png");
        TestCallable t3 = new TestCallable("https://pic.rmb.bdstatic.com/bjh/250208/dump/c72a0043dc181fcb3d38eb95ae461825.jpeg", "baidu3.png");
        //创建执行服务
        ExecutorService executor = Executors.newFixedThreadPool(3);
       //提交执行
         Future<Boolean> future1 = executor.submit(t1);
        Future<Boolean> future2 = executor.submit(t2);
        Future<Boolean> future3 = executor.submit(t3);
        //获取结果
        boolean result1 = future1.get();
        boolean result2 = future2.get();
        boolean result3 = future3.get();
        //关闭执行服务
        executor.shutdown();

    }
}
//先导入commons-io依赖,用于下载图片。可以到apache官网下载。
//下载器
class WebDownloader1 {
    //下载方法
    public void download(String url, String Name) {
        try {
            //下载图片
            FileUtils.copyURLToFile(new java.net.URL(url), new java.io.File(Name));
            System.out.println(Name + "下载完成!");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(Name + "下载失败!");
        }

    }
}

静态代理

你:真实角色 
婚庆公司:代理你,帮你处理结婚的事
结婚:实现都实现结婚接口即可
//静态代理模式总结
//真实对象和代理对象实现同一个接口
//代理对象要代理真实对象
//好处:代理对象可以做很多真实对象做不了的事情,真实对象专注做自己的事情


public class StacticProxy {
    public static void main(String[] args) {
        Marry target = new you();
        WeddingCompany proxy = new WeddingCompany(target);
        proxy.HappyMarry();
}
interface Marry {//定义接口
    void HappyMarry();
    }

//真实角色
static class you implements Marry {
    @Override
    public void HappyMarry() {
        System.out.println("要结婚了");
    }
}
//代理角色,帮助你结婚
static class WeddingCompany implements Marry {
    private Marry target;
    public WeddingCompany(Marry target) {
        this.target = target;
        }
    @Override
    public void HappyMarry() {
        before();
        this.target.HappyMarry();//调用真实角色的方法
        after();
    }
    private void before() {
        System.out.println("请安排婚礼仪式");
    }
    private void after() {
        System.out.println("婚礼圆满结束");
    }
}
}

Lambda表达式

package FF;

/**
 * 推导lambda表达式
 */
public class TestLambda1 {
    //3.静态内部类,可以将2简化到3
    static class Like2 implements ILike {
        public void lambda() {
            System.out.println("I like lambda22!");
        }
    }

    public static void main(String[] args) {
        ILike like = new Like();
        like.lambda();
        ILike like2 = new Like2();
        like2.lambda();

        //4.局部内部类,可以将3简化到4
        class Like3 implements ILike {
            public void lambda() {
                System.out.println("I like lambda33!");
            }
        }
        ILike like3 = new Like3();
        like3.lambda();
        //5.匿名内部类,没有类的名称,必须借助接口或者父类,可以将4简化到5
        like = new ILike() {
            @Override
            public void lambda() {
                System.out.println("I like lambda44!");
            }
        };
        like.lambda();
        //6.lambda简化,可以将5简化到6
        like = () -> System.out.println("I like lambda55!");//箭头后面是方法体
        //love=(a,b,c)->{System.out.println(a+b+c);};//多参数的lambda表达式
        like.lambda();
        //I like lambda!
        //I like lambda22!
        //I like lambda33!
        //I like lambda44!
        //I like lambda55!

        //总结:lambda表达式只能有一行代码的情况下,可以简化为更简洁的形式,但是如果有多行代码,那么就用代码块包裹
        //前提接口为函数式接口,否则无法使用lambda表达式,函数式接口只有一个抽象方法
        //多个参数也可以去掉参数类型,要去掉都去掉,必须加上括号
    }
}
//1.定义一个函数式接口
interface ILike {
    void lambda();
}
//2.实现类
class Like implements ILike {
    public void lambda() {
        System.out.println("I like lambda!");
    }
}

线程状态




线程停止

package state;
//测试stop
//1.建议线程正常停止--》利用次数,不建议死循环
//2.建议使用标志位---》利用标志位,判断是否需要停止线程
//3.不要使用stop或destroy等过时或JDK不推荐的方法
public class TestStop implements Runnable {
    //1.设置标志位
    private boolean flag = true;

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

    }
}

线程休眠

package state;

import java.util.Date;

//网络延时能避免操作同一对象时
//模拟倒计时

public class TestSleep {
    public static void main(String[] args) throws InterruptedException {
        /**try {
            tenDown();
        }catch (InterruptedException e){
            e.printStackTrace();
        }**/
        //打印当前时间
        Date startTime = new Date(System.currentTimeMillis());//获取当前时间
        while(true){
            try {
                Thread.sleep(1000);
                System.out.println(new Date(System.currentTimeMillis()) + " 倒计时中...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //模拟倒计时
    public static void tenDown() throws InterruptedException {
        int num = 10;
        while(true){
            Thread.sleep(1000);
            System.out.println(num--);
            if(num == 0){
                break;
            }

        }
    }
}

线程礼让

package state;
//测试礼让,礼让不一定成功,看CPU心情
public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield, "线程1").start();
        new Thread(myYield, "线程2").start();
    }
}
class MyYield implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程开始执行");
        Thread.yield();//礼让
        System.out.println(Thread.currentThread().getName() + "线程停止执行");
    }
}
//线程2线程开始执行
//线程1线程开始执行
//线程2线程停止执行
//线程1线程停止执行

Join

package state;
//想像插队
public class TestJoin implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(" vip来了 " + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
//启动线程
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);


        thread.start();
        //主线程
        for (int i = 0; i < 1000; i++) {
            if (i == 200) {
                thread.join(); //插队
            }
            System.out.println("main线程 " + i);

        }

    }
}

观测线程状态

package state;
//观察线程的状态
public class TestState {
    public static void main(String[] args) {
        Thread thread =new Thread(()->{
            for(int i=0;i<10;i++){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("/////");
        });
                //观察线程的状态
        Thread.State state = thread.getState();
        System.out.println(state);
        //观察启动
        thread.start();
         state = thread.getState();
         System.out.println(state);//Run
        while (state != Thread.State.TERMINATED) {//只要线程没有终止,就一直观察状态
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            state = thread.getState();//更新线程状态
            System.out.println(state);//输出线程状态

        }

    }
}

线程优先级

package state;
//测试线程优先级
public class TestPriority {
    public static void main(String[] args) {
        //主线程的优先级
        System.out.println(Thread.currentThread().getName() + " -->"+Thread.currentThread().getPriority());
        //创建线程
        Thread t1 = new Thread(new MyPriority(), "t1");
        Thread t2 = new Thread(new MyPriority(), "t2");
        Thread t3 = new Thread(new MyPriority(), "t3");
        Thread t4 = new Thread(new MyPriority(), "t2");
        //设置优先级,再启动线程
        t1.start();
        t2.setPriority(8);
        t2.start();
        t3.setPriority(Thread.MAX_PRIORITY);
        t3.start();
        t4.setPriority(Thread.MIN_PRIORITY);
        t4.start();


    }

}
class MyPriority implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " -->" + Thread.currentThread().getPriority());
    }
}

守护线程

package state;
//测试守护进程
//上帝守护你
//守护线程会在用户线程结束后,自动结束,不用手动关闭,会晚一些结束,适用于长时间运行的后台任务
//用户线程线束,虚拟机也会退出
public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();
        Thread t1 = new Thread(god);
        Thread t2 = new Thread(you);
        t1.setDaemon(true);//默认是false表示是用户线程,正常线程都是用户线程
        t1.start();//启动上帝线程
        t2.start();//启动你线程



    }
}

//上帝
    class God implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                while (true) {
                    System.out.println("上帝守护你");
                }
            }
        }
    }
//你
    class You implements Runnable {
    @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
            System.out.println("你一生都开心的活着");
             }
        System.out.println("====goodbye world======");
        }


    }

线程同步(重点)

多个线程操作同一个资源
并发:同一个对象被多个线程同时操作,抢车票,两个银行同时取钱
队列+锁才能保证安全
保证安全,就一定会损失性能,反之也是
三个不安全的案例

package syn;
//不安全的买票
//线程不安全,可能会有负数
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();
        new Thread(station,"me").start();
        new Thread(station,"you").start();
        new Thread(station,"黄牛").start();
    }
}
class BuyTicket implements Runnable {
    //票
    private int ticket=10;
    boolean flag=true;
    @Override
    public void run() {
        //买票
        while (flag) {
            try{
                buy();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    private void buy() throws InterruptedException{
        //判断票是否充足
        if(ticket<=0){
            flag=false;
            return;
        }
        //模拟延迟
        Thread.sleep(100);
        //买票
        System.out.println(Thread.currentThread().getName()+"拿到"+ticket--);
    }
}
package syn;
//不安全的取钱
//两个人去银行取钱
//sleep可以放大问题的发生性
public class UnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account(1000, "结婚基金");
        Drawing you = new Drawing(account, 500, "你");
        Drawing girlFriend = new Drawing(account, 600, "girlFriend");
        you.start();
        girlFriend.start();

    }
}
//账户
class Account {
    int money;
    String name;
    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//银行
class Drawing extends Thread {
    Account account;
    //取了多少钱
    int drawMoney;
    //现在有多少钱
    int nowMoney;
    public Drawing(Account account, int drawMoney, String name) {
        super(name);
        this.account = account;
        this.drawMoney = drawMoney;

    }
    //取钱
    @Override
    public void run() {
        //先判断余额是否足够
        if (account.money -drawMoney<0) {
            System.out.println(Thread.currentThread().getName() + "取钱失败,余额不足!");
            return;
        }
        //sleep可以放大问题的发生性
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //卡内余额=yuan卡内余额-取钱数
        account.money -= drawMoney;
        //你手里的钱
        nowMoney += drawMoney;
        System.out.println(account.name + "取钱成功,余额为:" + account.money );
        System.out.println(Thread.currentThread().getName() + "手里有:" + nowMoney);
            //余额足够,开始取钱
    }
}
package syn;

import java.util.ArrayList;

//线程不安全的集合
public class UnsafeList {
    public static void main(String[] args) {
        ArrayList<String> list= new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                list.add(Thread.currentThread().getName());
            }).start();

        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}//9998,还是有可能出现线程安全问题的。

同步方法和同步块
同一个对象被两个线程中启用对象的synchronized方法时,会依次排队执行,而不会共同执行,引起数据错乱

package syn;
//不安全的买票
//线程不安全,可能会有负数
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();
        new Thread(station,"me").start();//操作同一个对象
        new Thread(station,"you").start();
        new Thread(station,"黄牛").start();
    }
}
class BuyTicket implements Runnable {
    //票
    private int ticket=10;
    boolean flag=true;
    @Override
    public void run() {
        //买票
        while (flag) {
            try{
                buy();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    //synchronized关键字保证线程安全,同步方法,锁的是this
    //锁的是this,所以多个线程操作的是同一个对象,互斥
    private synchronized void buy() throws InterruptedException{
        //判断票是否充足
        if(ticket<=0){
            flag=false;
            return;
        }
        //模拟延迟
        //Thread.sleep(100);
        //买票
        System.out.println(Thread.currentThread().getName()+"拿到"+ticket--);
    }
}

方法里面需要修改的内容才需要锁,锁的太多,浪费资源

package syn;
//不安全的取钱
//两个人去银行取钱
//sleep可以放大问题的发生性
public class UnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account(1000, "结婚基金");
        Drawing you = new Drawing(account, 500, "你");
        Drawing girlFriend = new Drawing(account, 600, "girlFriend");
        you.start();
        girlFriend.start();

    }
}
//账户
class Account {
    int money;
    String name;
    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//银行
class Drawing extends Thread {
    Account account;
    //取了多少钱
    int drawMoney;
    //现在有多少钱
    int nowMoney;
    public Drawing(Account account, int drawMoney, String name) {
        super(name);
        this.account = account;
        this.drawMoney = drawMoney;

    }
    //取钱
    @Override
    public void run() {
        //锁的对象是变化的量,需要增删改
        synchronized (account) {
            //先判断余额是否足够
            if (account.money - drawMoney < 0) {
                System.out.println(Thread.currentThread().getName() + "取钱失败,余额不足!");
                return;
            }
            //sleep可以放大问题的发生性
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //卡内余额=yuan卡内余额-取钱数
            account.money -= drawMoney;
            //你手里的钱
            nowMoney += drawMoney;
            System.out.println(account.name + "取钱成功,余额为:" + account.money);
            System.out.println(Thread.currentThread().getName() + "手里有:" + nowMoney);
            //余额足够,开始取钱
        }
    }
}
package syn;

import java.util.ArrayList;

//线程不安全的集合
public class UnsafeList {
    public static void main(String[] args) {
        ArrayList<String> list= new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                synchronized (list) {
                list.add(Thread.currentThread().getName());
                }
            }).start();

        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}//9998,还是有可能出现线程安全问题的。
package syn;

import java.util.concurrent.CopyOnWriteArrayList;

//测试JUC安全类型的集合,安全的类
public class TestJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

同步方法和同步方法块的区别

package FF;

public class SynchronizationExample {
    public static void main(String[] args) {
        // 创建一个银行账户,初始余额为 1000 元
        BankAccount account = new BankAccount(1000);

        // 创建两个线程,使用同步方法进行存款
        Thread thread1 = new DepositThread(account, 500, true);
        Thread thread2 = new DepositThread(account, 300, true);

        // 启动线程
        thread1.start();
        thread2.start();

        try {
            // 等待线程执行完毕
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("使用同步方法存款后,账户余额: " + account.getBalance() + " 元");

        // 创建另外两个线程,使用同步块进行存款
        Thread thread3 = new DepositThread(account, 200, false);
        Thread thread4 = new DepositThread(account, 400, false);

        // 启动线程
        thread3.start();
        thread4.start();

        try {
            // 等待线程执行完毕
            thread3.join();
            thread4.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("使用同步块存款后,账户余额: " + account.getBalance() + " 元");
    }
}

// 银行账户类
class BankAccount {
    private double balance;

    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }

    // 同步方法:存款操作
    public synchronized void depositUsingSyncMethod(double amount) {
        System.out.println(Thread.currentThread().getName() + " 进入同步方法开始存款...");
        double temp = balance;
        try {
            // 模拟一些耗时操作
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        temp += amount;
        balance = temp;
        System.out.println(Thread.currentThread().getName() + " 存款 " + amount + " 元,当前余额: " + balance + " 元");
    }

    // 非同步方法,使用同步块进行存款操作
    public void depositUsingSyncBlock(double amount) {
        System.out.println(Thread.currentThread().getName() + " 进入方法开始存款...");
        // 同步块,使用当前对象作为锁
        synchronized (this) {
            System.out.println(Thread.currentThread().getName() + " 进入同步块开始存款...");
            double temp = balance;
            try {
                // 模拟一些耗时操作
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            temp += amount;
            balance = temp;
            System.out.println(Thread.currentThread().getName() + " 存款 " + amount + " 元,当前余额: " + balance + " 元");
        }
        System.out.println(Thread.currentThread().getName() + " 离开方法");
    }

    public double getBalance() {
        return balance;
    }
}

// 存款线程类
class DepositThread extends Thread {
    private BankAccount account;
    private double amount;
    private boolean useSyncMethod;

    public DepositThread(BankAccount account, double amount, boolean useSyncMethod) {
        this.account = account;
        this.amount = amount;
        this.useSyncMethod = useSyncMethod;
    }

    @Override
    public void run() {
        if (useSyncMethod) {
            account.depositUsingSyncMethod(amount);
        } else {
            account.depositUsingSyncBlock(amount);
        }
    }
}
//Thread-0 进入同步方法开始存款...
//Thread-0 存款 500.0 元,当前余额: 1500.0 元
//Thread-1 进入同步方法开始存款...
//Thread-1 存款 300.0 元,当前余额: 1800.0 元
//使用同步方法存款后,账户余额: 1800.0 元
//Thread-2 进入方法开始存款...
//Thread-3 进入方法开始存款...
//Thread-2 进入同步块开始存款...
//Thread-2 存款 200.0 元,当前余额: 2000.0 元
//Thread-3 进入同步块开始存款...
//Thread-2 离开方法
//Thread-3 存款 400.0 元,当前余额: 2400.0 元
//Thread-3 离开方法
//使用同步块存款后,账户余额: 2400.0 元

1、同步方法 - 非静态同步方法以this为锁
非静态同步方法的锁是当前对象this。这意味着当一个线程进入非静态同步方法时,它会获取当前对象的锁,其他线程如果想要进入同一个对象的任何非静态同步方法,就必须等待这个锁的释放。

public class NonStaticSyncMethodExample {
    private int data = 0;

    // 非静态同步方法
    public synchronized void increment() {
        data++;
        System.out.println(Thread.currentThread().getName() + " 执行 increment 方法,data = " + data);
    }

    public static void main(String[] args) {
        NonStaticSyncMethodExample example = new NonStaticSyncMethodExample();
//同一个对象example,建立了两个线程,两个线程中的方法都是用了上面的increment,可以理解为执行了两遍方法
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example.increment();
            }
        }, "线程1");
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example.increment();
            }
        }, "线程2");
        thread1.start();
        thread2.start();
    }
}

在这个例子中,increment方法是一个非静态同步方法。两个线程线程1和线程2都在操作同一个NonStaticSyncMethodExample对象的increment方法。由于非静态同步方法的锁是this(即当前对象),所以同一时间只有一个线程可以进入increment方法,保证了data自增操作的线程安全性。
2、同步方法 - 静态同步方法以类的Class对象为锁
静态同步方法的锁是当前类的Class对象。这意味着对于所有实例,只要是调用静态同步方法,都会竞争同一个类的Class对象锁。

public class StaticSyncMethodExample {
    private static int sharedData = 0;

    // 静态同步方法
    public static synchronized void increment() {
        sharedData++;
        System.out.println(Thread.currentThread().getName() + " 执行 increment 方法,sharedData = " + sharedData);
    }

    public static void main(String[] args) {
        StaticSyncMethodExample example1 = new StaticSyncMethodExample();
        StaticSyncMethodExample example2 = new StaticSyncMethodExample();
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example1.increment();
            }
        }, "线程1");
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example2.increment();
            }
        }, "线程2");
        thread1.start();
        thread2.start();
    }
}

在这个例子中,increment是静态同步方法。即使有两个不同的StaticSyncMethodExample对象(example1和example2),但由于静态同步方法的锁是类的Class对象,所以线程1和线程2在调用increment方法时,依然会竞争同一个锁,保证了sharedData自增操作的线程安全性。
3、同步方法块 - 可以指定任意对象作为锁
同步方法块可以指定任意对象作为锁,这使得同步的控制更加灵活。可以根据具体需求,选择不同的锁对象来控制不同代码块的同步。
同步方法块的几种方法
以实例对象为锁

public class InstanceLockSyncBlock {
    private int data = 0;

    public void increment() {
        // 使用this作为锁,保护对data的操作
        synchronized (this) {
            data++;
        }
    }
}

以类对象为锁

public class ClassLockSyncBlock {
    private static int data = 0;

    public static void increment() {
        // 使用类对象作为锁,保护对静态变量data的操作
        synchronized (ClassLockSyncBlock.class) {
            data++;
        }
    }
}

以自定义对象为锁

public class CustomLockSyncBlock {
    private int data = 0;
    private final Object lock = new Object();

    public void increment() {
        // 使用自定义的lock对象作为锁
        synchronized (lock) {
            data++;
        }
    }
}

死锁


死锁案例

public class DeadlockExample {
    // 创建两个对象作为锁
    private static final Object resource1 = new Object();
    private static final Object resource2 = new Object();

    public static void main(String[] args) {
        // 创建第一个线程
        Thread thread1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println("Thread 1: Holding resource 1...");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 1: Waiting for resource 2...");
                synchronized (resource2) {
                    System.out.println("Thread 1: Holding resource 1 and resource 2...");
                }
            }
        });

        // 创建第二个线程
        Thread thread2 = new Thread(() -> {
            synchronized (resource2) {
                System.out.println("Thread 2: Holding resource 2...");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 2: Waiting for resource 1...");
                synchronized (resource1) {
                    System.out.println("Thread 2: Holding resource 2 and resource 1...");
                }
            }
        });

        // 启动两个线程
        thread1.start();
        thread2.start();
    }
}

代码解释
资源定义:创建了两个对象 resource1 和 resource2 作为需要争夺的资源。
线程创建:
thread1 首先获取 resource1 的锁,然后休眠 100 毫秒,之后尝试获取 resource2 的锁。
thread2 首先获取 resource2 的锁,然后休眠 100 毫秒,之后尝试获取 resource1 的锁。
死锁发生:当 thread1 持有 resource1 并等待 resource2 时,thread2 持有 resource2 并等待 resource1,此时两个线程都在等待对方释放资源,从而形成了死锁。
如何解锁

public class DeadlockAvoidanceExample {
    // 创建两个对象作为锁
    private static final Object resource1 = new Object();
    private static final Object resource2 = new Object();

    public static void main(String[] args) {
        // 创建第一个线程
        Thread thread1 = new Thread(() -> {
            // 按照相同顺序获取锁,先获取 resource1,再获取 resource2
            synchronized (resource1) {
                System.out.println("Thread 1: Holding resource 1...");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 1: Waiting for resource 2...");
                synchronized (resource2) {
                    System.out.println("Thread 1: Holding resource 1 and resource 2...");
                }
            }
        });

        // 创建第二个线程
        Thread thread2 = new Thread(() -> {
            // 同样按照相同顺序获取锁,先获取 resource1,再获取 resource2
            synchronized (resource1) {
                System.out.println("Thread 2: Holding resource 1...");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 2: Waiting for resource 2...");
                synchronized (resource2) {
                    System.out.println("Thread 2: Holding resource 1 and resource 2...");
                }
            }
        });

        // 启动两个线程
        thread1.start();
        thread2.start();
    }
}

代码解释
获取锁的顺序:在原代码中,thread1 先获取 resource1 再尝试获取 resource2,而 thread2 先获取 resource2 再尝试获取 resource1,这就可能导致环路等待。修改后的代码让两个线程都按照先获取 resource1 再获取 resource2 的顺序来获取锁。
避免死锁原理:当 thread1 先获取了 resource1 后,thread2 会等待 resource1 的释放,只有当 thread1 释放了 resource1 并且 thread2 获取到 resource1 之后,thread2 才会去尝试获取 resource2,这样就避免了两个线程相互等待对方持有的锁,从而破坏了环路等待条件,避免了死锁的发生。
除了统一锁的获取顺序,还可以采用以下方法避免死锁:

Lock锁


package syn;

import java.util.concurrent.locks.ReentrantLock;

//测试锁
public class TestLock {
    public static void main(String[] args) {
        TestLock2 t = new TestLock2();
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        t1.start();
        t2.start();
        t3.start();
    }
}


class TestLock2 implements Runnable {
    int ticket = 10;
    //定义lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();//加锁
                if (ticket > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(ticket--);
                } else {
                    break;
                }
                }finally{
                    lock.unlock();
                }
                //解锁

            }
        }
    }

线程通信问题

线程协作(生产者和消费者模式)



管程法

package syn;
//测试生产者消费者模型--》利用缓冲区解决:管程法
public class TestPc {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();
        Producer producer = new Producer(container);
        Consumer consumer = new Consumer(container);
        producer.start();
        consumer.start();

    }

}
//生产者
class Producer extends Thread {
    SynContainer container;
    public Producer(SynContainer container) {
        this.container = container;
    }
    //生产
    @Override
    public void run() {
        for (int i = 0; i < 100 ;i++) {
            container.push(new Chicken(i));
            System.out.println("生产了" + i+"只鸡");
        }
    }

}
//消费者
class Consumer extends Thread {
    SynContainer container;
    public Consumer(SynContainer container) {
        this.container = container;
    }
    //消费
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了" + container.pop().i + "只鸡");
        }
    }

}
//产品
class Chicken{
    int i;
    public Chicken(int i) {
        this.i = i;
    }
}

//缓冲区
class SynContainer {
    Chicken[] chickens = new Chicken[10];
    //容器计数器
    int count = 0;
    //生产者放入产品
    public synchronized void push(Chicken chicken) {
        //如果容器满了,就需要等待消费者
        if (count == chickens.length) {
            //通知消费者可以取出产品,生产等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果容器没有满,就直接放入产品
         chickens[count] = chicken;
         count++;
         //可以通知消费者可以取出产品
        this.notify();

    }
    //消费者取出产品
    public synchronized Chicken pop() {
        //判断能否消费
        if (count ==0) {
            //等待生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消费
        count--;
        Chicken chicken = chickens[count];

        //吃完了产品,通知生产者可以生产
        return chicken;
    }

}

第二个案例

// 缓冲区类,用于存储生产者生产的数据
class Buffer {
    private static final int MAX_SIZE = 5;
    private int[] buffer;
    private int count;
    private int in;
    private int out;

    public Buffer() {
        buffer = new int[MAX_SIZE];
        count = 0;
        in = 0;
        out = 0;
    }

    // 生产者调用的方法,用于向缓冲区中添加数据
    public synchronized void produce(int item) throws InterruptedException {
        // 当缓冲区已满时,生产者线程等待
        while (count == MAX_SIZE) {
            wait();
        }
        // 将数据添加到缓冲区
        buffer[in] = item;
        in = (in + 1) % MAX_SIZE;
        count++;
        System.out.println("生产者生产了: " + item);
        // 通知可能正在等待的消费者线程
        notifyAll();
    }

    // 消费者调用的方法,用于从缓冲区中取出数据
    public synchronized int consume() throws InterruptedException {
        // 当缓冲区为空时,消费者线程等待
        while (count == 0) {
            wait();
        }
        // 从缓冲区中取出数据
        int item = buffer[out];
        out = (out + 1) % MAX_SIZE;
        count--;
        System.out.println("消费者消费了: " + item);
        // 通知可能正在等待的生产者线程
        notifyAll();
        return item;
    }
}

// 生产者线程类
class Producer implements Runnable {
    private Buffer buffer;

    public Producer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                buffer.produce(i);
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

// 消费者线程类
class Consumer implements Runnable {
    private Buffer buffer;

    public Consumer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                buffer.consume();
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

// 主类,用于启动生产者和消费者线程
public class MonitorMethodExample {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        Thread producerThread = new Thread(new Producer(buffer));
        Thread consumerThread = new Thread(new Consumer(buffer));

        producerThread.start();
        consumerThread.start();

        try {
            producerThread.join();
            consumerThread.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

信号灯法

// 共享资源类,包含信号灯和数据
class SharedData {
    // 存储生产的数据
    private String data;
    // 信号灯,true 表示数据已生产,可以消费;false 表示需要生产数据
    private boolean isProduced = false;

    // 生产者调用此方法生产数据
    public synchronized void produce(String newData) {
        // 当数据已生产且未被消费时,生产者线程等待
        while (isProduced) {
            try {
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        // 生产数据
        this.data = newData;
        System.out.println("生产者生产了: " + data);
        // 标记数据已生产
        isProduced = true;
        // 唤醒可能正在等待的消费者线程
        notifyAll();
    }

    // 消费者调用此方法消费数据
    public synchronized String consume() {
        // 当数据未生产时,消费者线程等待
        while (!isProduced) {
            try {
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        // 消费数据
        System.out.println("消费者消费了: " + data);
        // 标记数据已消费
        isProduced = false;
        // 唤醒可能正在等待的生产者线程
        notifyAll();
        return data;
    }
}

// 生产者线程类
class ProducerThread implements Runnable {
    private SharedData sharedData;

    public ProducerThread(SharedData sharedData) {
        this.sharedData = sharedData;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            // 模拟生产的数据
            String product = "产品" + i;
            sharedData.produce(product);
            try {
                // 模拟生产所需时间
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

// 消费者线程类
class ConsumerThread implements Runnable {
    private SharedData sharedData;

    public ConsumerThread(SharedData sharedData) {
        this.sharedData = sharedData;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            sharedData.consume();
            try {
                // 模拟消费所需时间
                Thread.sleep(800);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

// 主类,程序入口
public class SignalLightExample {
    public static void main(String[] args) {
        // 创建共享资源对象
        SharedData sharedData = new SharedData();
        // 创建生产者线程
        Thread producer = new Thread(new ProducerThread(sharedData));
        // 创建消费者线程
        Thread consumer = new Thread(new ConsumerThread(sharedData));

        // 启动生产者线程
        producer.start();
        // 启动消费者线程
        consumer.start();

        try {
            // 等待生产者线程执行完毕
            producer.join();
            // 等待消费者线程执行完毕
            consumer.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

使用线程池

package syn;

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

//测试线程池

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.shutdown();
    }

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

高级主题

posted @ 2025-02-08 10:38  乘加法  阅读(15)  评论(0)    收藏  举报