Java多线程

1.1 线程概述

进程:每个进程都有独立的代码和数据空间,进程是资源分配的最小单位,每个独立的程序占有一个进程

线程:表示程序的执行流程,是CPU调度执行的基本单位,同一类线程共享代码和数据空间,进程是线程的容器,即一个进程包含1+n个线程

多线程:每个线程完成一个功能,并与其他线程在同一个进程中并发执行,这种机制被称为多线程

 

多线程的优点:让程序更好的利用系统资源,不但能更好的利用系统的空闲时间,还能快速对用户的请求作出响应,使程序的运行效率大大提高,也增加了程序的灵活程度;最重要的是,可通过多线程技术解决不同任务之间的协调操作与运行、数据交互、资源分配等难题;在某些情况下使程序设计更简单。

 

 

 

1.2 线程的创建

实现多线程编程的方式主要有两种:

一种是继承Thread类

另一种是实现Runnable接口

 

继承Thread类:

 

 

 

package com.se.Thread;

// 子线程,继承Thread类,重写run()方法
//创建线程方式一:继承Thread类,重写run方法,调用start开启线程
//总结:注意,线程开启不一定立即执行,有CPU调用
public class MyThread extends Thread{
    @Override
    public void run() {
        // 这个线程要干的事!
        for (int i = 0; i < 100; i++) {
            System.out.println(this.getName()+"-->MyThread run:"+i);
        }
    }
}

// 测试类
class Test02{
    public static void main(String[] args) {
        // 创建Thread子类对象
        MyThread myThread = new MyThread();
        // 给线程设置名字
        myThread.setName("我们的第一个子线程");
        // 开启线程(子线程)
        myThread.start();

        // 开启主线程
        for (int i = 0; i < 100; i++) {
            System.out.println("mian run:"+i);
        }
    }
}

实现Runnable接口:

 

 

 

package com.se.Thread;

// 子线程,继承Runnable接口,重写run()方法
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 子线程要执行的任务
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "-->MyRunnable run:" + i);
        }
    }
}

// 测试类
class Test03 {
    public static void main(String[] args) {
        // 创建Runnable实现类的对象
        MyRunnable runnable = new MyRunnable();
        // 以此对象作为参数构造Thread类
        Thread thread = new Thread(runnable);
        // 给线程设置名字
        thread.setName("我们的第二个子线程");
        // 开启线程(子线程)
        thread.start();

        // 开启主线程
        for (int i = 0; i < 100; i++) {
            System.out.println("mian run:" + i);
        }
    }
}

1.3 两种创建线程方式的对比

使用继承自Thread类的方式创建多线程:

优点:编写简单,如果需要访问当前线程,直接使用 this 即可

缺点:继承自Thread类了,不能再继承其他类

 

 

使用实现Runnable接口的方式创建多线程:

优点:还可以继承自其他类

缺点:编写稍微复杂,访问当前线程,需使用 Thread.currentThread() 方法

 

 

package com.kuangshen.demo02;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

//线程创建方式三:实现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() throws Exception {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载的文件名为:"+name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable testCallable1 = new TestCallable("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic_360_360%2Fbb%2F37%2Ff5%2Fbb37f583e8da88aed385306a07361c2a.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1629440173&t=03523962fee34ed4d7f9d7b0e1de3f2f", "1.jpg");
        TestCallable testCallable2 = new TestCallable("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic_360_360%2Fbb%2F37%2Ff5%2Fbb37f583e8da88aed385306a07361c2a.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1629440173&t=03523962fee34ed4d7f9d7b0e1de3f2f", "2.jpg");
        TestCallable testCallable3 = new TestCallable("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic_360_360%2Fbb%2F37%2Ff5%2Fbb37f583e8da88aed385306a07361c2a.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1629440173&t=03523962fee34ed4d7f9d7b0e1de3f2f", "3.jpg");
        //创建执行服务:
        ExecutorService ser = Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> r1 =  ser.submit(testCallable1);
        Future<Boolean> r2 = ser.submit(testCallable2);
        Future<Boolean> r3 =  ser.submit(testCallable3);
        //获取结果
        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();
        }
    }
}

 

1.4 线程的静态代理模式

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

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

package com.kuangshen.demo02;

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

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

public class StaticProxy {
    public static void main(String[] args) {
        You you = new You(); //你要结婚
        new Thread(() -> System.out.println("我爱你")).start();
        new WeddingCompany(new You()).happyMarry();
//        WeddingCompany weddingCompany = new WeddingCompany(you);
//        weddingCompany.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();
    }

    public void before() {
        System.out.println("结婚之前,布置现场");
    }

    public void after() {
        System.out.println("结婚之后,收尾款");
    }
}

1.5 Lamda表达式

总结:
  lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹。

  前提是接口为函数式接口
  多个参数也可以去掉参数类型,要去掉都去掉,必须加括号
package com.kuangshen.demo02;

/**
 * 推到lambda表达式
 */
public class TestLambdal {
    //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();
        ILike like2 = new Like2();
        like2.lambda();
        //4.成员内部类
        class Like3 implements ILike {

            @Override
            public void lambda() {
                System.out.println("i like lambda4");
            }
        }
        ILike like3 = new Like3();
        like3.lambda();

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

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

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

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

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

1.6 线程的生命周期

 

 

新建状态:当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值

就绪状态:当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行

运行状态:如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态

阻塞状态:当处于运行状态的线程失去所占用资源之后,便进入阻塞状态

死亡状态:线程在run()方法执行结束后进入死亡状态。此外,如果线程执行了interrupt()或stop()方法,那么它也会以异常退出的方式进入死亡状态。

 

 

 

1.4 线程的优先级

进程中至少有一个线程,在众多线程中,有时需要优先执行一个线程,这个线程的优先级越高,得到CPU执行的机会就越大,反之就越小

线程的优先级使用1-10之间的整数来表示,数字越大,优先级越高;除了通过整数表示优先级,还可以通过Thread类的3个静态常量表示:MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY

package com.se.Thread;

public class MyThread2 extends Thread{
    public MyThread2(String name){
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("当前线程名字:"+this.getName()+"/优先级:"+this.getPriority()+"/循环到第几次:"+i);
        }
    }
}
package com.se.Thread;

public class Test05 {
    public static void main(String[] args) {
        System.out.println("当前线程名字:"+Thread.currentThread().getName()+"/优先级:"+Thread.currentThread().getPriority()+"/主线程:");
        //创建子线程
        MyThread2 myThread1 = new MyThread2("第一个子线程");
        MyThread2 myThread2 = new MyThread2("第二个子线程");
        MyThread2 myThread3 = new MyThread2("第三个子线程");
        MyThread2 myThread4 = new MyThread2("第四个子线程");
        //设置优先级
        myThread1.setPriority(Thread.MIN_PRIORITY); // 1
        myThread2.setPriority(Thread.NORM_PRIORITY);// 5
        myThread3.setPriority(6);
        myThread4.setPriority(Thread.MAX_PRIORITY); // 10

        myThread1.start();
        myThread2.start();
        myThread3.start();
        myThread4.start();
    }
}

1.5 线程的控制

sleep()方法,来使正在执行的线程以指定的毫秒数暂停,进入睡眠状态,在该线程休眠时间内,CPU会调度其他线程

package com.se.Thread;

public class MySleepThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.getName()+"输出数字:"+i);
            if (i % 2 == 0) {
                try {
                    Thread.sleep(500); //当前线程休眠500毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
package com.se.Thread;

public class Test06 {
    public static void main(String[] args) {
        //创建一个子线程
        MySleepThread mySleepThread = new MySleepThread();
        //启动子线程
        mySleepThread.start();

        //主线程的代码
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "输出数字:" + i);
            if (i % 3 == 0) {
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

 

yield()方法,来暂停当前正在执行的线程对象,把机会让给相同或优先级更高的线程,因此, yield()方法也称为线程让步

package com.se.Thread;

public class MyYeildThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(this.getName() + "循环次数:" + i);
            if (i == 10) {
                System.out.println("当前线程执行了10次," + this.getName() + "我先让步");
                Thread.yield(); //让步
            }
        }
    }
}
package com.se.Thread;

public class Test07 {
    public static void main(String[] args) {
        MyYeildThread thread1 = new MyYeildThread();
        MyYeildThread thread2 = new MyYeildThread();

        thread1.start();
        thread2.start();
    }
}

 

join()方法,来实现让某个线程插队这一功能。当线程调用join()方法时,线程会进入阻塞状态,直到join方法加入的线程执行完毕,该线程才会继续执行

package com.se.Thread;

public class MyJoinThread extends Thread {
    @Override
    public void run() {
        System.out.println(this.getName() + "线程开启...");
        for (int i = 0; i < 20; i++) {
            System.out.println(this.getName() + "--->" + i);
        }
        System.out.println(this.getName() + "线程关闭...");
    }
}
package com.se.Thread;

public class Test08 {
    public static void main(String[] args) {
        MyJoinThread thread1 = new MyJoinThread();
        MyJoinThread thread2 = new MyJoinThread();

        thread1.start();
        thread2.start();

        try {
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程执行完毕!");
    }
}

1.6 线程的同步

在多线程中,为了处理共享资源竞争,Java可以使用同步机制;

所谓同步机制,指的是两个线程同时作用在一个对象上,应该保持对象数据的统一性和整体性。

Java中提供了关键字synchronized,可以处理多线程同步竞争资源的问题

 

 

 

 

 

 

 

 

案例演示:

有1500张从北京西到郑州的z63车次的车票,售票1000张,退票500张,利用多线程中线程同步
package com.se.syncPackage;

public class Tickets {
    public static final Object lock = new Object(); //
    public static int bjz2zz = 1500; // 有1500张从北京西到郑州的z63车次的车票

    /**
     * 售票
     */
    public synchronized void sale(){
        bjz2zz--;
    }

    /**
     * 退票
     */
    public synchronized void refound(){
        bjz2zz++;
    }
}

package com.se.syncPackage;

public class SaleTickets extends Thread {
    private Tickets tickets;

    public SaleTickets(Tickets tickets) {
        this.tickets = tickets;
    }

    @Override
    public synchronized void run() {
        for (int i = 0; i < 1000; i++) {
            //售票
            tickets.sale();
        }
    }
}
package com.se.syncPackage;

public class RefoundTickets extends Thread {
    private Tickets tickets;

    public RefoundTickets(Tickets tickets) {
        this.tickets = tickets;
    }

    @Override
    public synchronized void run() {
        for (int i = 0; i < 500; i++) {
            // 退票
            tickets.refound();
        }
    }
}
package com.se.syncPackage;

public class Test01 {
    public static void main(String[] args) {
        Tickets tickets = new Tickets();
        //售票的子线程
        SaleTickets saleTickets = new SaleTickets(tickets);
        //退票的子线程
        RefoundTickets refoundTickets = new RefoundTickets(tickets);

        saleTickets.start();
        refoundTickets.start();

        try {
            saleTickets.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            refoundTickets.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Tickets.bjz2zz);
    }
}

1.7 线程的停止

总结:
1.建议线程正常停止 --》 利用次数,不建议死循环

2.建议使用标志位 --》 设置一个标志位
3.不要使用stop或者destory等果实或者jdk不建议使用的方法
package com.kuangshen.state;

//测试stop
//1.建议线程正常停止 --》 利用次数,不建议死循环
//2.建议使用标志位 --》 设置一个标志位
//3.不要使用stop或者destory等果实或者jdk不建议使用的方法
public class TestStop implements Runnable{
    //设置一个标志位
    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("线程停止");
            }
        }
    }
}

 

1.8 线程的状态

 

 

Thread thread = new Thread();
thread.getState();//获取线程当前的状态
Thread.State.NEW //新生状态
Thread.State.RUNNABLE // 运行状态
Thread.State.TERMINATED  //终止状态
package com.kuangshen.state;

//观察测试线程的状态
public class TestState {
    public static void main(String[] args) {
        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);

        //观察启动后
        thread.start();//启动线程
        state = thread.getState();
        System.out.println(state);

        while (state != Thread.State.TERMINATED) { //只要线程不终止,就一直输出状态
            try {
                Thread.sleep(100);
                state = thread.getState(); // 更新线程状态
                System.out.println(state);// 输出状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

1.9 线程的死锁

所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。

 

在多线程中,形成死锁的4个必要条件:

1.互斥使用

2.不可抢占

3.请求和等待

4.循环等待

 

Java中的死锁是应该尽量避免的,避免死锁,只需要避免4个条件中的任意一个即可;

常用的方法:

1.避免嵌套锁

2.设置锁定的顺序

3.设置锁定等待的时间

 

 

 

 

 

 

 


package com.se.deadLockPackage;

public class Tickets {
    public static final Object lock = new Object(); //
    public static int bjz2zz = 1500; // 有1500张从北京西到郑州的z63车次的车票

    public static final Object lock2 = new Object(); //
    public static int bjz2gz = 1500; // 有1500张从北京西到广州的z63车次的车票

    /**
     * 售票
     */
    public void sale() {
        synchronized (lock) {
            bjz2zz--;
            // bjx2gz也减少了一张?
            synchronized (lock2) {
                bjz2gz--;
            }
        }
    }

    /**
     * 退票
     */
    public void refound() {
        // 锁的先后顺序要一致,否则会出现线程的死锁
        synchronized (lock) {
            bjz2zz++;
            synchronized (lock2) {
                bjz2gz++;
            }
        }
    }
}

package com.se.deadLockPackage;

public class SaleTickets extends Thread {
    private Tickets tickets;

    public SaleTickets(Tickets tickets) {
        this.tickets = tickets;
    }

    @Override
    public synchronized void run() {
        for (int i = 0; i < 1000; i++) {
            // 售票
            tickets.sale();
        }
    }
}
package com.se.deadLockPackage;

public class RefoundTickets extends Thread {
    private Tickets tickets;

    public RefoundTickets(Tickets tickets) {
        this.tickets = tickets;
    }

    @Override
    public synchronized void run() {
        for (int i = 0; i < 500; i++) {
            tickets.refound();
        }
    }
}
package com.se.deadLockPackage;

public class Test01 {
    public static void main(String[] args) {
        Tickets tickets = new Tickets();
        SaleTickets saleTickets = new SaleTickets(tickets);
        RefoundTickets refoundTickets = new RefoundTickets(tickets);
        saleTickets.start();
        refoundTickets.start();
        try {
            saleTickets.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            refoundTickets.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Tickets.bjz2zz);
        System.out.println(Tickets.bjz2gz);
    }
}

posted @ 2021-07-21 12:18  蔡地像徐坤  阅读(115)  评论(0编辑  收藏  举报