Java基础 第19天 多线程

标签: Java基础 多线程
1人阅读 评论(0) 收藏 举报
分类:

Java基础 多线程

多线程介绍

线程和进程:

  • 进程:进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进 程,进程是处于运行过程中的程序,并且具有一定独立功能。

  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有 一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

  • 简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

  • 多线程概述

    日常生活中很多事都是可以同时进行的,例如:人可以同时进行呼吸、血液循环、思考 问题等活动。
    多线程是指一个应用程序中有多条并发执行的线索,每条线索都被称作为一个线程,它 们会交替执行,彼此之间可以进行通信。

运行原理

CPU 在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这 个叫做线程上下文切换(对于进程也是类似)

  • 分时调度

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

  • 抢占式调度

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

主线程

JVM 启动后,会有一个执行路径(线程)从 main 方法开始的,一直执行到 main 方法结束,这 个线程在 Java 中称之为主线程。
当程序的主线程执行时,如果遇到了循环而导致程序在指定位置停留时间过长,则无法马上 执行下面的程序,需要等待循环结束后能够执行。

那么,能否实现一个主线程负责执行其中一个循环,再由另一个线程负责其他代码的执行, 最终实现多部分代码同时执行的效果? 能够实现同时执行,通过 Java 中的多线程技术来解决该问题。

单线程举例

示例代码
public class Test02 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        MyThread mt = new MyThread();
        mt.run();
    }

}

class MyThread {
    public void run() {
        while (true) {
            System.out.println("MyThread 类的 run() 方法在执行");
        }
    }
}

Thread 类

Thread 是 Java 程序中开启多线程的类,Java 虚拟机允许应用程序并发地运行多个执行线 程。

  • 构造方法摘要

    Thread() 分配新的 Thread 对象。
    Thread(Runnable target) 分配新的 Thread 对象。
    Thread(String name) 分配新的 Thread 对象。

  • 常用方法

static void sleep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。

创建线程方式一继承Thread类

  • 创建线程的步骤

    1. 定义一个类继承 Thread 类。
    2. 重写 run 方法。
    3. 创建子类对象
    4. 调用 start 方法,开启线程并让线程执行,同时还会告诉 JVM 去调用 run 方法。
示例代码
public class Test01 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub  
        Thread t = new Threadfirst();
        t.start();


    }

}
/*
 *  创建线程的步骤:
 *  1. 定义一个类继承 Thread 类。
 *   2. 重写 run 方法。 
 *   3. 创建子类对象 
 *   4. 调用 start 方法,开启线程并让线程执行,同时还会告诉 JVM 去调用 run 方法。
 */

class Threadfirst extends Thread{
    @Override
    public void run() {
        System.out.println("这是另一个线程");
        System.out.println(0/0);
    }
}
线程对象调用 run 方法和调用 start 方法区别
  • 调用 run 方法不开启线程。仅是对象调用方法。
  • 调用 start 开启线程,并让 jvm 调用 run 方法在开启的线程中执行

获取线程名称

Thread 提供了获取当前正在运行的线程对象的方法,还有个方法是获取当前线程对象 的名称。

String getName()
返回该线程的名称。
static Thread currentThread()
返回对当前正在执行的线程对象的引用。

  • Thread.currentThread():获取当前线程对象

  • Thread.currentThread().getName():获取当前线程对象的名称

通过结果观察,原来主线程的名称:main;自定义的线程:Thread-0,线程多个时,数 字顺延。如 Thread-1 进行多线程编程时,不要忘记了 Java 程序运行是从主线程开始,main 方法就是主线程 的线程执行内容。

示例代码
public class Test03 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        //Thread.currentThread().getName()获得当前执行的进程的名字
        Thread t = new ThreadSecond("老子的线程");
        t.setName("第二个进程");
        Thread t1 =new ThreadSecond("第三个线程");
        t1.start();
        t.start();
        for(int i = 0;i<10;i++){
            Thread.currentThread().setName("主线程");
            System.out.println(Thread.currentThread().getName()+"--"+i);
        }
    }

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

    @Override
    public void run() {
        for(int i = 0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"--"+i);
        }
    }
}

创建线程方式二:实现Runnable接口

创建线程的另一种方式是实现 Runnable 接口。该接口有一个未实现的方法(run)。
创建 Runnable 的子类对象,通过构造方法传入到 Thread 对象中即可。

  • Runnable 接口中的方法

void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。

  • Thread 类构造方法

    Thread(Runnable target)
    分配新的 Thread 对象。
    Thread(Runnable target, String name)
    分配新的 Thread 对象。

  • 创建线程的步骤。

    1. 定义类实现 Runnable 接口
    2. 覆盖接口中的 run 方法
    3. 创建 Thread 类的对象
    4. 将 Runnable 接口的子类对象作为参数传递给 Thread 类的构造函数
    5. 调用 Thread 类的 start 方法开启线程
示例代码
public class Test04 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Runnable r = new Threadthird();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        t1.start();
        t2.start();
        t3.start();

    }

}
/*
 * 1. 定义类实现 Runnable 接口 
 * 2. 覆盖接口中的 run 方法 
 * 3. 创建 Thread 类的对象 
 * 4. 将 Runnable接口的子类对象作为参数传递给 Thread 类的构造函数 
 * 5. 调用 Thread 类的 start 方法开启线程
 */
class Threadthird implements Runnable {

    @Override
    public void run() {
        // TODO Auto-generated method stub
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"--"+i);
        }

    }

}

两种实现多线程方式的区别

示例代码
public class Test05 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        new TicketWindow().start();
        new TicketWindow().start();
        new TicketWindow().start();
        new TicketWindow().start();
        new TicketWindow().start();
        new TicketWindow().start();
        new TicketWindow().start();
        new TicketWindow().start();
    }

    static class TicketWindow extends Thread{
        private int tickets = 100;

        @Override
        public void run() {
            while(true){
                if(tickets>0){
                    System.out.println(Thread.currentThread().getName()+"出售第"+(tickets--)+"张票");
                }else{
                    break;
                }
            }

        }
    }
}

由于现实中票资源是共享的,因此上面的运行结果显然不合理,为了保证资源共享,在程序 中只能创建一个售票对象,然后开启多个线程去运行同一个售票对象的售票方法,简单来说 就是四个线程同事运行同一个售票程序,这时候就需要多线程。使用多线程的第二种方式。

示例代码
public class Test06 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        TicketsSecond ts = new TicketsSecond();
        new Thread(ts,"线程1").start();//Thread的构造方法,传递一个Runnable对象和一个线程名字的字符串
        new Thread(ts,"线程2").start();
        new Thread(ts,"线程3").start();
        new Thread(ts,"线程4").start();
    }
    static class TicketsSecond implements Runnable{

        private int tickets = 100;
        public int getTickets() {
            return tickets;
        }
        public void setTickets(int tickets) {
            this.tickets = tickets;
        }
        @Override
        public void run() {
            while(true){
                if(tickets>0){
                    System.out.println(Thread.currentThread().getName()+"出售第"+(tickets--)+"张票");
                }else{
                    break;
                }
            }
        }

    }

}
  • 实现 Runnable 接口相对于继承 Thread 类来说,有如下显著好处:
    1. 适合多个相同程序代码的线程去处理同一个资源的情况,把线程同程序代码、数据 有效的分离
    2. 可以避免由 Java 的单继承带来的局限性

匿名方式创建线程

匿名内部类方式创建线程,可以方便的实现每个线程执行不同的线程任务操作。

  • 方式 1:创建线程对象时,直接重写 Thread 类中的 run 方法
示例代码
public class Test07 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        for(int i = 0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"--"+i);
        }
        new Thread(){
            @Override
            public void run() {
                for(int i = 0;i<10;i++){
                    System.out.println(Thread.currentThread().getName()+"--"+i);
                }
            }
        }.start();
    }

}
  • 方式 2:使用匿名内部类的方式实现 Runnable 接口,重新 Runnable 接口中的 run 方法
示例代码
public class Test08 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        for(int i = 0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"--"+i);
        }
        Runnable r = new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                for(int i = 0;i<10;i++){
                    System.out.println(Thread.currentThread().getName()+"--"+i);
                }
            }
        };
        new Thread(r).start();
    }

}

线程安全

学习线程安全问题之前,先来看做一下多线程售票案例,使用多线程模拟火车站的售票窗口, 每一个线程表示一个售票窗口,共出售 100 张票

示例代码
public class Test09 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        new SellTickets().start();
        new SellTickets().start();
        new SellTickets().start();
        new SellTickets().start();
    }

}
class SellTickets extends Thread{

    private int tickets = 100;

    public int getTickets() {
        return tickets;
    }

    public void setTickets(int tickets) {
        this.tickets = tickets;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(true){
            if(tickets>0){
                try {
                    Thread.sleep(0);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"出售第"+(tickets--)+"张票");
            }else{
                break;
            }
        }

    }

}

很显然上面的例子出现了问题

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,那么这个全局变量是线程安全的;
若有多个线程同时执行写操作(修改),都需要考虑线程同步,否则就可能发生线程安全问题。

线程同步

Java 中提供了线程同步机制,它能够解决上述的线程安全问题。
线程同步的方式有两种
- 方式一:同步代码块
- 方式二:同步方法

同步代码块

同步代码块:

同步代码块: 在代码块声明上,加上 synchronized

synchronized (锁对象){
可能会产生线程安全问题的代码
}

同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能 够保证线程安全。

  • lock 是一个锁对象,是同步代码块的关键,锁对象可以是任意类型的对象,是多 个线程共享的锁对象

    必须是唯一的,“任意”说的是共享锁对象的类型。所以,锁对象的创建代码不能放到 run()方法中,否则每个线程都会创建一个新的对象,每个锁都有自 己的标志,那就没有意义了
    被锁对象:表示如果当前线程访问”被锁对象”的 synchronized 的代码块时,其 它线程不能访问此代码块,另外,也不能访问”被锁对象”中的其它 synchronized 的代码块;
    同步的好处:解决了多线程并发访问的问题,使多线程并发访问的共享数据能 够保持一致性;

  • 同步的弊端

    使当前的对象的工作效率降低;因为要处理线程同步的问题;

  • 主要优化票池类,注意 synchronized 部分

优化示例一
class ThreadThird implements Runnable{

    private int tickets = 100;
    private N s = new N();
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(true){
            synchronized (s) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "  " + tickets--);
                } else {
                    break;
                }
            }

        }
    }

}
class N{}

同步方法

同步方法:在方法声明上加上 synchronized

public synchronized void method(){
可能会产生线程安全问题的代码
}

同步方法中的锁对象是 this

存取款案例

使用两个线程模拟银行存钱和取钱操作
同步的方法:(比较常见的方式)(默认锁的就是当前对象)

示例代码
public class Demo {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Account acc = new Account();
        SetThread setThread = new SetThread(acc);
        GetThread getThread = new GetThread(acc); // 开启存取钱的线程
        setThread.start();
        getThread.start();
        System.out.println("主线程等待 1 秒......");// 确保存钱、取钱两个线程执行完毕
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程醒来......");
        System.out.println("最终余额:" + acc.getBalance());

    }

}
public class Account {
    private int balance = 10000;

    // 同步方法
    // public void setMoney(int m) {
    // this.balance += m;
    // }
    // public void getMoney(int m) {
    // this.balance -= m;
    // }

    public synchronized void setMsoney(int m) {
        this.balance += m;
    }

    public synchronized void getMoney(int m) {
        this.balance -= m;
    }

    public int getBalance() {
        return this.balance;
    }

}
public class GetThread extends Thread{
    private Account acc;

    public GetThread(Account acc) {
        this.acc = acc;
    }

    public void run() {
        for (int i = 0; i < 1000; i++) {
            this.acc.getMoney(1000);
        }
    }
}
public class SetThread extends Thread {

    private Account acc;

    public SetThread(Account acc) {
        this.acc = acc;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            acc.setMsoney(1000);
        }
    }
}

死锁

同步锁使用的弊端:当线程任务中出现了多个同步锁时,如果同步中嵌套了其他的同步。这 时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免 掉。
死锁产生的四个必要条件
1. 互斥使用,当一个资源被一个线程使用是,别的线程不能使用
2. 不可抢占,不可抢占别人的资源,只能由使用者释放
3. 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4. 循环等待,存在一个等待队列,即 A 占有 B,B 占有 A

示例代码
package lock;

public class Test01 {

    public static void main(String[] args) {
        Try01 t1 = new Try01();
        Try02 t2 = new Try02();
        Myclass01 m1 = new Myclass01(t1, t2);
        Myclass01 m2 = new Myclass01(t1, t2);
        m1.start();
        m2.start();

    }
}
class Myclass01 extends Thread{
    private Try01 t1;
    private Try02 t2;

    public Myclass01(Try01 t1, Try02 t2) {
        super();
        this.t1 = t1;
        this.t2 = t2;
    }

    @Override
    public void run() {
        synchronized (t1) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("马上执行t1.show");
            t1.show();
        }

    }
}
class Myclass02 extends Thread{
    private Try01 t1;
    private Try02 t2;

    public Myclass02(Try01 t1, Try02 t2) {
        super();
        this.t1 = t1;
        this.t2 = t2;
    }

    @Override
    public void run() {
        synchronized (t2) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("马上执行t2.show");
            t2.show();
        }
    }

}

class Try01{
    public synchronized void show(){
        System.out.println("我是第一个方法!");
    }
}
class Try02{
    public synchronized void show(){
        System.out.println("我是第二个方法!");
    }
}

Lock接口

Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。

方法摘要

void lock() 获取锁
void unlock() 释放锁

文档中的使用示例

示例代码
public class Test02 {
    private int balance = 10000; // 为了多个方法共同使用同一把锁对象,所以,将 Lock 对象定义在成员变量这里;
    Lock lock = new ReentrantLock();

    public void setMoney(int m) {
        lock.lock();
        try {
            this.balance += m;
        } finally {
            lock.unlock();
        }
    }

    public void getMoney(int m) {
        lock.lock();
        try {
            this.balance -= m;
        } finally {
            lock.unlock();
        }
    }

    public int getBalance() {
        return this.balance;
    }

}

线程等待唤醒机制

线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相 同。通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
等待唤醒机制所涉及到的方法:

  • wait():等待,将正在执行的线程释放其执行资格和执行权,并存储到线程池中

  • notify():唤醒,唤醒线程池中被 wait()的线程,一次唤醒一个,而且是任意的

  • notifyAll():唤醒全部:可以将线程池中的所有 wait()线程都唤醒。

以上方法都定义在 Object 类中
其实,所谓唤醒的意思就是让线程具备执行资格。必须注意的是,这些方法都是在同步中才 有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。
因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象。能被任意对象调用 的方法一定定义在 Object 类中。

示例代码
public class Demo {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        BaoZipu bzp = new BaoZipu();
        SetThread set = new SetThread(bzp);
        GetThread get = new GetThread(bzp);
        set.start();
        get.start();

    }

}
public class BaoZipu {

    private ArrayList<String> bzlist = new ArrayList<>();

    public synchronized void setBaozi(String s) {
        this.bzlist.add(s);
        notifyAll();
    }

    public synchronized String getBaozi() {
        if (this.bzlist.size() <= 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        String s = bzlist.get(0);
        bzlist.remove(0);
        return s;
    }
}

class SetThread extends Thread {
    private BaoZipu bzp;

    public SetThread(BaoZipu bzp) {
        this.bzp = bzp;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            bzp.setBaozi("包子");
        }
    }
}

class GetThread extends Thread {
    private BaoZipu bzp;

    public GetThread(BaoZipu b) {
        this.bzp = b;
    }

    @Override
    public void run() {
        while (true) {
            String s = bzp.getBaozi();
            System.out.println("我得到了一个:" + s);
        }
    }
}

定时器

Java 中的定时器
1. 可以在指定的时间,开始做某件事情
2. 可以从指定的时间开始,并且间隔多长时间,会重复的做某件事情

实现定时器

示例代码
public class Time {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Timer timer = new Timer();
        System.out.println("3秒后,开始执行, 之后每隔1秒重复执行一次......");
        //timer.schedule(new MyTimerTask(timer), 1000 * 3);
        timer.schedule(new MyTimerTask(timer), 1000 * 3, 1000);

    }

}
class MyTimerTask extends TimerTask{

    private Timer timer;
    public MyTimerTask(Timer t){
        this.timer = t;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println("duang...........");

    }

}
查看评论

Java基础核心技术:多线程(day16-day17)

-
  • 1970年01月01日 08:00

java基础第19天

----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------ Map概述    Map实现类:HashMap(默认)、TreeMap、...
  • ghzhangfoxmail
  • ghzhangfoxmail
  • 2013-03-23 15:31:10
  • 305

u-boot-2014.10移植第19天----添加nand flash命令支持(一)

今天继续移植nand flash,对于很多情况而言,u-boot从nand flash启动是一个至关重要的功能,毕竟NOR flash成本太高,不是所有开发板都会添加NOR flash。u-boot中...
  • sonbai
  • sonbai
  • 2014-12-31 20:03:38
  • 2324

java 总结 web mysql

  • 2017年09月05日 21:49
  • 50MB
  • 下载

Java语言程序设计-基础篇(原书第10版)_中文版_带书签.pdf

  • 2017年03月29日 23:17
  • 59B
  • 下载

高清版Java语言程序设计-基础篇(原书第8版)

  • 2017年09月25日 19:49
  • 59.46MB
  • 下载

Java多线程完整版基础知识

Java多线程完整版基础知识 (翟开顺由厚到薄系列) 1.前言 线程是现代操作系统中一个很重要的概念,多线程功能很强大,java语言对线程提供了很好的支持,我们可以使用java提供的thread类很容...
  • T1DMzks
  • T1DMzks
  • 2016-07-13 00:39:53
  • 8797

java程序设计基础_陈国君版第五版_第四章习题

java程序设计基础_陈国君版第五版_第四章习题 import java.util.Scanner; public class Main4_1 { public static void main...
  • gaoenbin626
  • gaoenbin626
  • 2016-03-08 13:24:06
  • 4492

java基础(多线程,IO,集合,网络编程,泛型)

  • 2008年10月14日 11:34
  • 479KB
  • 下载

Java语言程序设计(基础篇)原书第十版第十一章答案

前面几个题有的没有导入包,后面的加进去了  自己也是刚刚学Java不久可能有的写的很啰嗦  如果有错的话希望指正!!感谢 还有这个markdown编辑器第一次用,类名没搞进去,大家凑活看。 ...
  • qq_40865682
  • qq_40865682
  • 2017-11-19 12:51:17
  • 2126
    个人资料
    持之以恒
    等级:
    访问量: 435
    积分: 284
    排名: 27万+
    文章存档
    最新评论