多线程(解决线程安全的3方法,Callable,Lambda)

🌟线程与进程
进程是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程是进程中的一个执行路径,共享一个内存空间,一个进程最少有一个线程,是对进程进一步划分
✨当进程里面一个线程都没有时,该进程就被关闭了
例如:一个QQ音乐进程,拥有听歌显示图片刷评论等多个线程

分时调度:
为了能让每个线程均分CPU,一千多个线程快速轮流使用CPU的使用权,交替执行,给人造成一个假象好像多个任务同时在进行一样

Java使用的调度机制——抢占式调度:
哪个线程优先级高就先抢到CPU使用权概率大,优先级可以被设置

🌟同步与异步
同步:执行某件事情排队执行(效率低但安全)
异步:同时执行(效率高但数据不安全)可能会导致数据错乱

🌟并发与并行
并发:指定时间段内发生(一天的并发量,一秒的并发量是多少)
并行:指两个或多个事件同时发生

🌟两种代码实现多线程的方式:
1.Thread 线程类,任何要被执行的线程类都要继承Thread类,重写run()
2.线程类中的run() 方法就是线程要实行的任务方法
主方法中的代码顺序执行,走着走着,出现了一个多线程,多线程被执行时要调用Thread类中的start() 方法交替被运行(看谁先抢到CPU)

public static void main(String[] args){
MyThread m = new MyThread();
m.start();
    for(int I =0;i<10;i++){
        System.out.println(“汗滴禾下土”+i);
}

public class MyThread extends Thread{
//run 方法就是线程要执行的任务方法
public void run(){
    //这里的代码 就是一体阿信的执行路径
    //这个执行路径的处理方式不是调用run()方法,而是通过thread对象的start()来启动任务
    for(int I =0;i<10;i++){
        System.out.println(“锄禾日当午”+i);
    }
  }
}

⚠️注意:每个线程都有自己的栈空间,共用一个堆内存。子线程当中的任务方法都在子线程中运行,即在自己的栈空间中入栈和弹栈

✨实现Runnable接口任务

public static void main(String[] args){
//实现Runnable 
//1.创建一个任务对象
MyRunnable r = new MyRunnable();
//2.创建一个线程,并为其分配一个任务
Thread t =new Thread(r);
//3.执行这个线程
t.start();
for(int I =0;i<10;i++){
    System.out.println(“疑是地上霜”+i);
    }
}
public class MyRunnable implements Runnable{
    public void run(){
    //线程的任务
    for(int i =0;i<10;i++){
        System.out.println(“床前明月光”+i);
     }
  }
}

✨实现Runnable 与 继承Thread相比有如下优势
1.通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况
2.避免单继承的局限性
3.任务与线程分身是分离的,提高了程序的健壮性
4.后续学习的线程池技术,接受Runnable类型的任务,不接收Thread类型的线程

🌟Thread方法结束不能用stop(),这是已过时的方法,用变量,当变量是1可以执行,如果变量改变,变成-1,return方法停止线程
✨Thread类常用的方法有给线程设置优先级,sleep();休眠等

🌟获取当前运行线程对象/名称

public static void main(String[] args){
System.out.println(Thread.currentThread().getName());
Thread t = new Thread(new MyRunnable());
t.setName();
t.start();
new Thread(new MyRunnable(),name:”锄禾日当午”).start();
new Thread(new MyRunnable()).start();

}
static class MyRunnable implements Runnable{
    public void run(){
        System.out.println(Thread.currentThread().getName());
}
}
上述代码打印结果:
main
Thread-2
Thread-1
Thread-0

🌟线程的休眠 sleep()可以指定时间休眠

public static void main(String[] args){
    for(int i=0;i<10;i++){
        System.out.println(i);
        Thread.sleep(1000); //sleep()方法是静态类,直接由Thread类调用即可,内部放毫秒值,每打印一次就休息1秒
    }
}

🌟线程阻塞
所有消耗时间的操作都称为线程阻塞,又称为耗时操作
如:等待用户输入,线程是不往下走的,一定要等用户输入完毕才会继续执行

🌟线程中断
一个线程是一个独立的执行路径,它是否应该结束,应该有其自身决定stop()不能使用了因为外部掐死了线程,现在给线程打一个中断标记
代码示例:

public static void main(String[] args){
Thread t1 = new Thread(new MyRunnable());
t1.start();

   for(int i=0;i<5;i++){
        System.out.println(i);
	try{
        Thread.sleep(1000);
}catch(InterruptedException e){
	e.printStackTrace();}
        }

//给线程t1添加中断标记,在线程等待或休眠时会审查是否有标记,有中断标记会抛出一个异常,提醒程序员是否要中断线程
t1.interrupt();
}

static class MyRunnable implements Runnable{
 public void run(){
     for(int i=0;i<10;i++){
        System.out.println(i);
	try{
        Thread.sleep(1000);
}catch(InterruptedException e){
	//e.printStackTrace();
        System.out.println(“发现了标记,现在停止此线程”);
	return;
	}
        }
    }
}

🌟守护线程
线程分为守护线程和用户线程
用户线程:当一个进程不包含任何的存活的用户线程时,进程结束
守护线程:守护用户线程,当最后一个用户线程结束时,所有守护线程自动死亡,用户进程即将关闭的时间内守护线程还没有死
代码示例:

public static void main(String[] args){
Thread t1 = new Thread(new MyRunnable());
//设置t1为守护线程,当main方法结束时,t1线程自动死亡
t1.setDaemon(true);
t1.start();

   for(int i=0;i<5;i++){
        System.out.println(i);
	try{
        Thread.sleep(1000);
}catch(InterruptedException e){
	e.printStackTrace();}
        }


}

static class MyRunnable implements Runnable{
 public void run(){
     for(int i=0;i<10;i++){
        System.out.println(i);
	try{
        Thread.sleep(1000);
}catch(InterruptedException e){
	//e.printStackTrace();
        System.out.println(“发现了标记,现在停止此线程”);
	return;
	}
        }
    }
}

🌟解决线程安全问题
✨方案1:同步代码块
格式:

synchronized(锁对象){
              //锁对象可以是万物,无意义,只用来告诉代码被这个东西锁住了,打上个标记
}
public class test {
    public static void main(String[] args){
        Runnable t = new Ticket();
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
    }



}
public static  class Ticket implements Runnable{
    //声明票数
    private int count =10;
    //创建锁对象
    private Object o = new Object();


    public void run(){
        while(true){
            synchronized(o){
                if(count>0){
                    System.out.println("正在出票...");
                    try{
                        Thread.sleep(1000);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
                }
            }
        }
    }

}

✨方案2:同步方法
格式:修饰符 synchronized 返回值类型 方法名(参数列表){
访问了共享数据的代码
}
使用步骤:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2.在方法上添加synchronized 修饰符

public class test {
    public static void main(String[] args){
        RunnableImpl r = new Runnable();
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();
    }
}

public static  class RunnableImpl implements Runnable{
    //声明票数
    private int count =10;

//设置线程卖票
    public void run(){
//使用死循环,让卖票操作重复执行
        while(true){
		payTicket();
        }
    
//定义一个同步方法:
 public Synchronized void payTicket(){
        if(count>0){
            System.out.println("正在出票...");
            try{
                Thread.sleep(1000);
           }catch(InterruptedException e){
                e.printStackTrace();
           }
           count--;
           System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
      }
}

✨方案3:显式锁Lock
前两种方案都是隐式锁

public class test {
    public static void main(String[] args){
        Runnable t = new Ticket();
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
    }



}
public static  class Ticket implements Runnable{
    //声明票数
    private int count =10;
    //创建锁对象
    Lock l = new ReentrantLock();//1.自己创建锁对象


    public void run(){
        while(true){
            l.lock(); //2.自己锁🔒显式锁
                if(count>0){
                    System.out.println("正在出票...");
                    try{
                        Thread.sleep(1000);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
                }
            l.unlock();//3.解锁🔒显式锁
        }
    }

}

🌟公平锁和非公平锁
公平锁是排队谁先来给谁,非公平锁是大家一起抢使用权。
以上学习的三种锁,也就是Java中的三种锁都是非公平锁,而Lock显式锁🔒的参数设置为true就变成了公平锁:
Lock l = new ReentrantLock(true);

🌟线程死锁
对峙状态,警察与罪犯对峙,为避免死锁状态在一个方法中启动一个锁时不要再调用另一个锁

🌟多线程通信问题
生产者与消费者问题,A在下载,B在等待,A下载完毕叫醒B的过程
交替进行
等待与唤醒案例

🌟线程的六种状态
New 该开始创建未运行时的状态
Runnable 运行时的状态
Blocked 阻塞状态,排队是的状态
Waiting 无限休眠等待的状态,被唤醒回到Runnable状态
TimeWaiting 定时休眠等待自动唤醒状态,回到Runnable状态
Terminated 线程死亡状态

🌟带返回值的线程Callable
🚩第三种创建线程的方法
有两种创建线程的方法-一种是通过创建Thread类,另一种是通过使用Runnable创建线程。但是,Runnable缺少的一项功能是,当线程终止时(即run()完成时),我们无法使线程返回结果。为了支持此功能,Java中提供了Callable接口。
为了实现Runnable,需要实现不返回任何内容的run()方法,而对于Callable,需要实现在完成时返回结果的call()方法。请注意,不能使用Callable创建线程,只能使用Runnable创建线程。
另一个区别是call()方法可以引发异常,而run()则不能。
为实现Callable而必须重写call方法。

当call()方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。为此,可以使用Future对象。将Future视为保存结果的对象–它可能暂时不保存结果,但将来会保存(一旦Callable返回)。因此,Future基本上是主线程可以跟踪进度以及其他线程的结果的一种方式。要实现此接口,必须重写5种方法,但是由于下面的示例使用了库中的具体实现,因此这里仅列出了重要的方法。
public boolean cancel(boolean mayInterrupt):用于停止任务。如果尚未启动,它将停止任务。如果已启动,则仅在mayInterrupt为true时才会中断任务。
public Object get()抛出InterruptedException,ExecutionException:用于获取任务的结果。如果任务完成,它将立即返回结果,否则将等待任务完成,然后返回结果。
public boolean isDone():如果任务完成,则返回true,否则返回false
可以看到Callable和Future做两件事-Callable与Runnable类似,因为它封装了要在另一个线程上运行的任务,而Future用于存储从另一个线程获得的结果。实际上,future也可以与Runnable一起使用。
要创建线程,需要Runnable。为了获得结果,需要future。
Java库具有具体的FutureTask类型,该类型实现Runnable和Future,并方便地将两种功能组合在一起。
可以通过为其构造函数提供Callable来创建FutureTask。然后,将FutureTask对象提供给Thread的构造函数以创建Thread对象。因此,间接地使用Callable创建线程。

🌟线程池概述
很优秀,可以重复使用,有时很闲,给到任务时很忙,定长线程池长度固定,没有被完成的任务会排队等待线程A闲下来,再去完成

✨缓存线程池(长度无限制)
任务加入后的流程:
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在,则创建线程,并放入线程池,然后使用

package thread;

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

public class test {
    public static void main(String[] args){
        //创建一个缓存线程池
        ExecutorService service = Executors.newCachedThreadPool();
        //指挥线程池执行新的任务
        service.execute(new Runnable(){
            public void run(){
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        service.execute(new Runnable(){
            public void run(){
                System.out.println(Thread.currentThread().getName()+"h汗滴禾下土");
            }
        });
        service.execute(new Runnable(){
            public void run(){
                System.out.println(Thread.currentThread().getName()+"谁知盘中餐");
            }
        });
        try{
            Thread.sleep(1000);
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        service.execute(new Runnable(){
            public void run(){
                System.out.println(Thread.currentThread().getName()+"谁知盘中餐");
            }
        });

    }

}

结论:缓存线程池中主线程休眠一秒,任务会被给到已创建了的空闲线程执行

✨定长线程池:长度固定,不允许在扩容
任务加入后的流程:
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在空闲线程,且线程池未满,创建线程并放入线程池再使用
4.不存在空闲线程,且线程池已满,等待线程池存在空闲线程

public class test {
    public static void main(String[] args){
        //创建一个定长线程池,固定长度给2
        ExecutorService service = Executors.newFixedThreadPool(2);
        //指挥线程池执行新的任务
        service.execute(new Runnable(){
            public void run(){
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });

与缓存线程池一样,每执行一个任务休眠3秒钟,任务会排队给到线程池中空闲下来的线程去执行

✨单线程线程池:任务均由一个线程去执行
执行流程:
1.判断线程池的那个线程 是空闲
2.空闲则使用
3.不空闲则等待,池中的单个线程空闲后 使用

public class test {
   public static void main(String[] args){
        //创建一个定长线程池,固定长度给2
   ExecutorService service = Executors.newSingleThreadExecutor();
        //指挥线程池执行新的任务
        service.execute(new Runnable(){
            public void run(){
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });

✨周期定长线程池:
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在空闲线程,且线程池未满,创建线程并放入线程池再使用
4.不存在空闲线程,且线程池已满,等待线程池存在空闲线程

周期性任务执行时:
定时执行,当某个时机触发时,自动执行某任务

创建1:定时执行一次任务

public class test {
   public static void main(String[] args){
        //创建一个定长线程池,固定长度给2
ScheduledExcecutorService service=Executors.newScheduledThreadPool(2);
        //1.定时执行一次:3个参数
		参数1:定时执行的任务
		参数2:时长数字
		参数3:时长数字的时间单位,TimeUnit的常量指定(如:秒、分)
        service.schedule(new Runnable(){
            public void run(){
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        },delay:5,TimeUnit.SECONDS);

创建2:周期执行一次任务

public class test {
   public static void main(String[] args){
        //创建一个定长线程池,固定长度给2
ScheduledExcecutorService service=Executors.newScheduledThreadPool(2);
        //2.周期执行:4个参数
		参数1:任务
		参数2:延迟时长数字(第一次执行在什么时间以后)
		参数3:周期时长数字(每隔多久执行一次)
		参数4.时长数字的单位
        service.scheduleFixedRate(new Runnable(){
            public void run(){
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        },initialDelay:5,period:1,TimeUnit.SECONDS);

🌟Lambda表达式
格式:
(参数列表)->(一些方法的代码);
解释格式:
():接口中的抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数使用逗号分隔
->:传递的意思,把参数传递给方法体{}
{}:重写接口的抽象方法

✨无参数返回值的练习

public class test {
    public static void main(String[] args) {

        //调用invokeCook方法,参数是Cook接口,传递Cook接口的匿名内部类
        invokeCook(new Cook() {
            public void makeFood() {
                System.out.println("吃饭了");
            }
        });

        //使用Lambda比阿大使,简化匿名内部类
        invokeCook(()->{
            System.out.println("吃饭了");
        });
    }
    
    //定义一个方法,参数传递Cook接口,方法内部调用Cook接口中的方法makeFood
    public static void invokeCook(Cook cook){
        cook.makeFood();
    }
    public interface Cook{
        void makeFood();
    }

✨Lambada有参数练习
例1 需求:
使用数组存储多个Person对象
对数组中的Person对象使用Array的sort方法通过年龄进行升序排序

代码实现:

package main;

import Person.Person;

import java.util.Arrays;
import java.util.Comparator;

public class Test {
    public static void main(String[] args) {
        //使用数组存储多个Person对象
        Person[] arr = {
                new Person("柳岩",38),
                new Person("迪丽热巴",18),
                new Person("古力娜扎",19)
        };
        //对数组中的Person对象使用Arrays的sort()方法通过年龄进行升序(前边-后边)排序
        /*Arrays.sort(arr,new Comparator<Person>(){
            public int compare(Person o1,Person o2){
                return o1.getAge()-o2.getAge();
            }
        });*/

        //使用Lambda表达式,简化匿名内部类
        //因为想要简化的内部类对象有参数,
        Arrays.sort(arr,(Person o1,Person o2)->{
            return o1.getAge()-o2.getAge();
        });
        //遍历数组
        for(Person p:arr){
            System.out.println(p);
        }
    }
}

//打印结果则是按照年龄顺序排列的

✨Lambada有参数有返回值的练习
例2 需求:
给定一个计算器Calculator接口,内含抽象方法calc可以讲两个int数字相加得到和值,使用Lambda的标准格式调用invokeCalc方法,完成120和130的相加计算
需求解析:
1.定义一个方法
2.参数传递两个int类型的整数
3.参数传递Calculator接口
4.方法内部调用Calculator中的方法calc计算两个整数的和

接口:

package main;
//给定一个计算器Calculator接口,内含抽象方法calc可以将两个int数字相加得到和值
public interface Calculator {
    //定义一个计算两个int整数和的方法,并返回结果
    public abstract int calc(int a,int b);
}

测试类:
package main;

public class Test{
    public static void main(String[] args) {
        //调用invokeCalc方法,方法的参数是一个接口,可以使用匿名内部类
        invokeCalc(10,20,new Calculator(){
            public int calc(int a,int b){
                return a+b;
            }
        });

        //使用invokeCalc方法,方法的参数是一个接口,可以使用匿名内部类
        invokeCalc(120,130,(int a,int b)->{
            return a + b;
        });
    }
    /**
     * 定义一个方法
     * 参数传递两个int类型的整数
     * 参数传递Calculator接口
     * 方法内部调用Calculator中的方法calc计算两个整数的和
     */

    public static void invokeCalc(int a,int b,Calculator c){
        int sum = c.calc(a,b);
        System.out.println(sum);
    }
}

✨Lambda表达式:可推导,可以省略
凡是根据上下文推导出来的内容,都可以省略不写
可以省略的内容:
1.(参数列表):括号中的参数列表的数据类型,可以省略不写
2.(参数列表):括号中的参数如果只有一个,那么类和()都可以省略
3.(一些代码):如果()中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)。注意:要省略{},return,分号必须一起省略

如:

1.new Thread(()->{
    System.out.println(“Thread.currentThread().getName()+”新线程建立了”)}).start();

优化省略lambda:
new Thread(
()->System.out.println(“Thread.currentThread().getName()+”新线程建立了”)).start();

2.invokeCook(()->{System.out.println(“吃饭了”)});
优化省略Lambda:
invokeCook(()->System.out.println(“吃饭了”));

3.Arrays.sort(arr.(Person o1,Person o2)->{
    Return o1.getAge()-o2.getAge();
});
优化省略Lambda:
Arrays.sort(arr,(o1,o2)->o1.getAge()-o2.getAge());

4.invokeCalc(120,130(int a, int b)->){
    return a+b;
}
invokeCalc(120,130,(a,b)->a+b);
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值