Java语言(多线程1(线程概述、线程的实现、线程方法、模拟龟兔赛跑、线程同步))

Java语言基础

多线程

进程与线程
  1. 线程是依赖于进程存在的:
CPU
进程A
进程B
进程...
线程A1
线程A2
线程A3
线程B1
线程B2
线程B3
线程...
  1. 进程概述:
    进程就是正在运行的程序,是系统进行资源分配和调用的独立单位;每一个进程都有它自己的内存空间和系统资源。
    通过任务管理器我们就可以看到进程的存在。
  2. 多进程的意义:
    单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),这正是人们所需的,所以我们常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。

对于单核计算机来讲,游戏进程和音乐进程并不是同时运行的,CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率

  1. 并行和并发:
    并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。例如:你可以边吃饭边打电话, 这两件事情可以同时执行;
    并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。例如:你吃一口饭喝一口水, 以正常速度来看, 完全能够看的出来, 当你把这个过程以n倍速度执行时…可以想象一下。
  2. Java程序运行原理:
    Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程;该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法,所以 main方法运行在主线程中;JVM启动就是多线程的,至少启动了垃圾回收线程和主线程。
  3. 如何实现多线程:
    由于线程是依赖进程而存在的,所以我们先创建一个进程出来,而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程;但Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序,Java可以去调用C/C++写好的程序来实现多线程程序,由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,然后提供一些类供我们使用,这样我们就可以实现多线程程序了;如Thread类。

    1:初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
    2:运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
    3:阻塞(BLOCKED):表示线程阻塞于锁。
    4:等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
    5:超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
    6:终止(TERMINATED):表示该线程已经执行完毕。
线程对象
  • 实现线程:
  1. 多线程程序实现的方式1:继承Thread类
public class Demo extends Thread {
    @Override
    public void run() {
        System.out.println("HelloWorld!");
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        demo.start();
    }
}
/*run()和start()方法的区别:
 我们启动线程使用不是run方法,而应该是start方法.使该线程开始执行;
 Java 虚拟机调用该线程的 run 方法。
*/
  1. 多线程程序实现的方式2:实现Runnable接口
public class Demo implements Runnable {

    @Override
    public void run() {
        System.out.println("HelloWorld!");
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        Thread thread = new Thread(demo);
        thread.start();
    }
}
  1. 多线程程序实现的方式2:实现Callable接口
    前面两种线程定义方式都有这两个问题:
    (1)无法获取子线程的返回值;
    (2)run方法不可以抛出异常。
    为了解决这两个问题,我们就需要用到Callable这个接口了。
public class Demo implements Callable {

    @Override
    public Object call() throws Exception {
        int j=1;
        System.out.println("HelloWorld!");
        return j;//子线程的返回值
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        FutureTask futureTask = new FutureTask(demo);
        Thread thread = new Thread(futureTask);
        thread.start();
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
//使用 FutureTask 包装 Callable 对象。因为 FutureTask 实现了 Runnable,所以可将 FutureTask 提交给 Executor 执行。
  • 线程命名:
    所有的线程程序的执行,每一次都是不同的运行结果,如果要想区分每一个线程,那么就需要依靠线程的名字。对于线程的名字一般而言会在启动之前进程定义,不建议对已经启动的线程,进行更改名称,或者为不同线程设置重名的情况。
//继承Thread类的线程命名,默认名和命名
public class Demo extends Thread {
    @Override
    public void run() {
        System.out.println("子线程名称:"+Thread.currentThread().getName());
    }
    
    public static void main(String[] args) {
        Demo demo= new Demo();
        demo.start();
        System.out.println("主线程名称:"+Thread.currentThread().getName());
    }
}
//主线程名称:main
//子线程名称:Thread-0

/**
*修改线程名字:
*/
public class Demo extends Thread {
    public Demo(String name) {
        //super(name);
        this.setName(name);
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        Demo demo= new Demo("新名字");
        demo.start();
        System.out.println("线程名称:"+demo.getName());
    }
}

//实现Runnable接口的线程命名
public class Demo2 implements Runnable {

    @Override
    public void run() {
        System.out.println("线程名称:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        Demo2 demo2 = new Demo2();
        Thread thread = new Thread(demo2, "线程1");
        System.out.println("线程名称:" + thread.getName());
        thread.start();
    }
}
  • 线程方法:
  1. setPriority(int newPriority):更改线程的优先级;线程的优先级用数字表示,范围从1~10,Thread.MIN_PRIORITY = 1;
    Thread.MAX_PRIORITY = 10;
    Thread.NORM_PRIORITY = 5;
    使用以下方式改变或获取优先级:getPriority(),setPriority(int xxx)

优先级的设定建议在start()调度前

public class Demo implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println("线程名称:"
                    + Thread.currentThread().getName()
                    + ",线程优先级:"
                    + Thread.currentThread().getPriority()
                    );
        }
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        Thread thread1 = new Thread(demo,"线程1");
        thread1.setPriority(Thread.MIN_PRIORITY);
        Thread thread2 = new Thread(demo,"线程2");
        thread2.setPriority(Thread.MAX_PRIORITY);
        Thread thread3 = new Thread(demo,"线程3");
        thread3.setPriority(5);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
/*
线程名称:线程2,线程优先级:10
线程名称:线程2,线程优先级:10
线程名称:线程2,线程优先级:10
线程名称:线程3,线程优先级:5
线程名称:线程3,线程优先级:5
线程名称:线程3,线程优先级:5
线程名称:线程1,线程优先级:1
线程名称:线程1,线程优先级:1
线程名称:线程1,线程优先级:1
*/
  1. static void sleep(long millis):线程休眠,在指定的毫秒数内让当前正在执行的线程休眠
//线程休眠演示:每隔1秒在屏幕上输出一句诗
public class MyTest implements Runnable {

    @Override
    public void run() {
        String s[]= {
                "鹅!鹅!鹅!",
                "曲项向天歌",
                "白毛浮绿水",
                "红掌拨清波"
        };
        for (int i = 0; i < s.length; i++) {
            System.out.println(s[i]);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        MyTest myTest = new MyTest();
        Thread thread = new Thread(myTest);
        thread.start();
    }
}
  1. void join():线程联合;待此线程执行完成后,再执行其他线程,其他线程阻塞,导致当前线程暂停执行,直到线程终止。
public class Demo implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程" + i);
        }
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        Thread thread = new Thread(demo);
        thread.start();
        for (int i = 0; i < 5; i++) {
            System.out.println("主线程main" + i);
            if(i == 2) {
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
/*
主线程main0
主线程main1
主线程main2
子线程0
子线程1
子线程2
子线程3
子线程4
主线程main3
主线程main4
*/
  1. static void yield():线程礼让,暂停当前正在执行的线程对象,并执行其他线程
//模拟线程礼让:
public class MyTest implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "开始了");
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + "结束了");
    }
    public static void main(String[] args) {
        MyTest myTest = new MyTest();
        Thread thread1 = new Thread(myTest, "线程1");
        Thread thread2 = new Thread(myTest, "线程2");
        thread1.start();
        thread2.start();
    }
}
  • 守护线程:
    Java线程分为两种:用户线程和守护线程。
    (1):用户线程可以认为是系统的工作线程,虚拟机必须确保用户线程执行完毕,完成这个程序要完成的业务员操作。
    (2):守护线程是一种特殊的线程,就和它的名字一样,它是系统的守护者,在后台默默完成一些系统性的服务,比如垃圾回收线程、后台记录操作日志、监控内存;
    如果用户线程全部结束,则意味着这个程序无事可做。守护线程要守护的对象已经不存在了,那么整个应用程序就结束了。
  • “龟兔赛跑”:模拟线程
    1、实例化两个线程
    2、判断谁是胜利者,最终乌龟取胜
    3、模拟兔子睡觉,每10米休息一次
public class Race implements Runnable{
    private static String winner=null;
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            //调整比赛速度
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //模拟兔子睡觉
            if ("兔子".equals(Thread.currentThread().getName()) && i % 10 == 0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            boolean flag=gameOver(i);
            if (flag){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"米");
        }
    }

    public boolean gameOver(int m){
        if (winner!=null){
            return true;
        }
        if (m>=100){
            winner=Thread.currentThread().getName();
            System.out.println("胜利者是:"+winner);
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Race race = new Race();
        Thread thread = new Thread(race,"兔子");
        Thread thread1 = new Thread(race, "乌龟");
        thread.start();
        thread1.start();
    }
}
线程同步
  1. 引出:
    处理多线程问题时,我们遇到线程冲突,这时候我们就需要线程同步;线程同步其实就是一种等待机制 , 多个需要同时访问 此对象的线程进入这个对象的等待池 形成队列, 等待前面线程使用完毕 , 下一个线程再使用。

线程冲突:当在不同线程中运行作用于相同数据的两个操作时,就会发生干扰。这意味着这两个操作由多个步骤组成,并且步骤顺序重叠。

  1. 同步用法:
    Java编程语言提供了两种基本的同步习惯用法:同步语句(synchronized statements )和同步方法(synchronized methods )。
    同步语句是创建同步代码的一种方法;
    同步语句必须指定提供内部锁的对象。
  • 同步语句
    synchronized (Obj ) { }
    Obj 称之为 :同步监视器
    Obj 可以是任何对象 , 可以是this , 就是这个对象本身 、""等
    模拟电影院三个售票员(一个售票员就是一个线程)卖票,总共100张票,打印出是哪个售票员卖了一张票并统计剩余几张票,我们会发现启动这三个线程会产生顺序错乱,不同的售票员卖了同一张票,还会出现负值的情况,此时就需要进行线程同步:
//使用同步语句将会出现问题的语句框起来
public class ThreadConflict implements Runnable {
    // 票数100张
    private static int ticket = 100;

    @Override
    public void run() {
        while (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (""){
                if (ticket<=0){
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "卖了1张票,还剩" + --ticket + "张");
            }
        }
    }
    public static void main(String[] args) {
        // 实例化子线程
        ThreadConflict threadConflict = new ThreadConflict();
        // 模拟三个售票员
        Thread thread1 = new Thread(threadConflict, "售票员1");
        Thread thread2 = new Thread(threadConflict, "售票员2");
        Thread thread3 = new Thread(threadConflict, "售票员3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
  • 同步方法
    synchronized方法控制对 “对象” 的访问 , 每个对象对应一把锁 , 每个 synchronized方法都必须获得调用该方法的对象的锁才能执行 , 否则线程会阻塞 , 方法一旦执行 , 就独占该锁 , 直到该方法返回才释放锁 , 后面被阻塞的线程才能获得这个锁 , 继续执行。
    缺陷 : 若将一个大的方法申明为synchronized 将会影响效率
public class ThreadConflict2 implements Runnable {
    // 票数100张
    private static int ticket = 100;
    @Override
    public void run() {
        while (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sellTicket();
        }
    }

    public synchronized void sellTicket(){
        if (ticket<=0)
            return;
        System.out.println(Thread.currentThread().getName() + "卖了1张票,还剩" + --ticket + "张");
    }

    public static void main(String[] args) {
        // 实例化子线程
        ThreadConflict2 threadConflict = new ThreadConflict2();
        // 模拟三个售票员
        Thread thread1 = new Thread(threadConflict, "售票员1");
        Thread thread2 = new Thread(threadConflict, "售票员2");
        Thread thread3 = new Thread(threadConflict, "售票员3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

//下篇再见…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值