美文网首页
04-多线程

04-多线程

作者: abboo | 来源:发表于2020-10-17 15:03 被阅读0次

41. 多线程

主线程:执行主(main)方法的程序

JVM执行main方法,main方法会进入到栈内存,JVM会找操作系统开辟一条main方法通向cpu的执行路径,cpu就可以通过这个路径来执行main方法,而这个路径有一个名字,叫做main(主)线程

创建多线程的第一种方式:==创建Thread的子类==

java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类

实现步骤:

  1. 创建一个Thread类的子类
  2. 在Thread类中的子类中重写Thread类中的run方法,设置线程任务(开启任务要做什么?)
  3. 创建Thread类的子类对象
  4. 调用Thread类中的start方法,开启新的线程,执行run方法

void start():使该线程开始执行,Java虚拟机调用该线程run方法

结果是两个线程并发的运行,当前线程(从调用返回给start方法)和另一个线程(执行其run方法)

多次启动一个线程是非法的.特别是当线程已经结束执行后,不能再重新启动

==Java程序属于抢占式调度,哪个线程优先级高,哪个线程优先执行;同一个优先级,随机选择一个执行==

获取线程的名称:

  1. 使用Thread类中的方法getName()

String getName()

  1. 可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称

static Thread currentThread()返回对当前正在执行的线程对象的引用

设置线程名称:

  1. 使用Thread类中的方法setName()
  2. 使用一个带参数的构造方法,参数传递线程的名称,调用父类的构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字

public static void sleep(long mills)
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)

创建多线程程序的第二种方式:==实现Runnable接口==

java.lang.Runnable:Runnable接口应该由那些打算通过某一线程执行其实例的类来实现.类必须定义一个成为run的无参数方法

java.lang.Thread类的构造方法

Thread(Runnable target)分配新的Thread对象

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

实现步骤:

  1. 创建一个Runnable接口的实现类
  2. 在实现类中重写Runnable接口中的run方法,设置线程等
  3. 创建一个Runnable接口的实现类方法
  4. 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
  5. 调用Thread类中的start方法,开启新的线程run方法

实现Runnable接口创建多线程的好处:

  1. 避免了单继承的局限性:一个类只能继承一个类,其继承了Thread类就不能继承其他类,实现Runnable接口,还可以继承其他类,实现其它接口
  2. 增强了程序的拓展性,降低了程序的耦合性:实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦),实现类中,重写了run方法用来设置线程任务.创建Thread类对象,调用start方法用来开启新线程

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

使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法:

public class NoNameInnerClassThread { 
    public static void main(String[] args) { 
        // new Runnable(){ 
            // public void run(){ 
                // for (int i = 0; i < 20; i++) { 
                    // System.out.println("张宇:"+i); 
                // } 
            // } 
        // }; 
        //‐‐‐这个整体 相当于new MyRunnable() 
        Runnable r = new Runnable(){ 
            public void run(){ 
                for (int i = 0; i < 20; i++) { 
                    System.out.println("张宇:"+i); 
                } 
            } 
        //r可以替换成 new Runnable(){}
        new Thread(r).start();
        };

42. 线程安全

单线程程序不会出现线程安全

多线程程序,没有访问共享数据,不会产生问题

解决线程安全问题的一种方案:==使用同步代码块==

synchronized(锁对象){
    可能会出现线程安全问题的代码(访问了共享数据的代码)
}

注意:

  1. 通过代码块中的锁对象,可以使用任意的对象
  2. 但是必须保证多个线程使用的锁对象是同一个
  3. 锁对象的作用:把同步代码块锁住,只让一个线程在同步代码块中执行

同步锁: 对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

  1. 锁对象 可以是任意类型。
  2. 多个线程对象 要使用同一把锁。

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着 (BLOCKED)

解决线程安全问题的第二种方案:==同步方法==

同步方法:使用==synchronized==修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

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

使用步骤

  1. 吧访问了共享数据的代码抽取出来,放到一个方法中
  2. 在方法上添加synchronized修饰符

同步锁是谁?

  • 对于非static方法,同步锁就是this,就是实现类对象 new RunnableImpl()。
  • 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

解决线程安全问题的第二种方案:==Lock锁==

java.util.concurrent.locks.Lock接口

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

Lock接口中的方法:

void lock()获取锁

void unlock()释放锁

java.util.concurrent.locks.ReentrantLock implements Lock接口

使用步骤

  1. 在成员为止创建一个ReentrantLock对象
  2. 在可能会出现安全问题的代码前调用Lock接口中的方法获取lock获取锁
  3. 在可能会出现安全问题的代码后调用Lock接口中的方法获取unlock获取锁

unlock()方法可以放在finally中提高效率

43. 线程状态

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

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

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

44. 线程间通信

等待唤醒机制

  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个方法。

45. 线程池

线程可以复用,执行完一个线程,不用销毁,可以继续执行其他任务

线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源。

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

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

public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。

参数: int nThreads->创建线程池中包含的线程数量

返回值: ExecutorService接口->返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)

(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)

获取到了一个线程池ExecutorService对象,
那么怎么使用呢,

在这里定义了一个使用线程池对象的方法如下: public Future<?> submit(Runnable task) :获取线程池中的某一个线程对象,并执行 Future接口:用来记录线程任务执行完毕后产生的结果。

线程池的使用步骤

  1. 使用线程池的工厂类Executors里面提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
  2. 创建一个类,实现Runnable接口,重新run方法,设置线程任务
  3. 调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
  4. 调用ExecutorService中的方法shutdown销毁线程池(不建议使用)

Runnable实现类代码:

public class MyRunnable implements Runnable { 
    @Override 
    public void run() { 
        System.out.println("我要一个教练"); 
        try {
            Thread.sleep(2000); 
        } catch (InterruptedException e) { 
            e.printStackTrace(); 
        }
        System.out.println("教练来了: " + Thread.currentThread().getName()); System.out.println("教我游泳,交完后,教练回到了游泳池"); 
    } 
}

线程池测试类

public class ThreadPoolDemo { 
    public static void main(String[] args) { 
        // 创建线程池对象 
        ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象 
        // 创建Runnable实例对象 
        MyRunnable r = new MyRunnable(); 
        //自己创建线程对象的方式 
        // Thread t = new Thread(r); 
        // t.start(); ‐‐‐> 调用MyRunnable中的run() 
        
        // 从线程池中获取线程对象,然后调用MyRunnable中的run() 
        service.submit(r); 
        // 再获取个线程对象,调用MyRunnable中的run() 
        service.submit(r); 
        service.submit(r); 
        // 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。 
        // 将使用完的线程又归还到了线程池中 
        // 关闭线程池 
        //service.shutdown(); 
    } 
}

相关文章

网友评论

      本文标题:04-多线程

      本文链接:https://www.haomeiwen.com/subject/qsinpktx.html