关于java:这份java多线程笔记你真得好好看看我还没见过总结的这么全面的

1次阅读

共计 22172 个字符,预计需要花费 56 分钟才能阅读完成。

1. 线程,过程和多线程

1.程序 :指指令和数据的有序汇合,其自身没有任何意义,是一个动态的概念
2. 过程 :指执行程序的一次执行过程,是一个动静的概念。是零碎资源分配的单位(留神:很多多线程是模仿进去的,真正的多线程是指有多个 cpu,即多核,如服务器。即在一个 cpu 的状况下,在同一时间点,cpu 只能执行一个代码,因为切换的很快,所以就有同时执行的错觉)
3. 线程 :通常一个过程中能够蕴含若干个线程,一个过程中至多有一个线程。线程是 cpu 调度和执行的单位
4. 并行 :指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从宏观还是从宏观来看,二者都是一起执行的。
5. 并发:指在同一时刻只能有一条指令执行,但多个过程指令被疾速的轮换执行,使得在宏观上具备多个过程同时执行的成果,但在宏观上并不是同时执行的,只是把工夫分成若干段,使多个过程疾速交替的执行。

外围概念

  • 线程就是独立的执行门路
  • 在程序运行时,及时没有本人创立线程,后盾也会有多个线程,如主线程,gc 线程;
  • main()称之为主线程,为零碎的入口,用于执行整个程序;
  • 在一个过程中,如果开拓了多个线程,线程的行为由调度器安顿调度,调度器时与操作系统严密相干的,先后顺序时不能人为的干涉的。
  • 对同一份资源操作时,会存在资源争夺的问题,须要退出并发管制;
  • 线程会带来额定的开销,如 cpu 调度事件,并发管制开销。
  • 每个线程在本人的工作内存交户,内存管制不当会造成数据不统一。

2. 线程创立

三种创立形式:

  • Thread class====> 继承 Thread 类(重点)
  • Runnable 接口 ====> 实现 Runnable 接口(重点)
  • Callable 接口 ====> 实现 Callable 接口(理解)

Thread

  • 自定义线程类继承 Thread 类
  • 重写 run()办法,编写线程执行体
  • 创立线程对象,调用 start()办法启动线程
    不倡议应用:防止 OPP 单继承局限性
// 创立形式一:继承 Thread 类,重写 run 办法,调用 start 开启线程

public class TestThread1 extends Thread{
    @Override
    public void run() {
        //run 办法线程体
        for (int i = 0; i < 200; i++) {System.out.println("我在打游戏 -------"+i);
        }
    }

    public static void main(String[] args) {
        //main 线程,主线程

        // 创立一个线程对象
        TestThread1 testThread1 = new TestThread1();

        // 调用 start 办法, 开启线程
        testThread1.start();

        for (int i = 0; i < 200; i++) {System.out.println("我在打飞机游戏 -------"+i);
        }
    }
}

最初会发现“我在打游戏”和“我在打飞机游戏”会交替执行。
留神:线程不肯定立刻执行,由 cpu 安顿调度;

实现 Runnable

举荐应用 Runnable,防止单继承的局限性,不便同一个对象被多个线程应用

  • 定义 MyRunnable 类事项 Runnable 接口
  • 实现 run()办法,编写线程执行体
  • 创立线程对象,调用 start()办法启动线程
    买票案例
// 票数
    // 多个线程同时操作同一个对象
// 买火车票

// 发现问题,多个线程操作同一个资源的状况下,线程不平安,数据错乱
public class TestThread04 implements Runnable{

    // 票数
    private int ticketNums = 10;

    @Override
    public void run() {while (true){if (ticketNums <=0){break;}
            // 模仿延时
            try {Thread.sleep(200);
            } catch (InterruptedException e) {e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"票");
        }
    }

    public static void main(String[] args) {TestThread04 ticket = new TestThread04();

        new Thread(ticket,"小明").start();
        new Thread(ticket,"老师").start();
        new Thread(ticket,"黄牛").start();}
}
老师拿到了第 10 票
小明拿到了第 9 票
黄牛拿到了第 9 票
老师拿到了第 8 票
小明拿到了第 6 票
黄牛拿到了第 7 票
黄牛拿到了第 5 票
老师拿到了第 4 票
小明拿到了第 4 票
黄牛拿到了第 2 票
小明拿到了第 1 票
老师拿到了第 3 票

能够发现,多个线程操作同一资源可能存在并发性问题

龟兔赛跑案例

// 模仿龟兔赛跑
public class Race implements Runnable{

    // 胜利者
    private static String winner;

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

            // 模仿兔子劳动
            if (Thread.currentThread().getName().equals("兔子") && i%10==0){
                try {Thread.sleep(1);
                } catch (InterruptedException e) {e.printStackTrace();
                }
            }

            // 判断较量是否完结
            boolean flag = gameOver(i);
            // 如果较量完结了,就进行程序
            if (flag){break;}

            System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
        }
    }

    // 判断是否实现较量
    private boolean gameOver(int steps){if(winner!=null){// 曾经存在胜利者了
            return true;
        }{if (steps >= 100){winner = Thread.currentThread().getName();
                System.out.println("winner is"+winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {Race race = new Race();

        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();}
}

实现 Callable 接口(理解即可)

  1. 实现 Callable 接口,须要返回值类型
  2. 重写 call 办法,须要抛出异样
  3. 创立指标对象
  4. 创立执行服务:ExecutorService ser=Executors.newFixedThreadPool(1);
  5. 提交执行:Future result1 = ser.submit(t1);
  6. 获取后果:boolean r1 = result1.get()
  7. 敞开服务:ser.shutdownNow();

下载案例

// 线程创立形式三:实现 callable 接口
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() {WebDownLoader webDownLoader = new  WebDownLoader();
        webDownLoader.downLoader(url,name);
        System.out.println("下载了文件名为"+name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {TestCallable t1 = new TestCallable("https://i.guancha.cn/bbs/2020/07/10/20200710142345269.jpg?imageView2/2/w/500/format/jpg","1.jpg");
        TestCallable t2 = new TestCallable("https://i.guancha.cn/bbs/2020/07/10/20200710142345269.jpg?imageView2/2/w/500/format/jpg","2.jpg");
        TestCallable t3 = new TestCallable("https://i.guancha.cn/bbs/2020/07/10/20200710142345269.jpg?imageView2/2/w/500/format/jpg","3.jpg");

        // 创立执行服务
        ExecutorService ser= Executors.newFixedThreadPool(3);
        // 提交执行
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
        Future<Boolean> r3 = ser.submit(t3);
        // 获取后果
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();

        System.out.println(rs1);
        System.out.println(rs2);
        System.out.println(rs3);

        // 敞开服务
        ser.shutdownNow();}
    // 下载器
    class WebDownLoader{
        // 下载办法
        public void downLoader(String url,String name){
            try {FileUtils.copyURLToFile(new URL(url),new File(name));
            } catch (IOException e) {e.printStackTrace();
                System.out.println("io 异样,downloader 办法呈现问题");
            }
        }
    }
}

3.Lamda 表达式

  • λ 希腊字母表中排序第十一为的字母,英语名称为 Lambda
  • 防止匿名外部类定义过多
  • 其实质属于函数式编程的概念
  • jdk1.8 后新增的一种技术
(params)->expression[表达式]
  (params)->statement[语句]
    (params)->{statements}

a->System.out.println("i like lambda-->"+a);

new Thread(()->System.out.println("多线程学习。。。")).start();

为什么要应用 lambda 表达式

  • 防止匿名外部类定义过多
  • 能够让你的代码看起来很简洁
  • 去掉了一堆没有意义的代码,只留下外围的逻辑

函数式接口的定义

  • 任何接口,如果只蕴含惟一一个形象办法,那么他就是一个函数式接口。

        public interface Runnable{public abstract void run();
        }
    
  • 对于函数式接口,咱们能够通过 lambda 表达式来创立该接口的对象。

推到 lambda 表达式

/*
推到 lambda 表达式
 */

public class TestLambda1 {

    //3. 动态外部类
    static class Like2 implements ILike{
        @Override
        public void lambda() {System.out.println("i like lambda2");
        }
    }

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

        like = new Like2();
        like.lambda();

        //4. 部分外部类
        class Like3 implements ILike{
            @Override
            public void lambda() {System.out.println("i like lambda3");
            }
        }

        like = new Like3();
        like.lambda();

        //5. 匿名外部类, 没有类的名称,必须借助接口或者父类
        like = new ILike() {
            @Override
            public void lambda() {System.out.println("i like lambda4");
            }
        };
        like.lambda();

        //6. 用 lambda 简化
        ILike like1 = ()->{System.out.println("i like lambda6");
        };
        like1.lambda();}
}

//1. 定义一个函数式接口
interface ILike{void lambda();
}

//2. 实现类
class Like implements ILike{

    @Override
    public void lambda() {System.out.println("i like lambda");
    }
}

案列二

public class TestLambda2 {
    static class Love implements ILove{

        @Override
        public void love(int a) {System.out.println("i love u"+a);
        }
    }

    public static void main(String[] args) {
        //1.lambda 示意简化
        ILove love = (int a) ->{System.out.println("i love u"+a);
        };

        // 简化 1,去掉参数类型
        love = (a)->{System.out.println("i love u"+a);
        };

        // 简化 2,简化括号
        love = a->{System.out.println("i love u"+a);
        };

        // 简化 3,去掉花括号
        love = a -> System.out.println("i love u"+a);

        // 总结://lambda 表达式只能有一行代码的状况下能力简化成为一行,如果有多行,那么就用代码块包裹
            // 前提接口为函数式接口
            // 多个参数也能够去掉参数类型,要去掉就都去掉,必须加上括号

        love.love(521);

    }
}

interface ILove{void love(int a);
}

4. 动态代理

  • 你:实在角色
  • 婚庆公司:代理你,帮你解决结婚的事
  • 结婚:实现都实现结婚接口即可

案列

public class StaticProxy {public static void main(String[] args) {You you =new You();// 你要结婚

        new Thread(()-> System.out.println("我爱你")).start();

        new WeddingCompany(new You()).HappyMarry();}
}

interface Marry{
    // 世间四大喜事
        // 久旱逢甘霖
        // 他乡遇故知
        // 洞房花烛夜
        // 金榜题名时
    void HappyMarry();}

// 实在角色,你去结婚
class You implements Marry{

    @Override
    public void HappyMarry() {System.out.println("我要结婚,超开心");
    }
}

// 代理角色,帮忙你结婚
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 after() {System.out.println("结婚之后,收尾款");
    }

    private void before() {System.out.println("结婚之前,安排现场");
    }
}

5. 线程状态

五大状态

线程进行

setPriority(int newPriority): 更改线程的优先级

  • static void sleep(long millis): 在指定的毫秒数内让以后正在执行的线程休眠
  • void join():期待该线程终止
  • static void yield():暂停以后正在执行的线程对象,并执行其余线程
  • void interrupt():中断线程,别用这个形式
  • boolean isAlive():测试线程是否处于活动状态

1. 不举荐应用 JDK 提供的 stop(),destroy()办法。【已废除】

2. 举荐线程本人停止下来

3. 倡议应用一个标记位进行终止变量当 flag=false,则终止线程运行。

// 测试 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();

        new Thread(testStop).start();

        for (int i = 0; i < 1000; i++) {System.out.println("main.."+i);
            if (i == 900){
                // 调用 stop 办法,切换标记位,让线程进行
                testStop.stop();
                System.out.println("线程该进行了");
            }
        }
    }
}

线程休眠

  • sleep(工夫)指定以后线程阻塞的毫秒数
  • sleep 存在异样 InterruptedException
  • sleep 工夫达到后线程进入就绪状态
  • sleep 能够模仿网络延时,倒计时等。
  • 每一个对象都有一个锁,sleep 不会开释锁;

用 sleep 办法模仿倒计时和打印以后工夫

// 模仿倒计时
public class TestSleep2 {public static void main(String[] args) {
        // 打印以后零碎工夫
        Date startTime = new Date(System.currentTimeMillis());// 获取零碎以后工夫

        while (true){
            try {Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                startTime = 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;}
        }
    }

}

线程礼让

  • 礼让线程,让以后正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让 cpu 从新调度,礼让不肯定胜利!看 cpu 情绪
// 测试礼让线程
// 礼让不肯定胜利,看 cpu 情绪
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.currentThread().getName()+"线程开始执行");
        Thread.yield();// 礼让
        System.out.println(Thread.currentThread().getName()+"线程进行执行");
    }
}

Join

  • Join 合并线程,待此线程执行实现后,再执行其余线程,其余线程阻塞
  • 能够设想成插队
// 测试 join 办法 // 设想为插队
public class TestJoin  implements Runnable{
    @Override
    public void run() {
        try {Thread.sleep(200);
        } catch (InterruptedException e) {e.printStackTrace();
        }
        for (int i = 0; i < 100; 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 < 500; i++) {if (i == 200){thread.join();// 插队
            }
            System.out.println("main:"+i);
        }
    }
}

线程状态观测

Thread.State
线程状态。线程能够处于以下状态之一:

  • NEW:尚未启动的线程处于此状态
  • RUNNABLE:在 Java 虚拟机中执行的线程处于此状态。
  • BLOCKED:被阻塞期待监视器锁定的线程处于此状态
  • WAITING:正在期待另一个线程执行特定动作的线程处于此状态
  • TIMED_WAITING:正在期待另一个线程执行动作达到指定等待时间的线程处于此状态
  • TERMINATED:已退出的线程处于此状态

一个线程能够在给定的工夫点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。

// 察看测试线程的状态
public class TestState {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{for (int i = 0; i < 5; i++) {
                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);//Runnable

        while (state != Thread.State.TERMINATED){// 只有线程不终止,就始终输入状态
            Thread.sleep(100);
            state = thread.getState();// 更新线程状态
            System.out.println(state);
        }

    }
}

线程优先级

  • Java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器依照优先级决定应该调度哪个线程来执行。
  • 线程的优先级用数字示意,范畴从 1~10

    • Thread.MIN_PRIORITY = 1;
    • Thread.MAX_PRIORITY = 10;
    • Thread.NORM_PRIORITY = 5;
  • 应用以下形式扭转或获取优先级

    • getPriority() setPriority(int xxx)

优先级的设定倡议再 start()调度前
优先级低常识意味着取得调度的概率低,并不是优先级低就不会被调用了,这都是看 cpu 的调度。

6. 守护(daemon)线程

  • 线程分为 用户线程 守护线程
  • 虚拟机必须确保用户线程执行结束
  • 虚拟机不必期待守护线程执行结束
  • 如,后盾记录操作日志,监控内存,垃圾回收期待
// 测试守护线程
// 上帝守护你
public class TestDaemon {public static void main(String[] args) {God god = new God();
        You you = new You();

        Thread thread = new Thread(god);
        thread.setDaemon(true);// 默认是 false 示意是用户线程,失常的线程都是用户线程

        thread.start();
        new Thread(you).start();// 你 用户线程启动。。。。}
}

// 上帝
class God implements Runnable{

    @Override
    public void run() {while (true){System.out.println("上帝保佑着你");
        }
    }
}

// 你
class You implements Runnable{

    @Override
    public void run() {for (int i = 0; i < 36500; i++) {System.out.println("你毕生都开心的活着");
        }

        System.out.println("======goodbye!world============");
    }
}

7. 线程平安

线程同步

多个线程操作同一个资源
并发:同一个对象 多个线程同时操作

线程同步:

  • 现实生活中,咱们会遇到“同一个资源,多集体都想应用”的问题,比方,食堂排队打饭,每个人都想吃饭,最人造的解决办法就是,排队,一个个来。
  • 解决多线程问题是,多个线程拜访同一个对象,并且某些线程还想批改这个对象,着时候咱们就须要线程同步,线程同步其实就是一种期待机制,多个须要同时拜访此对象的线程进入这个 对象的期待池 造成队列,期待后面线程应用结束,下一个线程再应用

造成条件:队列 + 锁

锁:

  • 因为同一过程的多个线程共享同一块存储空间,在带来不便的同时,也带来了拜访抵触问题,为了保证数据在办法中被拜访时的正确性,在拜访时退出 锁机制 synchronized,当一个线程取得对象的排它锁,独占资源,其余线程必须期待,应用后开释锁即可,存在以下问题:

    • 一个线程持有锁会导致其余所有须要此锁的线程挂起;
    • 在多线程竞争下,加锁,开释锁会导致比拟多的上下文切换和调度延时,引起性能问题;
    • 如果一个优先级高的线程期待一个优先级低的线程开释锁,会导致优先级倒置,引起性能问题。

三大线程不平安案例

买票不平安

// 不平安的买票
// 线程不平安,有正数
public class UnsafeByyTicket {public static void main(String[] args) {BuyTicket station = new BuyTicket();

        new Thread(station,"我").start();
        new Thread(station,"你").start();
        new Thread(station,"黄牛").start();}
}

class BuyTicket implements Runnable{

    // 票
    private int ticketNums = 10;
    private boolean flag = true;// 内部进行形式

    @Override
    public void run() {
        // 买票
        while (flag){
            try {buy();
            } catch (InterruptedException e) {e.printStackTrace();
            }
        }
    }

    private void buy() throws InterruptedException {
        // 判断是否有票
        if (ticketNums <= 0){
            flag = false;
            return;
        }
        // 模仿延时
        Thread.sleep(100);

        // 买票
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
    }
}

取钱不平安

// 不平安的取钱
// 两个人去银行取钱,账户
public class UnsafeBank {public static void main(String[] args) {Account account = new Account(100,"结婚基金");

        Drawing you = new Drawing(account,50,"你");
        Drawing girlFriend = new Drawing(account,100,"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 drawingMoney;
    // 当初手里有多少钱
    int nowMoney;

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

    }

    @Override
    public void run() {
        // 判断有没有钱
        if (account.money-drawingMoney <0){System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
            return;
        }

        //sleep 能够放大问题的产生性
        try {Thread.sleep(1000);
        } catch (InterruptedException e) {e.printStackTrace();
        }

        // 卡余额 = 余额 - 你取的钱
        account.money =account.money-drawingMoney;
        // 你手里的钱
        nowMoney = nowMoney+drawingMoney;

        System.out.println(account.name+"余额为:"+account.money);
//        System.out.println(this.getName()+Thread.currentThread().getName());
        System.out.println(this.getName()+"手里的钱"+nowMoney);
    }
}

不平安的汇合

// 线程不平安的汇合
public class UnsafeList {public static void main(String[] args) throws InterruptedException {List<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {new Thread(()->{list.add(Thread.currentThread().getName());
            }).start();}
        Thread.sleep(5000);
        System.out.println(list.size());
    }
}

同步办法(synchronized)

  • 因为咱们能够通过 private 关键字来保证数据对象只能被办法拜访,所以咱们只须要针对办法提出一套机制,这套机制就是 synchronized 关键字,它包含两种用法:synchronized 办法和 synchronized 块
    同步办法:public synchronized void method(int args){}
  • synchronized 办法管制对“对象”的拜访,每个对象对应一把锁,每个 synchronized 办法都必须取得调用该办法的对象的锁能力执行,否则线程会阻塞,办法一旦执行,就独占该锁,直到该办法返回才开释锁,前面被阻塞的线程能力取得这个锁,继续执行
    缺点:若将一个大的办法申明为 synchronized 将会影响效率

    同步块

    • 同步块:synchronized (Obj){}
    • Obj称之为 同步监视器

      • Obj能够是任何对象,然而举荐应用共享资源作为同步监视器
      • 同步办法中无需指定同步监视器,因为同步办法的同步监视器就是 this,就是这个对象自身,或者是 class[反射中解说]
    • 同步监视器的执行过程

      1. 第一个线程拜访,锁定同步监视器,执行其中代码
      2. 第二个线程拜访,发现同步监视器被锁定,无法访问。
      3. 第一个线程拜访结束,解锁同步监视器。
      4. 第二个线程拜访,发现同步监视器没有锁,而后锁定并拜访。
### 平安的案例

// 不平安的取钱
// 两个人去银行取钱,账户
public class UnsafeBank {public static void main(String[] args) {Account account = new Account(1000,"结婚基金");

        Drawing you = new Drawing(account,50,"你");
        Drawing girlFriend = new Drawing(account,100,"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 drawingMoney;
    // 当初手里有多少钱
    int nowMoney;

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

    }

    // 取钱
    //synchronized 默认锁的是 this
    @Override
    public void run() {synchronized (account){

            // 判断有没有钱
            if (account.money-drawingMoney <0){System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
                return;
            }

            //sleep 能够放大问题的产生性
            try {Thread.sleep(1000);
            } catch (InterruptedException e) {e.printStackTrace();
            }

            // 卡余额 = 余额 - 你取的钱
            account.money =account.money-drawingMoney;
            // 你手里的钱
            nowMoney = nowMoney+drawingMoney;

            System.out.println(account.name+"余额为:"+account.money);
//        System.out.println(this.getName()+Thread.currentThread().getName());
            System.out.println(this.getName()+"手里的钱"+nowMoney);
        }

    }
}
// 不平安的买票
// 线程不平安,有正数
public class UnsafeByyTicket {public static void main(String[] args) {BuyTicket station = new BuyTicket();

        new Thread(station,"我").start();
        new Thread(station,"你").start();
        new Thread(station,"黄牛").start();}
}

class BuyTicket implements Runnable{

    // 票
    private int ticketNums = 10;
    private boolean flag = true;// 内部进行形式

    @Override
    public void run() {
        // 买票
        while (flag){
            try {buy();
            } catch (InterruptedException e) {e.printStackTrace();
            }
        }
    }

    //synchronized 同步办法,锁的是 this
    private synchronized void buy() throws InterruptedException {
        // 判断是否有票
        if (ticketNums <= 0){
            flag = false;
            return;
        }

        // 买票
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
    }
}
// 线程不平安的汇合
public class UnsafeList {public static void main(String[] args) throws InterruptedException {List<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {new Thread(()->{synchronized (list){list.add(Thread.currentThread().getName());
                }
            }).start();}
        Thread.sleep(3000);
        System.out.println(list.size());
    }
}

JUC 平安类型的汇合

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

死锁

  • 多个线程各自占有一些共享资源,并且相互期待其余线程占有的资源能力运行,而导致两个或者多个线程都在期待对方开释资源,都进行执行的状况,某一个同步块同时领有“两个以上对象的锁”时,就可能会产生“死锁”的问题

死锁防止办法

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

  • 互斥条件:一个资源每次只能被一个过程应用。
  • 申请与放弃条件:一个过程因申请资源而阻塞时,对以取得的资源放弃不放。
  • 不剥夺条件:过程以取得的资源,在未应用完之前,不能强行剥夺。
  • 循环期待条件:若干过程之间造成一种头尾相接的循环期待资源关系。

下面列出了死锁的四个必要条件,咱们只有想方法破解其中的任意一个或多个条件就能够防止死锁产生

8.Lock(锁)

  • 从 JDK5.0 开始,Java 提供了更弱小的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁应用 Lock 对象充当
  • java.util.concurrent.locks.Lock 接口是管制多个线程对共享资源进行拜访的工具。锁提供了对共享资源的独占拜访,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先取得 Lock 对象。
  • ReentrantLock 类实现了 Lock,它领有与 synchronized 雷同的并发性和内存语义,在实现线程平安的管制中,比拟罕用的是 ReentrantLock,能够显示加锁,开释锁。

synchronized 与 Lock 的比照

  • Lock 是显式锁(手动开启和敞开锁,别忘记敞开锁)synchronized 是隐式锁,出了作用域主动开释
  • Lock 只有代码块锁,synchronized 有代码块锁和办法锁
  • 应用 Lock 锁,JVM 将破费较少的工夫来调度线程,性能更好。并且具备更好的扩展性(提供更多的子类)
  • 优先应用程序

    • Lock > 同步代码块(曾经进入了办法体,调配了相应资源)> 同步办法(在办法体之外)
// 测试 Lock 锁
public class TestLock {public static void main(String[] args) {TestLock2 testLock2 = new TestLock2();

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

class TestLock2 implements Runnable{

    int ticketNums = 1000;

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

    @Override
    public void run() {while (true){
            try {lock.lock();// 加锁
                if (ticketNums > 0){
               /* try {Thread.sleep(1000);
                } catch (InterruptedException e) {e.printStackTrace();
                }*/
                System.out.println(Thread.currentThread().getName()+" "+ticketNums--);
            }else {break;}
            }finally {lock.unlock();
            }

        }
    }
}

9. 线程合作

生产者消费者模式

  • 利用场景:生产者和消费者问题

    • 假如仓库中只能寄存一件产品,生产者将生产进去的产品放入仓库,消费者将仓库中产品取走生产。
    • 如果仓库中没有产品,则生产者将产品放入仓库,否则进行生产并期待,直到仓库中的产品被消费者取走为止
    • 如果仓库中放有产品,则消费者能够将产品取走生产,否则进行生产并期待,直到仓库中再次放入产品为止。

线程通信 - 剖析

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

  • 对于生产者,没有生产产品之前,要告诉消费者期待,而生产了产品之后,又须要马上告诉消费者生产
  • 对于消费者,在生产之后,要告诉生产者曾经完结生产,须要生产新的产品以供生产。
  • 在生产者消费者问题中,仅有 synchronized 是不够的

    • synchronized 可阻止并发更新同一个共享资源,实现了同步
    • synchronized 不能用来实现不同线程之间的消息传递(通信)

线程通信

  • Java 提供了几个办法解决线程之间的通信问题

办法名

作用

wait()

示意线程始终期待,直到其余线程告诉,与 sleep 不同,会开释锁

wait(long timeout)

指定期待的毫秒数

notify()

唤醒一个处于期待状态的线程

notifyAll()

唤醒同一个对象上所有调用 wait()办法的线程,优先级别高的线程优先调度

均是 Object 类的办法,都只能在同步办法或者同步代码块中应用,否则会抛出异样 IllegalMonitorStateException

解决形式 1

并发合作模型“生产者 / 消费者模式”—> 管程法

  • 生产者:负责生产数据的模块(可能是办法,对象,线程,过程)
  • 消费者:负责解决数据的模块(可能是办法,对象,线程,过程)
  • 缓冲区:消费者不能间接应用生产者的数据,他们之间有个“缓冲区”
    生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
// 测试:生产者消费者模型 --》利用缓冲区解决:管程法

// 生产者,消费者,产品,缓冲区
public class TestPC {public static void main(String[] args) {SynContainer container = new SynContainer();

        new Productor(container).start();
        new Consumer(container).start();}
}

// 生产者
class Productor extends Thread{
    SynContainer container;

    public Productor(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().id+"只鸡");
        }
    }
}

// 产品
class Chicken{
    int id;// 产品编号

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

// 缓冲区
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.notifyAll();}

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

        // 吃完了,告诉生产者生产
        this.notifyAll();
        return chicken;
    }
}

解决形式 2

  • 并发合作模型“生产者 / 消费者模式”–> 信号灯法
package com.kuang.gaoji;

// 测试生产者消费者问题 2:信号灯法,标记位解决
public class TestPc2 {public static void main(String[] args) {TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();}
}

// 生产者 --》演员
class Player extends Thread{
    TV tv;
    public Player(TV tv){this.tv = tv;}

    @Override
    public void run() {for (int i = 0; i < 20; i++) {if (i%2 == 0){this.tv.play("高兴大本营播放着");
            }else {this.tv.play("抖音:记录美好生活");
            }
        }
    }
}

// 消费者 --》观众
class Watcher extends Thread{
    TV tv;
    public Watcher(TV tv){this.tv = tv;}

    @Override
    public void run() {for (int i = 0; i < 20; i++) {tv.watch();
        }
    }
}

// 产品 --》节目
class TV{
    // 演员表演时,观众期待
    // 观众观看,演员期待
    String voice;// 表演的节目
    boolean flag = true;

    // 表演
    public synchronized void play(String voice){if (flag!= true){
            try {this.wait();
            } catch (InterruptedException e) {e.printStackTrace();
            }
        }
        System.out.println("演员表演了"+voice);
        // 告诉观众观看
        this.notifyAll();// 告诉唤醒
        this.voice = voice;
        this.flag = !this.flag;
    }

    // 观看
    public synchronized void watch(){if (flag == true){
            try {this.wait();
            } catch (InterruptedException e) {e.printStackTrace();
            }
        }
        System.out.println("观众观看了"+voice);
        // 告诉演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

10. 线程池

  • 背景:常常创立和销毁、使用量特地大的资源,比方并发状况下的线程,对性能影响很大。
  • 思路:提前创立好多个线程,放入线程池中,应用时间接获取,应用完放回池中。能够防止频繁创立销毁、实现反复利用。相似生存中的公共交通工具。
  • 益处:

    • 进步响应速度(缩小了创立新线程的工夫)
    • 升高资源耗费(反复利用线程池中线程,不须要每次都创立)
    • 便于线程治理(…)

      • corePoolSize: 线程池的大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有工作时最多放弃多长时间后会终止。

应用线程池

  • JDK5.0 起提供了线程池相干 API:ExecutorService 和 Executors
  • ExecutorService:真正的线程池接口。常见子类 ThreadPoolExecutor

    • void execute(Runnable command):执行工作 / 命令,没有返回值,个别用来执行 Runnable
    • Futuresubmit(Callabletask):执行工作,有返回值,个别用来执行 Callable
    • void shutdown():敞开线程池
  • Executors:工具类、线程池的工厂类,用于创立并返回不同类型的线程池。
// 测试线程池
public class TestPool {public static void main(String[] args) {
        //1. 创立线程池
        //newFixedThreadPool 参数为:线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);

        // 执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //2. 敞开链接
        service.shutdown();}
}

class MyThread implements Runnable{

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

11. 总结

// 回顾总结线程的创立
public class ThreadNew {public static void main(String[] args) {new MyThread1().start();

        new Thread(new MyThread2()).start();

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

        try {Integer integer = futureTask.get();
            System.out.println(integer);
        } catch (InterruptedException e) {e.printStackTrace();
        } catch (ExecutionException e) {e.printStackTrace();
        }

    }
}

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

//2. 实现 Runnable 接口
class MyThread2 implements Runnable{

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

//3. 实现 Callable 接口
class MyThread3 implements Callable<Integer>{

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

最初

感激你看到这里,看完有什么的不懂的能够在评论区问我,感觉文章对你有帮忙的话记得给我点个赞,每天都会分享 java 相干技术文章或行业资讯,欢送大家关注和转发文章!

正文完
 0