多线程详细解析

多线程

一、基本概念

并发:指两个或多个事件在一同一个时间段内交替发生。
并行:指两个或多个事件在一同一时刻同时发生(同时发生)。
       一般在单处理器上多线程是交替并发执行的。但在多处理器
 上并发执行的程序可以分配到多个处理器上,实现多任务并行执行
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。

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

  简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程 
  
  n*线程==>进程    n*进程==>程序
线程调度
- 分时调度
  所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
- 抢占式调度
  优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,
让CPU的使用率更高。


二、实现步骤

Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。

1、java实现多线程的三种方法

① 继承Thread类来创建并启动多线程
    1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
    2. 创建Thread子类的实例,即创建了线程对象
    3. 调用线程对象的start()方法来启动该线程代码如下:
② 实现Runnable接口 重写run方法
java.lang.Runnable
步骤如下:
1.定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
2.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
3.调用线程对象的start()方法来启动线程

通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。
Thread类实际上也是实现了Runnable接口的类。
在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnabletarget)构造出对象,然后调用Thread对象的start()方法来运行多线程代码。实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。
③ 匿名内部类方式实现线程的创建
使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法
④ Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。
但是如果实现了Runable接口的话,则很容易的实现资源共享。

总结:实现Runnable接口比继承Thread类所具有的优势:
1.适合多个相同的程序代码的线程去共享同一个资源。
2.可以避免java中的单继承的局限性。
3.增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
4.线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

扩充:在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程


三、代码实现

1、java实现多线程的三种方法

①、继承Thread类

public class MyThread extends Thread {
	//定义指定线程名称的构造方法
	public MyThread(String name) {
		//调用父类的String参数的构造方法,指定线程的名称
		super(name);
	}
	/**
	 * 重写run方法,完成该线程执行的逻辑
	 */
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(getName()+":正在执行!"+i);
		}
	}
}
------------------------------------------------------------------------------
1.2、Thread类的常用方法
    public Thread():分配一个新的线程对象。
    public Thread(Stringname):分配一个指定名字的新的线程对象。    
    public Thread(Runnabletarget):分配一个带有指定目标新的线程对象。
    public Thread(Runnabletarget,Stringname):分配一个带有指定目标新的线程对象并指定名字。
    常用方法:
    public String getName():获取当前线程名称。
    public void start():导致此线程开始执行;Java虚拟机调用此线程的run方法。!主程序开启线程时使用!
    public void run():此线程要执行的任务在此处定义代码。
    public static void sleep(longmillis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。               public static Thread currentThread():返回对当前正在执行的线程对象的引用。
    翻阅API后得知创建线程的方式总共有两种,一种是继承Thread类方式,一种是实现Runnable接口方式,方式一我们上一天已经完成,接下来讲解方式二实现的方式。
②、实现Runnable接口 重写run方法。

public class runnableimp implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

public class runnnable1 {
    public static void main(String[] args) {
        runnableimp ra = new runnableimp();
        Thread th = new Thread(ra);
        th.start();

        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

③、匿名内部类实现多线程
//继承Tread的匿名写法
new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName()+i);
                }
            }

        }.start();
//实现接口的匿名写法
new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName()+i);
                }
            }
        }).start();

四、线程安全

1、线程同步的四种方式

①基础概念
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
一个线程对共享数据进行更改时,其他线程不可以对改线程进行改动!
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
②同步代码块:synchronized
synchronized(任意对象){
    代码块内容同一时刻只可以有一个线程进行访问
}
③同步方法 方法有synchronized修饰
public synchronized void method(){
	方法内容同一时刻只可以有一个线程进行访问
}
调用该方法时同一时刻也就只有一个线程可以调用
④lock锁
 //在锁内的代码同一时刻只可以有一个线程进行访问
 Lock l = new ReentrantLock();//创建锁对象
 public void run() {
        while(true){
            l.lock();
            try {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "  w第" + ticket--);
                }
            }
            finally {
                l.unlock();
            }


        }
    }
⑤等待唤醒机制
等待唤醒机制什么是等待唤醒机制这是多个线程间的一种协作机制。
谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。就是在一个线程进行了规定操作后,就进入等待状态(wait()),等待其他线程执行完他们的指定代码过后再将其唤醒(notify());在有多个线程进行等待时,如果需要,可以使用 notifyAll()来唤醒所有的等待线程。
wait/notify 就是线程间的一种协作机制。等待唤醒中的方法等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:
1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
2. notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
3. notifyAll:则释放所通知对象的 wait set 上的全部线程。
注意:哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。

总结如下:如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态调用wait和notify方法需要注意的细节1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
⑥等待唤醒实列
//包子对象
package demo04;

public class Baozi {
    private String pi;
    private String xian;
    private Boolean flag=false;

    public Baozi() {
        this.pi = pi;
    }

    public String getPi() {
        return pi;
    }

    public void setPi(String pi) {
        this.pi = pi;
    }

    public String getXian() {
        return xian;
    }

    public void setXian(String xian) {
        this.xian = xian;
    }

    public Boolean getFlag() {
        return flag;
    }

    public void setFlag(Boolean flag) {
        this.flag = flag;
    }

    @Override
    public String toString() {
        return "Baozi{" +
                "pi='" + pi + '\'' +
                ", xian='" + xian + '\'' +
                ", flag=" + flag +
                '}';
    }
}


//包子店
package demo04;

public class dian extends Thread {
    private Baozi bz;
    public dian() {
    }
    public dian(Baozi bz){
        this.bz=bz;
    }

    @Override
    public  void run() {
        while (true){
            synchronized (bz) {
                if (bz.getFlag() == true) {
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("生产包子;等待2s");
                try {
                    sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                bz.setFlag(true);
                bz.setPi("厚皮");
                bz.setXian("猪肉");
                System.out.println(bz + "包子生产好了 可以开吃了");
                bz.notify();//提醒吃货开吃
            }
        }
    }
}

//顾客
package demo04;
public class chihuo extends Thread {
    private Baozi bz;
    public chihuo() {
    }
    public chihuo(Baozi bz) {
        this.bz = bz;
    }

    @Override
    public void run() {
        while (true){
            synchronized (bz){
                if(bz.getFlag()==false){
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                bz.setFlag(false);
                System.out.println("吃货吃了"+bz+"的包子!");
                bz.notify();

            }
        }
    }
}
//主函数
package demo04;

public class main {
    public static void main(String[] args) {
        Baozi bz = new Baozi();
        dian d = new dian(bz);
        chihuo c = new chihuo(bz);
        d.start();
        c.start();
    }
}

总体逻辑:
    包子店判断没有包子就生产包子 唤醒顾客释放资源 包子店和顾客竞争资源 如果包子店再次获得资源 判断他有包子 进入无限等待 资源返回 顾客得到资源 吃完包子 唤醒包子店 释放资源  然后包子店和顾客再次竞争资源 如果顾客竞争到了资源 判断没有包子 顾客进入无限等待 返回资源 包子店得到资源 判断没有包子:(循环!) 
    	

2、线程状态

线程状态导致状态发生条件
1、NEW(新建)线程刚被创建,但是并未启动。还没调用start方法。
2、Runnable(可运行)线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。3、Blocked(锁阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
4、Waiting(无限等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
5、TimedWaiting(计时等待)同waiting状态,有几个方法有超时参数,调用他们将进入TimedWaiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep、Object.wait。
6、Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值