多线程

并发与并行

  • 并发:指两个或多个事件在同一个时间段内发生。
  • 并行:指两个或多个事件在同一时刻发生(同时发生)。

线程与进程

  • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多 个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创 建、运行到消亡的过程。
  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程 中是可以有多个线程的,这个应用程序也可以称之为多线程程序。 简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程。

创建线程类
Java使用java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是 完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。 Java中通过继承Thread类来创建并启动多线程的步骤如下:

1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把 run()方法称为线程执行体。
2. 创建Thread子类的实例,即创建了线程对象
3. 调用线程对象的start()方法来启动该线程

方法类:

public class MyThread extends Thread{
	//通过构造函数对线程进行命名
    public MyThread(String name) {
        super(name);
    }

    @Override
    //设置线程任务,即线程执行体
    public void run() {
        for (int i = 0; i <5 ; i++) {
            System.out.println(this.getName()+"--->"+i);
        }
    }
}

测试类:

public class demo01 {
    public static void main(String[] args)  {
		/创建三个线程
        MyThread p1 = new MyThread("p1");
        p1.start();

        MyThread p2 = new MyThread("p2");
        p2.start();

        MyThread p3 = new MyThread("p3");
        p3.start();
    }
}

运行结果:

p2--->0
p3--->0
p3--->1
p1--->0
p3--->2
p2--->1
p2--->2
p2--->3
p2--->4
p3--->3
p1--->1
p3--->4
p1--->2
p1--->3
p1--->4

从上面可以看到,线程是交替运行的,也就是因为java中是抢占式调度。那个线程抢到了CPU那个线程就执行。

多线程原理:

  • 程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的 start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。
  • 通过这张图我们可以很清晰的看到多线程的执行流程,那么为什么可以完成并发执行呢?我们再来讲一讲原理。
  • 多线程执行时,到底在内存中是如何运行的呢?以上个程序为例,进行图解说明:
  • 多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。
    来自黑马

Thread类

  • 构造方法

      public Thread() :分配一个新的线程对象。 
      public Thread(String name) :分配一个指定名字的新的线程对象。
      public Thread(Runnable target) :分配一个带有指定目标新的线程对象。 
      public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
    
  • 常用方法

      public String getName() :获取当前线程名称。
      public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。 
      public void run() :此线程要执行的任务在此处定义代码。
      public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
      public static Thread currentThread() :返回对当前正在执行的线程对象的引用。 
    

创建线程的第二种方法

  • 采用 java.lang.Runnable 也是非常常见的一种,我们只需要重写run方法即可。
  • 步骤如下:
  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正 的线程对象。
  3. 调用线程对象的start()方法来启动线程。
    代码实现:
public class MyRunnable implements   Runnable {
    @Override
    //设置线程任务
    public void run() {
        for(int i = 0 ;i < 5 ;i ++ ){
        	//返回当前线程的名称和i
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}
public class demo02 {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
		//通过Thread类的构造方法,创建线程t来执行线程任务
        Thread t = new Thread(mr,"Runnable");
        t.start();
    }
}

运行结果:

Runnable--->0
Runnable--->1
Runnable--->2
Runnable--->3
Runnable--->4

Thread和Runnable的区别
实现Runnable接口比继承Thread类所具有的优势:

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

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

匿名内部类创建线程

public class demo03 {
    public static void main(String[] args) {
        //匿名内部类实现Runnable接口
        Runnable r =new Runnable(){  //多态
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+"--->"+i);
                }
            }
        };
        Thread t = new Thread(r);
        t.start();

        //匿名内部类实现Thread类
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 10 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"--->"+i);
                }
            }
        }.start();
    }
}

线程安全

  • 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样 的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
  • 线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写 操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步, 否则的话就可能影响线程安全。

案例:实现买票窗口

  1. 第一种方法:同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
public class ticketRunnable implements Runnable {
   private int ticket = 100 ;
    Object object= new Object();
    @Override
    public void run() {
        while (ticket > 0) {
            synchronized (object) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(10);
                        System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                        ticket--;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

  1. 第二种方法:同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外 等着。
public class ticketRunnable implements Runnable {
    private int ticket = 100 ;
    @Override
    public void run() {
        while (ticket > 0) {
            payTicket();
        }
    }
    public synchronized void payTicket(){
            if (ticket > 0) {
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    }
}
  1. 第三种方法:java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ticketRunnable implements Runnable {
   private int ticket = 100 ;
   	//ReentrantLock实现了Lock接口
    Lock l =new ReentrantLock();
    @Override
    public void run() {
        while (ticket > 0) {
        	//得到锁
            l.lock();
            if (ticket > 0) {
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                	//释放锁
                    l.unlock();
                }
            }
        }
    }
}

Obejct类中的方法

  • void wait()
    在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
  • void notify()
    唤醒在此对象监视器上等待的单个线程。会继续执行wait方法之后的代码。

进入到TimeWaiting(计时等待)有两种方式

1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,
  还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态

唤醒的方法:

     void notify() 唤醒在此对象监视器上等待的单个线程。
     void notifyAll() 唤醒在此对象监视器上等待的所有线程。

线程通信

  • 为什么要处理线程间通信:
    多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们 希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
  • 如何保证线程间通信有效利用资源:
    多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就 是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效 的利用资源。而这种手段即—— 等待唤醒机制。

等待唤醒机制

  • 什么是等待唤醒机制
    这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是 故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时 候你们更多是一起合作以完成某些任务。
  • 就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将 其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。
  • wait/notify 就是线程间的一种协作机制。

调用wait和notify方法需要注意的细节:

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

互斥锁

  • 1.同步

      * 使用ReentrantLock类的lock()和unlock()方法进行同步
    
  • 2.通信

      * 使用ReentrantLock类的newCondition()方法可以获取Condition对象
      * 需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
      * 不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了
    

多个线程的通信:

package Threaddemo;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

class Thread_communicate extends  Thread{

    public static void main(String[] args) {
        final printer p = new printer();
        new Thread(){
            @Override
            public void run() {
                while(true) {
                    try {
                        p.print1();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                while (true) {
                    try {
                        p.print2();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                while (true) {
                    try {
                        p.print3();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
}
class printer{
    private int flag = 1 ;
    //创建对象
    private ReentrantLock r = new ReentrantLock();
    //三个线程有三种不同的状态
    private Condition c1 = r.newCondition();
    private Condition c2 = r.newCondition();
    private Condition c3 = r.newCondition();
    public void print1() throws InterruptedException {
            //lock()方法获得锁与unlock()方法释放锁实现同步
            r.lock();
            if(flag != 1){
                c1.await();
            }
            System.out.println("窝窝头,一块钱四个");
            flag = 2 ;
            //唤醒c2
            c2.signal();
            r.unlock();
    }
    public void print2() throws InterruptedException {
            r.lock();
            if (flag != 2) {
                c2.await();
            }
            System.out.println("谁TM买小米!");
            flag = 3 ;
            c3.signal();
            r.unlock();
    }
    public void print3() throws InterruptedException {
        r.lock();
        if (flag != 3) {
            c3.await();
        }
        System.out.println("菠菜,菠菜,贱卖!");
        flag = 1 ;
        c1.signal();
        r.unlock();
    }
}

窝窝头,一块钱四个
谁TM买小米!
菠菜,菠菜,贱卖!
窝窝头,一块钱四个
谁TM买小米!
菠菜,菠菜,贱卖!
窝窝头,一块钱四个
谁TM买小米!
菠菜,菠菜,贱卖!

线程池

  • 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池。
    在这里插入图片描述
    合理利用线程池能够带来三个好处:
  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内 存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

线程池的使用

  • Java里面线程池的顶级接口是 java.util.concurrent.Executor ,但是严格意义上讲 Executor 并不是一个线程 池,而只是一个执行线程的工具。真正的线程池接口是 java.util.concurrent.ExecutorService 。

  • 要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优 的,因此在 java.util.concurrent.Executors 线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官 方建议使用Executors工程类来创建线程池对象。

  • Executors类中有个创建线程池的方法如下:

    public static ExecutorService newFixedThreadPool(int nThreads) 
    返回线程池对象。(创建的是有界线 程池,也就是池中的线程个数可以指定最大数量)
    
  • 获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:

      public Future<?> submit(Runnable task) :
      获取线程池中的某一个线程对象,并执行
      Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。
    
import java.util.concurrent.*;

//Object泛型,也可以是其他,string,Integer都行
class Thread_Callable implements Callable<String> {
    @Override
    //能够抛出异常
    public String call() throws Exception {
        String s = "妈妈,我想吃烤山药";
        return Thread.currentThread().getName()+s ;
    }
}
class demo_Callable{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建一个有两个线程的线程池
        ExecutorService es = Executors.newFixedThreadPool(2);
        //如果没有返回值,直接调用submit()方法调用就行
        Future ft = es.submit(new MyThread());
        //上面这个线程没有返回值,所以返回null
        System.out.println(ft.get());
        //有返回值时就需要用Future接收,再调用get()方法
        
        Future<String> ft1 =  es.submit(new Thread_Callable());
        System.out.println(ft1.get());
        //关闭线程池
        es.shutdown();
        
        //这种方法很少用,一般配合线程池使用,如上
        FutureTask<String> ft2 = new FutureTask<String>(new Thread_Callable());
        Thread t = new Thread(ft2);
        t.start();
        System.out.println(ft2.get());
    }
}
public class MyRunnable implements   Runnable {
    @Override
    public void run() {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }
}

运行结果:

Thread-0--->0
Thread-0--->1
Thread-0--->2
Thread-0--->3
Thread-0--->4
null
pool-1-thread-2妈妈,我想吃烤山药
Thread-1妈妈,我想吃烤山药
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值