Java教程

学习笔记2021.10.29

本文主要是介绍学习笔记2021.10.29,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

2021.10.29

静态内部类?局部内部类?

以及用接口创建对象是什么情况,为什么不这样就会矛盾?

这篇中的第一个例子的细节要在挖一下。

多线程

Lamda表达式

引入的基本理念:

image-20211029105348875

函数式接口的定义

image-20211029105436975

即lamda表达式就是针对函数式接口来进行实现的。

具体代码示例

package Thread;

public class Demo05 {

    //静态内部类
     static class Love2 implements Ilove
    {
        @Override
        public void love(int a) {
            System.out.println("我喜欢"+a);
        }
    }

    public static void main(String[] args) {
        Ilove ll =new Love();
        ll.love(10);

        ll = new Love2();
        ll.love(20);

        //局部内部类
        class Love3 implements Ilove
        {
            @Override
            public void love(int a) {
                System.out.println("我喜欢"+a);
            }
        }

        ll = new Love3();
        ll.love(30);

        ll = new Ilove() {
            @Override
            public void love(int a) {
                System.out.println("我喜欢"+a);
            }
        };

        ll.love(40);

        ///lamda表达式

        ll = (a) ->{
                System.out.println("我喜欢"+a);
        };

        ll.love(50);

    }
}

//只定义了一个方法的函数式接口
interface Ilove
{
    void love(int a);
}

//1.通过外部定义类,在main方法中创建对象调用
class Love implements Ilove
{
    @Override
    public void love(int a) {
        System.out.println("我喜欢"+a);
    }
}

这里发现还是对上面写到的各种创建类不熟悉,后面补习过后还是要记录记忆一下。

ll = a ->
    System.out.println("我喜欢"+a);

其中lamda表达式可以简化成这种形式,注意有多行时候对应加花括号和大括号就可以了。

其中箭头后的部分就是重写的内容,前面的就是形参。ll即是类创建的对象。

image-20211029112750238

线程的各种状态

image-20211029112836891

对应的还有个观测线程状态的方法,有具体实际需求时再去看使用细节吧,其实也就是调用返回一个值运行的过程。

大概的分类和运行如下图所示,后面会具体对各个状态进行呈现。相对应的对于线程各种状态设置的方法:

image-20211029112936142

线程的停止:

基本原则是不推荐手动停止,最后设置一个标志位,当其运行到指定标志时自己停止。

package Thread;//线程停止测试

public class Demo06 implements Runnable{

    private boolean flag = true;

    @Override
    public void run() {
        int i = 0;
        while(flag)
        {
            System.out.println("正在运行"+i++);
        }
    }

    //设置一个公开的方法转换标志位使线程停止

    public void stop()
    {
        this.flag = false;
    }

    public static void main(String[] args) {

        Demo06 dd = new Demo06();
        new Thread(dd).start();

        for (int i = 0; i < 5000; i++) {
            if(i==4000) {
                dd.stop();
                System.out.println("停止了");
            }
        }

    }
}

image-20211029113853788

基本就在上面代码中所示了,没啥特殊的需要说了。

线程休眠

image-20211029113934018

其实简单理解,线程sleep的方法就可以理解成java中实现延时的方法,注意以毫秒为单位即可。

在具体项目中通过引入延时可以放大问题所在,其他的就当作一个普通延时去理解即可。

线程礼让

image-20211029114320678

其实意思就是让执行态的线程回到就绪态让CPU重新选择执行一次。

package Thread;

public class Demo07 {

    public static void main(String[] args) {
        ylie yy =new ylie();

        new Thread(yy,"a").start();
        new Thread(yy,"b").start();
    }

}

class ylie implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始了");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"结束了");
    }
}

image-20211029114737924-16354792582631

此时即是没有礼让成功。

线程强制执行

image-20211029114813183

这里就不写了。。就是简单的在主线程执行的过程中join一个其他线程的示例。这里注意就算没有join也会并发执行,但是加了join后就会优先执行完加入的线程

image-20211029115021595

image-20211029115044896

线程的优先级

image-20211029115906947

同样的优先级高的不是一定先执行,只是在先执行的可能上有了更高的比重。

package Thread;//线程优先级测试

public class Demo08 {

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"---"+Thread.currentThread().getPriority());

        prio pp =new prio();

        Thread t1 = new Thread(pp);
        Thread t2 = new Thread(pp);
        Thread t3 = new Thread(pp);
        Thread t4 = new Thread(pp);

        //先设置优先级在启动
        t1.start();

        t2.setPriority(1);
        t2.start();

        t3.setPriority(4);
        t3.start();

        t4.setPriority(10);
        t4.start();

    }

}

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

image-20211029120949994

image-20211029121000868

由此可见高的不是一定会先执行。

守护线程

image-20211029115955692

第四条举的例子也都是守护线程的例子。

设置守护线程通过setDaemon(true)来实现。

反正就是记住一个观点,虚拟机要保证用户线程执行完毕,然后就不用管守护线程,跟着程序虚拟机就自然地一起结束了。

例子不想写了,也就是一个设置看结果的呈现,有兴趣翻视频。

线程同步

并发:同一个对象被多个线程同时操作。

同步的基本出发点即如下:

image-20211029153750483

形成的条件:队列+锁

锁的相关概念

image-20211029153939661

线程不完全的情况的举例:

  1. 比如说当同一个资源被多个线程同时去争抢的时候,首先可能会导致多个线程抢到了同一次。
  2. 因为每个内存都存在自己的工作内存,会导致在资源最后的时候也会同时执行超过了资源的限制。即比如下面的抢票程序会出现-1的情况。

image-20211029154400284

image-20211029160109806

  1. sleep放大问题发生性的体现就在于可以让多个线程同时达到执行对资源的操作的步骤,弱化了程序编写上的先后顺序。
  2. 简单理解,线程不安全就是在极短的时间内,由于资源中的数据来不及更新,会导致线程的判断条件起不了作用,然后在下一时刻的同时操作导致了问题出现。

image-20211029155107240

集合的不安全示例,也可以理解成由于太快导致多个线程添加到了集合中的同一个位置。

同步方法及同步块

image-20211029155329059

缺陷的具体体现即在于因为只有对资源进行修改的代码才需要锁的机制,所以当代码只是只读的类型的时候,则会造成一定的浪费。

加了同步方法的代码:

package Thread;

public class Demo09 {

    public static void main(String[] args) {
        Buy bb =new Buy();

       Thread t1 = new Thread(bb,"yi");
       Thread t2 = new Thread(bb,"er");
       Thread t3 = new Thread(bb,"san");

       t1.start();
       t2.start();
       t3.start();
    }


}


class Buy implements Runnable
{
    private int ticnum = 10;
    boolean flag = true;

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

    }

    //该方法变成了同步方法,锁的是定义该方法的类,即this
    private synchronized void buy() throws InterruptedException {
        if (ticnum<=0) {
            flag = false;
            return;
        }

        Thread.sleep(500);
        System.out.println(Thread.currentThread().getName()+"拿到了"+ticnum--);
    }
}

结果:

image-20211029160410131

此时包括结果出现的过程也可以发现是完全按顺序进行,并且也不会资源越界。

然后这里说明一下锁的对象的选择和方法的使用:

  • 锁的对象必须就是变化的量所在的对象,该量即是用来增删改的量,至少在这几个例子里面都是用于判断结束的量。
  • 默认锁的对象是当前类,当要锁不是当前类时,则调用同步块,括号中写目标对象,然后函数体照常写即可。
  • 注意,当且仅当在同步方法或者同步方法块内部时才会持有该对象的锁,只要一出这个范围,就可以理解成锁被释放了,这对于后面的死锁问题助于理解。

这里额外提一嘴即是JUC中提供了一些现成的安全的集合,即不用通过写同步方法的标志也可以实现线程的安全问题,后面到并发的时候再细说。

死锁

image-20211029161125064

package Thread;//死锁的示例

public class Demo10 {

    public static void main(String[] args) {
        Makeup m1 = new Makeup(0,"one");
        Makeup m2 = new Makeup(1,"two");

        Thread t1 =new Thread(m1);
        Thread t2 =new Thread(m2);

        t1.start();
        t2.start();
    }
}


class Lip
{

}

class mirro
{

}

class Makeup implements Runnable
{
    private int chio;
    private String name;
    static Lip ll = new Lip();
    static mirro mm = new mirro();

    public Makeup(int chio, String name) {
        this.chio = chio;
        this.name = name;
    }

    @Override
    public void run() {

        try {
            make();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void make() throws InterruptedException {
        if (chio == 0)
        {
            synchronized (ll)
            {
                System.out.println(this.name+"有L锁");
                Thread.sleep(1000);
                synchronized (mm)
                {
                    System.out.println(this.name+"有M锁");
                }
            }
        }
        else
        {
            synchronized (mm)
            {
                System.out.println(this.name+"有M锁");
                Thread.sleep(1000);
                synchronized (ll)
                {
                    System.out.println(this.name+"有L锁");
                }
            }
        }
    }
}

image-20211029162708030

执行后即会发现会一直卡在这个状态不继续执行下去。

修正的方法:

private void make() throws InterruptedException {
    if (chio == 0)
    {
        synchronized (ll)
        {
            System.out.println(this.name+"有L锁");
            Thread.sleep(1000);
        }
        synchronized (mm)
        {
            System.out.println(this.name+"有M锁");
        }
    }
    else
    {
        synchronized (mm)
        {
            System.out.println(this.name+"有M锁");
            Thread.sleep(1000);

        }
        synchronized (ll)
        {
            System.out.println(this.name+"有L锁");
        }
    }
}

image-20211029162818576

即就如上面所说,只要出了同步方法块,就相当于解除了锁,此时就不会达成互相一直持有这么一个状态了。

最后再总结一下:

image-20211029162940267

Lock锁

image-20211029163017292

理解成新版本提供的更加方便的实现方式即可。

具体的使用示例(用前面的例子):

package Thread;//lock的示例import java.util.concurrent.locks.ReentrantLock;public class Demo11 {    public static void main(String[] args) {        Buyy bb =new Buyy();        Thread t1 = new Thread(bb,"yi");        Thread t2 = new Thread(bb,"er");        Thread t3 = new Thread(bb,"san");        t2.start();        t1.start();        t3.start();    }}class Buyy implements Runnable{    private int ticnum = 10;    boolean flag = true;    private final ReentrantLock lock = new ReentrantLock();    @Override    public void run() {        try{            lock.lock();            while(flag) {                try {                    buy();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }        finally {            lock.unlock();        }    }    private  void buy() throws InterruptedException {        if (ticnum<=0) {            flag = false;            return;        }        Thread.sleep(500);        System.out.println(Thread.currentThread().getName()+"拿到了"+ticnum--);    }}

同步方法和lock锁的区别与联系:

image-20211029163730349

具体的区别简单理解就是可以指定锁的区域,即在这个区域内执行的过程是要严格加锁的。

线程协作

物理模型即是通过中间资源作为媒介,生产者生产和消费者使用的这么一个过程。

因此自然引入了线程通信及其分析:

image-20211029164135141

Java自己提供的用于解决线程间通信的方法:

image-20211029164259930

解决方法1 管程法

image-20211029164358491

emm这种方法知道有这么个概念就可以了,反正就是以中间的缓冲区作为媒介和判断,进而实现两个对象之间的通信。

解决方法2 信号灯法

image-20211029170201012

image-20211029170205403

其中表演和观看就分别对应着生产者和消费者的方法。

这里需要注意的点:

  • wait的意思是让线程处于等待而不是退出,是让出控制权等待下一次的执行。
  • 而notifyall则就是让所有处于等待的进入就绪态进而执行。这两个与flag标志位配合使用的情况大概也就如上图所示。
  • 到这里了要明白线程、对象、执行者等概念具体的处理位置。实际处理时也要分清楚。

线程池

image-20211029171110559

image-20211029171344268

代码示例:

package Thread;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;//线程池的测试public class Demo13 {    public static void main(String[] args) {        //1.创建服务,创建线程池        ExecutorService ee = Executors.newFixedThreadPool(10);        //2。将线程放入池子中并执行,丢入的是实现Run的接口就可        ee.execute(new myth());        ee.execute(new myth());        ee.execute(new myth());        ee.execute(new myth());        //关闭连接        ee.shutdown();    }}class myth implements Runnable{    @Override    public void run() {        System.out.println(Thread.currentThread().getName());    }}

image-20211029171721490

这篇关于学习笔记2021.10.29的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!