多线程学习(上)

多线程

一、实现方法

1、子类继承Thread类

  • 子类继承Thread类具备多线程能力

  • 启动线程:子类对象.start()

  • 不建议使用:避免oop单继承局限性

package com.yang.demo01;


// 创建线程方式一:继承thread类,重写run()方法,调用start开启线程

// 总结:注意,线程开启不一定立即执行,由cpu调度执行
public class TestThread1 extends Thread{
    @Override
    public void run() {
        // run()方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码---" + i);
        }
    }

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

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

        // 调用start()方法
        testThread1.start();

        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习多线程" + i);
        }
    }

}

2、实现Runnable接口

  • 实现接口Runnable具有多线程 能力
  • 启动线程:传入目标对象+Thread对象.start()
  • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
package com.yang.demo01;

// 创建线程方式2:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法。

public class TestThread3 implements Runnable{
    @Override
    public void run() {
        // run()方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码---" + i);
        }
    }

    public static void main(String[] args) {
        //创建runnable接口实现类对象
        TestThread3 testThread3 = new TestThread3();

        //创建线程对象,通过线程对象来开启我们的线程,代理
        new Thread(testThread3).start();


        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习多线程" + i);
        }
    }
}

3、实现Callable接口(了解)

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

  2. 重写call方法,需要抛出异常

  3. 创建目标对象

  4. 创建执行服务:

    ExecutorService ser = Exectutors.newFixedThreadPool(1);
    
  5. 提交执行:

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

    boolean r1 = result1.get()
    
  7. 关闭服务

    ser.shutdownNow();
    

    例子

    package com.yang.demo02;
    
    
    import com.yang.demo01.TestThread2;
    import org.apache.commons.io.FileUtils;
    
    import java.io.File;
    import java.io.IOException;
    import java.net.URL;
    import java.util.concurrent.*;
    
    // 线程创建方法三: 实现callable接口
    
    /*
    callable的好处
    1、可以定义返回值
    2、可以抛出异常
     */
    
    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://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","baidu1.png");
            TestCallable t2 = new TestCallable("https://pics6.baidu.com/feed/8ad4b31c8701a18bc6763047027627012938feff.jpeg?token=07aff6155ccd172ada2ed24269656b48","baidu2.png");
            TestCallable t3 = new TestCallable("https://pics7.baidu.com/feed/d01373f082025aafd934f5676eb48b6d024f1afe.png?token=153640a94d130ca0904fd51038d857d8","baidu3.png");
            TestCallable t4 = new TestCallable("https://pics0.baidu.com/feed/bd315c6034a85edf24708ba9dd0d292adc54751a.jpeg?token=f8e1bfb63400e6e4debd4b3e25625ba9","baidu4.png");
    
            // 创建执行服务
            ExecutorService ser = Executors.newFixedThreadPool(4);
            // 提交执行
            Future<Boolean> r1 = ser.submit(t1);
            Future<Boolean> r2 = ser.submit(t2);
            Future<Boolean> r3 = ser.submit(t3);
            Future<Boolean> r4 = ser.submit(t4);
            // 获取结果
            boolean rs1 = r1.get();
            boolean rs2 = r2.get();
            boolean rs3 = r3.get();
            boolean rs4 = r4.get();
    
            System.out.println(rs1);
            System.out.println(rs2);
            System.out.println(rs3);
            System.out.println(rs4);
            // 关闭服务
            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方法出现问题");
            }
        }
    }
    

二、并发问题

1、初识并发问题

package com.yang.demo01;

// 多个线程同时同时操作同一个对象
// 买火车票的例子

// 发现问题:多个线程在操作同一个资源的情况下,线程不安全,数据紊乱
public class TestThread4 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) {
        TestThread4 testThread4 = new TestThread4();

        new Thread(testThread4,"老王").start();
        new Thread(testThread4,"老杨").start();
        new Thread(testThread4,"老黄牛").start();

    }
}

问题:

多个线程在操作同一个资源的情况下,线程不安全,数据紊乱

老杨-->拿到了第10票
老王-->拿到了第9票
老黄牛-->拿到了第10票
老杨-->拿到了第8票
老黄牛-->拿到了第6票
老王-->拿到了第7票
老黄牛-->拿到了第5票
老杨-->拿到了第5票
老王-->拿到了第5票
老黄牛-->拿到了第4票
老杨-->拿到了第4票
老王-->拿到了第3票
老黄牛-->拿到了第2票
老杨-->拿到了第1票
老王-->拿到了第0票

三、静态代理模式


// 静态代理模式总结:
// 真实对象和代理对象都要实现同一接口
// 代理对象必须代理真实角色

// 好处:
    // 代理对象可以做很多真实对象做不了的事情
    // 真实对象专注做自己的事情

public class StaticProxy {

    public static void main(String[] args) {

        You you = new You();  // 你要结婚
        // 多线程
        new Thread(()-> System.out.println("我爱你!")).start();
        // 静态代理
        new WeddingCompany(you).HappyMarry();

    }
}


interface Marry{
    // 人间四大喜事
        //久旱逢甘霖
        //他乡遇故知
        //洞房花烛夜
        //金板题名时

    void HappyMarry();
}

// 真实角色,你去结婚
class You implements Marry{
    @Override
    public void HappyMarry() {
        System.out.println("我要结婚了,超开心");
    }
}

// 代理角色,帮助你结婚
class WeddingCompany implements Marry{

    // 代理谁-->真实目标角色
    private final 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("结婚之前,布置现场");
    }
}

四、Lamda表达式

  • λ希腊字母表排序第十一位,英语名称Lambda
  • 避免过多定义
  • 其实质属于函数式编程的概念
(params) -> expression [表达式]
    (params) -> statement [语句]
    	(params) -> {statements}
a -> System.out.println("i like lambda-->" + a)
    new Thread(()->System.out.println("多线程学习!!")).start();
  • 为什么使用Lambda表达式
    • 避免匿名内部类定义过多
    • 可以让你的代码看起来更整洁
    • 去掉一堆没有意义的代码,只留下核心逻辑

用多看多,看习惯就会觉得代码变得简洁

  • 理解Functional Interface(函数式接口)是学习java8 lambda表达式的关键所在。

  • 函数式接口的定义

    • 任何接口,如果只包含唯一一个抽象方法,那么他就是一个函数式接口。
    public interface Runnable{
        public abstract void run();
    }
    
    • 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。

Lambda表达式推导

  • 一步步简化
package com.yang.lambda;

/*
推导Lambda表达式
 */
public class TestLabda {

    // 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简化
        like = () ->{
            System.out.println("i like lambda5!");
        };
        like.lambda();

    }
}

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

// 2、实现类
class Like implements ILike{
    @Override
    public void lambda() {
        System.out.println("i like lambda!");
    }
}
package com.yang.lambda;

public class TestLambda2 {

    public static void main(String[] args) {
        ILove love = (int a, int b) -> {
            System.out.println("i love you-->"+a);
        };

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

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

        // 总结
            // lambda表达式只能有一行代码的形式才能简化成一行,如果有多行,那么就用代码快包裹。
            // 前提是函数式接口。
            // 多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号。
        love.Love(521,502);
    }
}


// 1、定义接口
interface ILove{
    void Love(int a ,int b);
    }



五、线程状态

1、五大状态

  • 创建状态

    Thread t = new Thread()
    

    线程对象一旦创建就进入到新生状态

  • 就绪状态

    当调用start()方法,线程立即进入就绪状态,但不意味着立即调度执行

  • 运行状态

    进入运行状态,线程才是真正的执行线程体的代码块

  • 阻塞状态

    当调用sleep,wait或同步锁定时,线程进入阻塞状态,就是代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待cpu调度执行。

  • 死亡状态

    线程中断或者结束,一旦进入死亡状态,就不能再次启动

2、线程方法

setPriority(int newPriority)	//更改线程的优先级
static void sleep(long millis)	//在指定的毫秒数内让当前正在执行的线程休眠
void join()	//等待改线程终止
static void yield()	//暂停当前正在执行的线程对象,并执行其他线程
void interrupt()	//中断线程,别用这个方式
bollean isAlive()	//测试线程是否处于活动状态

停止线程

  • 不推荐使用jdk提供的stop()、destroy()方法。【已废弃】
  • 推荐线程自己停下来
  • 建议使用一个标志位进行终止变量,当flag= false,则终止线程运行。
package com.yang.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();
        new Thread(teststop).start();

        for (int i = 0; i < 101; i++) {
            System.out.println("run....main"+ i);
            if (i == 100){
                // 调用stop方法切换标志位,让线程停止
                teststop.stop();
                System.out.println("线程停止了!");
            }

        }
    }
}

守护线程

守护(daemon)线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
    • 如,后台记录操作日志,监控内存,垃圾回收等待..
package com.yang.state;


// 测试守护线程
// 上帝守护你
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!======");  //Hello,World!
    }
}

六、线程同步机制

多个线程操作同一个线程

  • 并发:同一个对象多个线程同时操作
    • 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这个时候我们就要同步线程,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
  • 队列和锁
    • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
      • 一个线程持有锁会导致其他所有需要此锁的线程挂起;
      • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,比起性能问题;
      • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。

谢谢秦疆老师无私奉献,笔记为做完,有时间会补上的。
附上老师视频链接,欢迎大家前往学习!
【狂神说Java】多线程详解

posted @ 2022-03-28 14:38  江小豚  阅读(33)  评论(0)    收藏  举报