Java学习笔记day10——多线程相关

【相关名词解释】

1. 程序

一组指令的集合,是静态的。

2. 进程

一个程序的一次运行

当程序启动后,操作系统就会给这个程序分配一个进程的ID,并且会给他分配一块独立的内存空间。

若一个程序被启动了多次,就有多个进程

多个进程之间无法共享数据

如果两段代码需要进行数据交互,成本比较高(通过硬盘或网络)

进程之间的切换成本也比较高,现在操作系统都支持多任务,操作系统在每一个任务之间进行任务,要给进程做镜像,要记录当前进程的状态,执行到哪个指令了

操作系统分配资源的最小单位是进程

每一个进程至少有一个线程

3. 线程

进程中的其中一条执行路径,多个线程会属于同一个进程

这多个线程会共享同一个进程中的一些资源,比如Java中堆内存的数据,比如方法区的数据等

如果两段代码需要进行数据交互,可以直接在内存中操作(会出现安全问题)

线程之间的切换,需要记录的数据少很多,因为很多线程数据是共享的,这部分不需要单独镜像,只需要记录每一个线程要执行的下一条指令

CPU调度的最小单位是线程

4. JVM

(1)方法区

(2)堆

(3)Java虚拟机栈

(4)本地方法栈

(5)程序计数器:记录每一个线程要执行的下一条指令

Java程序一个main方法,就是一个进程,但是可以在main中,再开启多个线程

Java程序后台有几个线程是默默运行的:GC线程、异常的检查和处理、类加载

5. 并行和并发

并行:指两个或多个事件在同一时刻发生(同时发生),指在同一时刻,有多个指令在多个处理器上同时执行

并发:指两个或多个事件在同一事件段内发生,在同一时刻只能有一条指令执行,但多个进程的指令被快速轮换执行,使得在宏观上有多个进程同时执行的效果

6. 线程调度

(1)分时调度

所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间

(2)抢占式调度

优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调用

【多线程】

创建方式有四种:

继承Thread、实现Runnable接口、实现Callable接口、线程池(这里学两种)

1. 多线程的创建和启动方式一:继承Thread类

步骤:

(1)编写一个类,让他继承Thread类

(2)重写父类的public void run(){},该方法不是由程序员调用,而是线程调度时,自动调用

要让一个线程做什么事,必须把这个代码写到run中,run()的方法体,称为线程体

(3)创建自定义线程类的对象

(4)启动线程

调用自定义线程类对象的start()方法——启动线程方法

【注】不要手动调run()方法,run()方法是顺序执行,start()方法是多线程

如:

public class ThreadTest {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                for(int i = 2;i <= 10;i+=2){
                    System.out.println("在线程内:"+i);
                }
            }
        }.start();
        for (int i = 1;i<10;i+=2){
            System.out.println("在main中:"+i);
        }
    }
}

2. 多线程的创建和启动方式二:实现Runnable接口

步骤:

(1)编写线程类,实现Runnable

(2)重写接口的抽象方法public void run()

(3)创建自定义对象类

(4)创建一个Thread对象类,让Thread对象类代理自定义线程类对象

(5)启动线程

如:

public class RunnableTest  {
    public static void main(String[] args) {
        MyRunnable my = new MyRunnable();
        Thread t = new Thread(my);
        t.start();
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i = 2;i<=10;i+=2){
            System.out.println("线程内:"+i);
        }
    }
}

 3. Thread类的方法

(1)构造方法

public Thread():分配一个新的线程对象

public Thread(String name):分配一个指定名字的新的线程对象

public Thread(Runnable target):分配一个带有指定目标新的线程对象

public Thread(Runnable target,String name):分配一个带有指定目标的星的线程对象并命名

String getName():获取线程名字

        如果没有手动指定线程名称,默认是Thread-编号,从0开始

        如果需要手动指定线程名称,可以通过构造器,或者setName(String name)方法

static Thread currentThread():获取执行当前语句的线程对象

(2)线程优先级

public final int getPriority():返回线程优先级

public final void setPriority(int newPriority):改变线程优先级

线程优先级高的,有更多的机会/概率被优先调用

t.setPriority();
java.lang.IllegalArgumentException
当设置的优先级不在MIN_PRIORITY到MAX_PRIORITY范围内,就会报这个异常
正常范围:1~10

(3)和线程的状态有关的方法

public static void sleep(long millis)throws InterruptedException:线程休眠,单位(毫秒)

public static void yield():让当前线程暂停一下,让出CPU,但是下次CPU还是原来的调用

public join():等待该线程终止。该线程是调用join方法的线程(加塞、阻塞)

        在()内添加时间,就是阻塞该时间,时间到了之后继续抢CPU

如:

public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
       Thread t = new Thread("偶数线程"){
       public void run(){
           for(int i = 2;i<=100;i+=2){
               System.out.println(i+getName());
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }
       };
       t.start();
       for(int i = 1;i<100;i+=2){
           System.out.println(i+Thread.currentThread().getName());
           if(i == 5){
               Thread.currentThread().join();
               //让奇数线程彻底暂停,让出全部CPU,直到偶数线程走完再继续
           }
       }
    }
}

线程练习1:加塞

自定义线程类ChatThread:问是否结束(输入Y/y结束),如果输入的不是y,继续问是否结束,知道输入y才结束。

主线程打印[1,10],每隔10毫秒打印一个数字,现在当主线程打印完5之后,就让自定义线程类加塞,直到自定义线程类结束,主线程再继续

代码:

import java.util.Scanner;

class ChatThread extends Thread{
    Scanner input = new Scanner(System.in);

    @Override
    public void run() {
        while(true){
            System.out.println("请确认是否结束?Y/y");
            char answer = input.next().charAt(0);
            if(answer == 'Y'||answer == 'y'){
                break;
            }
        }
    }
}

public class TestThread {
    public static void main(String[] args) {
        for(int i = 1;i<=10;i++){
            System.out.println(i);
            if(i == 5){
                ChatThread t= new ChatThread();
                t.start();
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

先把该写的逻辑写全,然后补上try…catch

线程练习2:修改和加塞

编写龟兔赛跑多线程程序,设赛道长度为30米

兔子的速度为10米每秒,兔子每跑完10米休眠的时间10秒

乌龟的速度是1米每秒,乌龟每跑完10米的休眠时间是1秒

要求:要等兔子和乌龟的线程结束,主线程(裁判)才能公布最后的结果

提示:System.currentTimeMillis()方法扩返回当前时间的毫秒值(long 类型)

分析:跑道长度为30米,即从0循环到30

兔子速度为10米每秒,即兔子跑1米用时100毫秒

乌龟速度为1米每秒,即乌龟跑1米用时1000毫秒

代码:

兔子类:

public class Rabbit extends Thread{
    long time;
    public Rabbit(String name) {
        super(name);
    }

    @Override
    public void run() {

        long start = System.currentTimeMillis();
        for(int i = 1;i<=30;i++){

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName()+"跑了"+i+"秒");
            if(i == 10||i == 20){
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        long end = System.currentTimeMillis();
        time = end-start;
        System.out.println(getName()+"耗时:"+(end-start));
    }
}

乌龟类

public class Gui extends Thread {
    long time;
    public Gui(String name) {
        super(name);
    }

    @Override
    public void run() {
        long start = System.currentTimeMillis();
        for (int i = 1; i <= 30; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName() + "跑了" + i + "秒");
            if (i == 10 || i == 20) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        long end = System.currentTimeMillis();
        time = end-start;
        System.out.println(getName()+"耗时:"+(end-start));
    }
}

测试类:

public class Competition {
    public static void main(String[] args) {
        Gui g = new Gui("乌龟");
        Rabbit r = new Rabbit("兔子");

        g.start();
        r.start();
        try {
            r.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            g.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(g.time > r.time){
            System.out.println("兔子胜");
        }else {
            System.out.println("乌龟胜");
        }
    }
}

4. 线程的停止

原本有stop(),但是已过时。

替换为flag,放入for循环的条件判断中,如:

通过join()方法等待快的线程结束,结束后,改变慢线程的boolean值,从而实现慢线程停止

public class StopTest {
    public static void main(String[] args) {
        Odd o = new Odd();
        Even e = new Even();
        o.start();
        e.start();
        try {
            e.join();
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        o.setFlag(false);
    }
}
class Odd extends Thread{
    boolean flag = true;
    @Override
    public void run() {
        for(int i = 1;i<=100&&flag;i+=2){
            System.out.println(i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
class Even extends Thread{
    @Override
    public void run() {
        for(int i = 2;i <= 100;i+=2){
            System.out.println(i);
        }
    }
}

线程练习3:龟兔赛跑优化(通过比较距离)

将重复代码进行优化,整合为一个类,通过创建线程对象进行调用

代码为:

Sporter类:

public class Sporters extends Thread{
    private int distance;
    private final  int MAX_DISTANCE = 30;
    private static boolean flag = true;
    private int restTime;
    private int runTime;

    public Sporters(String name, int restTime, int runTime) {
        super(name);
        this.restTime = restTime;
        this.runTime = runTime;
    }

    @Override
    public void run() {
        while(distance <MAX_DISTANCE && flag){
            try {
                Thread.sleep(runTime);
                distance++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName()+"跑了"+distance);

            if(distance == 10 || distance == 20){
                try {
                    Thread.sleep(restTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        if(distance == MAX_DISTANCE){
            System.out.println(getName()+"已经到达终点");
            flag = false;
        }
    }

    public int getDistance() {
        return distance;
    }
}

测试类:

public class NewCompetition {
    public static void main(String[] args) {
        Sporters tu = new Sporters("兔子", 10000, 100);
        Sporters gui = new Sporters("乌龟", 1000, 1000);
        tu.start();
        gui.start();
        try {
            tu.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            gui.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (tu.getDistance() > gui.getDistance()) {
            System.out.println("兔子胜");
        } else {
            System.out.println("乌龟胜");
        }
    }
}

5. 线程安全

当使用多个线程访问同一资源(文件、变量、记录等)的时候,如多个线程只有读操作,就不会发生安全问题,但是如果多个线程中对资源有读写操作,就容易出现安全问题

(1)局部变量是不能共享的

因为每一个线程对象,都会调用一次run方法,而方法的每一次调用在“栈”中都会开辟独立内存空间

(2)实例变量可以共享,但前提是同一个对象

即,不使用继承,而使用接口实现线程去代理或者使用静态变量

如:

public class Tickets {
    public static void main(String[] args) {
        Tick t = new Tick();
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        t1.start();
        t2.start();
        t3.start();
    }
}
class Tick implements Runnable{
    private int i = 10;
    @Override
    public void run() {
        while(i>1){
            i--;
            System.out.println(Thread.currentThread().getName()+"卖出一张,剩余"+i+"张");
        }
    }
}

6. 如何解决安全问题?

(1)使用同步机制

同步就是给某段代码加“锁”

锁是一个对象,又被称为监视器对象。就是监视使用共享数据的这几个线程的调度执行情况

Java分为3个对象

①对象头:当前对象类所属的指针、MarkWord(GC、锁标记)、数组长度

②对象的实例变量

③对齐空白

即每一个对象都有一个标记位,标记当前哪个线程“占有”这个锁对象

这个标记位会记录这个线程的ID,只有“占有”锁对象的线程才有机会执行被锁的代码,称为同步代码

(2)如何给代码加锁:synchronized

①同步代码:锁整个方法

【修饰符】synchronized 返回类型 方法名(【形参列表】){     方法体   }

②同步代码块:锁方法里的一部分

synchronized(锁对象){

        需要被锁起来的代码

}

如:将上面的卖票过程进行优化:

public class Tickets {
    public static void main(String[] args) {
        Tick t = new Tick();
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        t1.start();
        t2.start();
        t3.start();
    }
}
class Tick implements Runnable{
    private int i = 100;
    @Override
    public void run() {
        while(i>1){
            oneTicket();
        }
    }
    public synchronized void oneTicket(){
        if(i>=1){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            i--;
            System.out.println(Thread.currentThread().getName()+"卖出一张,剩余"+i+"张");
        }
    }
}

(3)什么时候释放锁?

当synchronized锁住的代码全部执行完才会释放锁

(4)锁对象的选择问题

①任意类型的对象都可以当做监视线程的锁对象,无限制

②必须保证使用共享数据的多个线程,使用的是同一个锁对象

③同步方法的锁对象是不能自由选择的,是默认的

非静态方法的锁对象是this对象

静态方法的锁对象是当前类的Class对象

如,将上题再次进行优化:添加票对象

public class Ticket {
    private final int MAX_TOTAL = 10;
    private int total = MAX_TOTAL;

    public synchronized void sale(){
        if(total > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            total --;
            System.out.println(Thread.currentThread().getName()+"卖出一张票,还剩"+total);
        }else{
            System.out.println("没有票了");
        }
    }
    public boolean check(){
        return total>0;
    }
}

main中启动线程:

public class Test {
    public static void main(String[] args) {
        Ticket t = new Ticket();
        new Thread("窗口一"){
            @Override
            public void run() {
                while(t.check()){
                    t.sale();
                }
            }
        }.start();
        new Thread("窗口二"){
            @Override
            public void run() {
                while(t.check()){
                    t.sale();
                }
            }
        }.start();
    }
}

7. 什么是单例设计模式

(1)设计模式

程序员根据自己的经验总结出来的代码模板、套路

常见设计模式有23种

(2)单例设计模式

某个类的对象在整个应用程序中只有唯一的一个

如:Java中的JVM对象

(3)如何实现单例

①构造器私有化

②在类的内部创建好这个类的唯一对象

(4)单例设计模式的形式

①饿汉式

无论是否用到这个类的对象,在类初始化的时候,直接创建对象

②懒汉式

等需要对象时再创建

8. 线程通信的等待唤醒机制

wait()

notify():一般使用notifyall()

当这两个方法不是线程的监视器对象调用时,会报IllegalMonitorStateException(非法监视器)异常

监视器对象:同步锁对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值