18 - Java 线程

打印 上一主题 下一主题

主题 807|帖子 807|积分 2421

简介

进程(process)

对一个程序的运行状态, 以及在运行中所占用的资源(内存, CPU)的描述;
一个进程可以明确为一个程序; 但是反之, 一个程序就是一个进程, 这句话是错的。
进程的特点:


  • 独立性: 差别的进程之间是相互独立的, 相互之间资源不共享;


  • 动态性: 进程在程序中不是静止不动的, 而是一直是活动状态;


  • 并发性: 多个进程可以在一个处理器上同时运行, 互不影响;
线程(thread)

是进程的一个组成部分, 一个进程中可以包罗多个线程, 每一个线程都可以行止理一项任务;
进程在开发的时候, 会自动的创建一个线程, 这个线程叫做 主线程;
一个进程包罗多个线程, 且至少是一个, 假如一个进程中没有线程了, 这个进程会被终止;
多线程的实行是抢占式的, 多个线程在同一个进程中并发实行任务, 着实就是CPU快速的在差别的线程之间进行切换。
进程与线程的关系和区别



  • 一个程序运行后, 至少有一个进程;


  • 一个进程包罗多个线程, 至少一个线程;


  • 进程之间是资源不共享的, 但是线程之间是资源共享的;


  • 体系创建进程的时候, 必要为进程重新分配体系资源, 而创建线程则容易很多, 因此利用多线程在进行并发任务的时候, 服从比多进程高;
区别并行和并发

并行:指在同一时候,有多条指令在多个处理器上同时实行 ;
并发:指在同一时候只能有一条指令实行,但多个进程指令被快速的轮换实行;
创建线程

实现 Runnable 接口

步骤



  • 声明类 implements Runnable 接口;
  • 重写 Runnable 接口中的 run() 方法,线程实行体;
  • 创建 Runnable 实现类的对象,并把这个对象作为 Thread 的 target 进行 Thread 对象的实例化,这个 Thread 对象才是真正的线程对象 ;接纳 Runnable 接口的方式创建的多个线程可以共享同一个 target 对象的实例变量;
  • 调用 start 方法, 来启动线程 ;
  1. //自定义线程执行任务类
  2. public class MyRunnable implements Runnable{
  3.     //定义线程要执行的run方法逻辑
  4.     @Override
  5.     public void run() {
  6.         for (int i = 0; i < 10; i++) {
  7.             System.out.println("我的线程:正在执行!"+i);
  8.         }
  9.     }
  10. }
  11. public class Demo {
  12.     public static void main(String[] args) {
  13.         //创建线程执行目标类对象
  14.         Runnable runn = new MyRunnable();
  15.         //将Runnable接口的子类对象作为参数传递给Thread类的构造函数
  16.         Thread thread = new Thread(runn);
  17.         Thread thread2 = new Thread(runn);
  18.         //开启线程
  19.         thread.start();
  20.         thread2.start();
  21.         for (int i = 0; i < 10; i++) {
  22.             System.out.println("main线程:正在执行!"+i);
  23.         }
  24.     }
  25. }
复制代码
继承 Thread 类

Thread 是所有线程类的父类, 实现了对线程的抽取和封装;
方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。
步骤



  • 继承 Thread 类, 写一个 Thread 的子类 ;
  • 在子类中, 重写父类中的 run 方法, run 方法就代表了这个线程必要处理的任务(希望这个线程处理什么任务,就把这个任务写到 run 方法中), 因此, run 方法也被称为线程实行体 ;
  • 实例化这个子类对象,即是开发了一个线程 ;
  • 调用 star t方法, 来实行这个线程必要处理的任务(启动线程);
  1. //自定义线程类
  2. public class MyThread extends Thread {
  3.     //定义指定线程名称的构造方法
  4.     public MyThread(String name) {
  5.         //调用父类的String参数的构造方法,指定线程的名称
  6.         super(name);
  7.     }
  8.     /**
  9.     * 重写run方法,完成该线程执行的逻辑
  10.     */
  11.     @Override
  12.     public void run() {
  13.         for (int i = 0; i < 10; i++) {
  14.           System.out.println(getName()+":正在执行!"+i);
  15.         }
  16.     }
  17. }
  18. public class Demo{
  19.     public static void main(String[] args) {
  20.         //创建自定义线程对象
  21.         MyThread mt = new MyThread("新的线程!");
  22.         //开启新线程
  23.         mt.start();
  24.         //在主方法中执行for循环
  25.         for (int i = 0; i < 10; i++) {
  26.             System.out.println("main线程!"+i);
  27.         }
  28.     }
  29. }
  30. // 使用匿名内部类的方式创建
  31. new Thread() {
  32.     public void run() {
  33.             
  34.     }
  35. }.start();
复制代码
通过 Callable 和 Future 创建线程

Callable 接口



  • Callable 接口提供了一个 call() 方法(可以有返回值,可以声明抛出异常)可以作为线程实行体,Callable 接口里的泛型形参范例与 call() 方法返回值范例相同。
  • V call():计算结果,假如无法计算结果,则抛出一个异常。
Future 接口



  • Future 接口代表 Callable 接口里 call() 方法的返回值,表示异步计算的结果;
  • Future 接口的常用方法 V get():返回 Callable 任务里 call() 方法的返回值,假如计算抛出异常将会抛出 ExecutionException 异常,假如当前的线程在期待时被中断将会抛出 InterruptedException 异常(调用该方法将导致程序壅闭,必须比及子线程竣事后才会得到返回值);
  • V get(long timeout, TimeUnit unit):返回 Callable 任务里 call() 方法的返回值,该方法让程序最多壅闭 timeout 和 unit 指定的时间,假如经过指定时间后 Callable 任务依然没有返回值,将会抛出 TimeoutException 异常
  • boolean cancel(boolean maylnterruptlfRunning):试图取消该 Future 里关联的 Callable 任务 boolean isCancelled():假如在 Callable 任务正常完成前被取消,则返回 true ;
  • boolean isDone():假如 Callable 任务已完成,则返回 true;
FutureTask 类



  • FutureTask 实现类实现了 RunnableFuture 接口(RunnableFuture接口继承了 Runnable 接口和Future 接口)
  • 构造器:FutureTask(Callable callable)、FutureTask(Runnable runnable, V result)(指定乐成完成时 get 返回给定的结果为 result)
步骤



  • 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程实行体,而且有返回值;
  • 创建 Callable 实现类的实例,利用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值;
  • 利用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程;
  • 调用 FutureTask 对象的 get() 方法来获得子线程实行竣事后的返回值;
  1. public class CallableThreadTest implements Callable<Integer> {
  2.     public static void main(String[] args)  
  3.     {  
  4.         CallableThreadTest ctt = new CallableThreadTest();  
  5.         FutureTask<Integer> ft = new FutureTask<>(ctt);  
  6.         for(int i = 0;i < 100;i++)  
  7.         {  
  8.             System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);  
  9.             if(i==20)  
  10.             {  
  11.                 new Thread(ft,"有返回值的线程").start();  
  12.             }  
  13.         }  
  14.         try  
  15.         {  
  16.             System.out.println("子线程的返回值:"+ft.get());  
  17.         } catch (InterruptedException e)  
  18.         {  
  19.             e.printStackTrace();  
  20.         } catch (ExecutionException e)  
  21.         {  
  22.             e.printStackTrace();  
  23.         }  
  24.   
  25.     }
  26.     @Override  
  27.     public Integer call() throws Exception  
  28.     {  
  29.         int i = 0;  
  30.         for(;i<100;i++)  
  31.         {  
  32.             System.out.println(Thread.currentThread().getName()+" "+i);  
  33.         }  
  34.         return i;  
  35.     }  
  36. }
复制代码
创建线程的三种方式对比

继承 Thread 类



  • 线程类已经继承了 Thread 类,不能再继承其它父类
  • 假如必要访问当前线程,直接利用 this 即可获恰当前线程
  • 多个线程之间无法共享线程类中的实例变量
实现 Runnable、Callable 接口的方式创建多线程



  • 线程类只是实现了 Runnable 接口,还可以继承其它类
  • 假如必要访问当前线程,则必须利用 Thread. currentThread() 方法
  • 所创建的 Runnable 对象只是线程的 target,而多个线程可以共享同一个 target 对象的实例变量,以是恰当多个相同线程来处理同一份资源的情况
strart() 和 run() 的区别

start() 方法会开发一个线程, 然后在这个新的线程中实行 run() 中的逻辑,但是假如直接调用 run(),则表示必要在当前的线程中实行逻辑。
Runnable与Callable

相同点


  • 都是接口,都可以编写多线程程序,都接纳Thread.start()启动线程;
差别点


  • Runnable没有返回值;Callable可以返回实行结果,是个泛型,和Future、FutureTask配合可以用来获取异步实行的结果;
  • Callable接口的call()方法允许抛出异常;Runnable的run()方法异常只能在内部消化,不能往上继承抛;
  • Callalble接口支持返回实行结果,必要调用FutureTask.get()得到,此方法会壅闭主进程的继承往下实行,假如不调用不会壅闭。;
线程的生命周期

操作体系中线程的生命周期

操作体系中线程的生命周期通常包罗以下五个阶段:


  • 新建状态(New):利用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
  • 停当状态(Runnable):当线程对象调用了start()方法之后,该线程就进入停当状态。停当状态的线程处于停当队列中,要期待JVM里线程调度器的调度。
  • 运行状态(Running):假如停当状态的线程获取 CPU 资源,就可以实行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为壅闭状态、停当状态和死亡状态。
  • 壅闭状态(Blocked):假如一个线程实行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入壅闭状态。在睡眠时间已到或获得设备资源后可以重新进入停当状态。可以分为三种:

    • 期待壅闭:运行状态中的线程实行 wait() 方法,使线程进入到期待壅闭状态。
    • 同步壅闭:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
    • 其他壅闭:通过调用线程的 sleep() 或 join() 发出了 I/O 哀求时,线程就会进入到壅闭状态。当 sleep() 状态超时,join() 期待线程终止或超时,大概 I/O 处理完毕,线程重新转入停当状态。

  • 终止状态(Terminated):一个运行状态的线程完成任务大概其他终止条件发生时,该线程就切换到终止状态。

Java 中线程的生命周期

Java 中线程的生命周期可以细化为以下几个状态:


  • New(初始状态):线程对象创建后,但未调用 start()方法。
  • Runnable(可运行状态):调用 start()方法后,线程进入停当状态,期待 CPU 调度。
  • Blocked(壅闭状态):线程试图获取一个对象锁而被壅闭。
  • Waiting(期待状态):线程进入期待状态,必要被显式唤醒才能继承实行。
  • Timed Waiting(含期待时间的期待状态):线程进入期待状态,但指定了期待时间,超时后会被唤醒。
  • Terminated(终止状态):线程实行完成或因异常退出。

线程优先级

Java 程序中的每个线程并不是平均分配CPU时间的,为了使得线程资源分配更加合理,Java接纳的是抢占式调度方式,优先级越高的线程,优先利用CPU资源。
希望CPU花费更多的时间行止理更告急的任务,而不太告急的任务,则可以先让出一部分资源。
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY ),默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
线程的优先级一样平常分为以下三种:


  • MIN_PRIORITY  最低优先级


  • MAX_PRIORITY  最高优先级


  • NOM_PRIORITY  通例优先级
  1. public static void main(String[] args) {
  2.     Thread t = new Thread(() -> {
  3.         System.out.println("线程开始运行!");
  4.     });
  5.     t.start();
  6.     t.setPriority(Thread.MIN_PRIORITY);  //通过使用setPriority方法来设定优先级
  7. }
复制代码
优先级越高的线程,获得CPU资源的概率会越大,并不是说一定优先级越高的线程越先实行。
线程安全

保证多线程环境下共享的、可修改的状态的正确性。
线程安全必要保证几个根本特性:
常用的线程安全措施



  • 同步锁:通过 synchronized 关键字或 ReentrantLock 实现对共享资源的同步控制。
  • 原子操作类:Java 提供的 AtomicInteger、AtomicReference 等类确保多线程环境下的原子性操作。
  • 线程安全容器:如ConcurrentHashMap、CopyOnWriteArrayList 等,避免手动加锁。
  • 局部变量:线程内独立的局部变量天然是线程安全的,因为每个线程都有自己的栈空间(线程隔离)。
  • ThreadLocal:雷同于局部变量,属于线程当地资源,通过线程隔离保证了线程安全。
线程同步



  • 原子操作(atomic operation):不可被中断的一个或一系列操作。
  • 只必要对那些会改变共享资源的、不可被中断的操作进行同步即可。
  • 保证在任一时候只有一个线程可以进入修改共享资源的代码区,其它线程只能在该共享资源对象的锁池中期待获取锁。
  • 在 Java 中,每一个对象都拥有一个锁标记(monitor),也称为监督器。
  • 线程开始实行同步代码块或同步方法之前,必须先获得对同步监督器的锁定才能进入同步代码块大概同步方法进行操作。
  • 当前线程开释同步监督器:当前线程的同步代码块或同步方法实行竣事,碰到 break 或 return 语句,出现了未处理的 Error 或 Exception,实行了同步监督器对象的 wait() 方法或 Thread.join() 方法。
  • 当前线程不会开释同步监督器:当前线程的同步代码块或同步方法中调用 Thread. sleep()、Thread.yield() 方法其它线程调用了该线程的 suspend() 方法。
synchronized

同步代码块

通常推荐利用可能被并发访问的共享资源作为同步监督器对象。
  1. //同步代码块
  2. synchronized(同步监视器对象) { // 得到对象的锁,才能操作同步代    码
  3.     需要被同步代码;
  4. }
复制代码
同步方法



  • 利用 synchronized 关键字来修饰某个方法,就相当于给调用该方法的对象加了锁
  • 对于实例方法,同步方法的同步监督器是 this,即调用该方法的对象
  • 对于类方法,同步方法的同步监督器是当火线法地点类的字节码对象(如 ArrayUtil.class)
  • 不要利用 synchronized 修饰 run() 方法,而是把必要同步的操作界说在一个新的同步方法中,再在 run() 方法中调用该方法
  1. //同步方法
  2. public synchronized void m(String name){
  3.     需要被同步代码;
  4. }
复制代码
  1. public class SellTicket {
  2.     public static void main(String[] args) {
  3.         SellTicket03 sellTicket03 = new SellTicket03();
  4.         new Thread(sellTicket03).start();//第1个线程-窗口
  5.         new Thread(sellTicket03).start();//第2个线程-窗口
  6.         new Thread(sellTicket03).start();//第3个线程-窗口
  7.     }
  8. }
  9. //实现接口方式, 使用synchronized实现线程同步
  10. class SellTicket03 implements Runnable {
  11.     private int ticketNum = 100;//让多个线程共享 ticketNum
  12.     private boolean loop = true;//控制run方法变量
  13.     Object object = new Object();
  14.     //同步方法(静态的)的锁为当前类本身
  15.     //1. public synchronized static void m1() {} 锁是加在 SellTicket03.class
  16.     //2. 如果在静态方法中,实现一个同步代码块.
  17.     /*
  18.         synchronized (SellTicket03.class) {
  19.             System.out.println("m2");
  20.         }
  21.      */
  22.     public synchronized static void m1() {
  23.    
  24.     }
  25.     public static  void m2() {
  26.         synchronized (SellTicket03.class) {
  27.             System.out.println("m2");
  28.         }
  29.     }
  30.    
  31.     //1. public synchronized void sell() {} 就是一个同步方法
  32.     //2. 这时锁在 this对象
  33.     //3. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在this对象
  34.     public /*synchronized*/ void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行sell方法
  35.         synchronized (/*this*/ object) {
  36.             if (ticketNum <= 0) {
  37.                 System.out.println("售票结束...");
  38.                 loop = false;
  39.                 return;
  40.             }
  41.             //休眠50毫秒, 模拟
  42.             try {
  43.                 Thread.sleep(50);
  44.             } catch (InterruptedException e) {
  45.                 e.printStackTrace();
  46.             }
  47.             System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
  48.                     + " 剩余票数=" + (--ticketNum));//1 - 0 - -1  - -2
  49.         }
  50.     }
  51.     @Override
  52.     public void run() {
  53.         while (loop) {
  54.             sell();//sell方法是一共同步方法
  55.         }
  56.     }
  57. }
复制代码
synchronized 利用方式



  • 修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获恰当前对象实例的锁
  • 修饰静态方法: 也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。以是假如一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B必要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
  • 修饰代码块: 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
总结



  • synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。
  • synchronized 关键字加到实例方法上是给对象实例上锁。
  • 尽量不要利用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能。
锁(Lock)

锁的分类名词

锁的分类名词,有的是指锁的状态、有的指锁的特性、有的指锁的计划。
公平锁/非公平锁

公平锁是指多个线程按照申请所的顺序来获取锁。
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能是后申请的线程比先申请的线程有限获取锁。有可能,会造成优先级反转大概饥饿现象。
非公平锁的优点在于吞吐量比公平锁大。
在Java中,synchronized是一种非公平锁。
ReentrantLock则可以通过构造函数指定该锁是否公平锁,默认黑白公平锁。ReentrantLock通过AQS来实现线程调度,实现公平锁。
可重入锁

可重入锁又名递归锁,是指在同一个线程在持有锁的前提下,再碰到必要申请同一个锁的情况时可自动获取锁。而非可重入锁碰到这种情况会形成死锁,也就是“我申请我已经持有的锁,我不会开释锁也申请不到锁,以是形成死锁。”
Java中,synchronized在JDK 1.6优化后,属于可重入锁。
ReentrantLock,即Re entrant Lock,可重入锁。
  1. synchronized void A(){
  2.     System.out.println("A获取锁!");
  3.     B();
  4. }
  5. synchronized void B(){
  6.     System.out.println("B锁重入成功!");
  7. }
复制代码
 独享锁/共享锁

独享锁是指该锁一次只能被一个线程所持有,共享锁是指该锁可被多个线程所持有。
在Java中,
synchronized属于独享锁。
ReentrantLock也属于独享锁。
而Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。读锁的共享锁可保证高效的并发读,但是读写、写读、写写的过程是互斥的,防止脏读、数据丢失。独享锁和共享锁也是通过AQS实现的。
互斥锁/读写锁

上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。
互斥锁在Java中的具体实现就是ReentraLock。
读写锁在Java中的具体实现就是ReadWriteLock。
乐观锁/灰心锁

乐观锁与灰心锁不是指具体的什么范例的锁,而是指看待并发同步的角度。
灰心锁认为对同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,灰心锁采取加锁的情势,灰心地认为,不加锁的并发操作一定会出现问题。
乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会接纳实行更新,不断更新的方式更新数据,乐观地认为,不加锁的并发操作是没有事情的。
从上面的描述我们可以看出,灰心锁恰当写操作非常多的场景,乐观锁恰当读操作非常多的场景,不加锁会带来大量的性能提升。
灰心锁在Java中的利用就是利用各种锁。
乐观锁在Java中的利用就是无锁编程,经常接纳的是CAS算法,范例的例子就是原子类,通过CAS自旋实现原子操作的更新。
分段锁

分段锁着实是一种锁的计划,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的情势来实现高效的并发操作。
我们以ConcurrentHashMap来说一下分段锁的含义以及计划头脑,ConcurrentHashMap中的分段锁称为segment,它雷同于HashMap(JDK7与JDK8中HashMap的实现)的布局,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
当必要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道它要放在哪一个分段中,然后对这个分段进行加锁,以是当多线程put的时候,只要不死放在一个酚酸中,就实现了真正的并行插入。
但是在统计size的时候,即获取hashmap全局信息的时候,就必要获取所有的分段锁才能统计。
分段锁的计划目标就是细化锁的粒度,当操作不必要更新整个数组的时候,就针对数据的一项进行加锁操作。
偏向锁/轻量级锁/重量级锁

这三种所是指锁的状态,而且是针对synchronized。在 java 6通过引入锁的升级机制来实现高效synchronized。这三种锁的状态是通过对象监督器在对象头中的字段来表明的。
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的情势实行获取锁,不会壅闭,提高性能。
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会连续下去,当自旋一定次数的时候,还没有获取到锁,就会进入壅闭,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入壅闭,性能降低。
自旋锁

在Java中。自旋锁是指实行获取锁的线程不会立刻壅闭,而是接纳循环的方式去实行获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
死锁

死锁是一个经典的多线程问题。避免死锁告急吗?一旦一组Java线程发存亡锁,那么这组线程及锁涉及其已经持有锁的资源区将不再可用--除非重启应用。
死锁是计划上的bug,它并不一定发生,但它有可能发生,而且发生的情况一样平常出现在极端的高负载的情况下。
那么有什么办法为了避免死锁?

  • 让程序每次至多只能获得一个锁。但这个在多线程环境下通常不现实。
  • 计划时考虑清楚锁的顺序,尽量减少潜在的加锁交互数量
  • 避免利用synchronized,为线程期待设置期待上限,避免无限期待。
Java 中锁的利用

JDK 1.8 后的锁:


  • synchronized :非公平锁;可重入锁;独享锁;偏向锁/轻量级锁/重量级锁;
  • ReentrantLock:非公平锁;可重入锁;独享锁;互斥锁;
  • ReadWriteLock:读锁是共享锁,写锁是独享锁;读写锁;
  • StampedLock:读写锁;乐观锁;
在Java中,有多种方式可以实现线程锁,以下是一些常见的方式:
利用 synchronized 关键字

synchronized 关键字可以用来实现方法大概代码块的同步,确保同一时候只有一个线程实行被同步的代码。
  1. public synchronized void synchronizedMethod() {
  2.     // 需要同步的代码
  3. }
  4. // 或者使用同步代码块
  5. public void nonSynchronizedMethod() {
  6.     synchronized (this) {
  7.         // 需要同步的代码
  8.     }
  9. }
复制代码
利用 ReentrantLock

ReentrantLock 是一个可重入的锁,它比 synchronized 更灵活,提供了更多的功能,比如期待可中断、可实现公平锁期待时间最长的线程优先获取锁等。
  1. import java.util.concurrent.locks.ReentrantLock;
  2. public class LockExample {
  3.     private final ReentrantLock lock = new ReentrantLock();
  4.     public void lockMethod() {
  5.         lock.lock();
  6.         try {
  7.             // 需要同步的代码
  8.         } finally {
  9.             lock.unlock();
  10.         }
  11.     }
  12. }
复制代码
利用 StampedLock

StampedLock 是JDK8引入的新的锁机制,它提供了一种乐观锁的实现,相比于 ReentrantLock,它的读写锁机制更加灵活,可以有更高的并发。
  1. import java.util.concurrent.locks.StampedLock;
  2. public class StampedLockExample {
  3.     private final StampedLock lock = new StampedLock();
  4.     public void readMethod() {
  5.         long stamp = lock.tryReadLock();
  6.         try {
  7.             // 读操作
  8.         } finally {
  9.             lock.unlockRead(stamp);
  10.         }
  11.     }
  12.     public void writeMethod() {
  13.         long stamp = lock.writeLock();
  14.         try {
  15.             // 写操作
  16.         } finally {
  17.             lock.unlockWrite(stamp);
  18.         }
  19.     }
  20. }
复制代码
利用 ReadWriteLock

ReadWriteLock 接口界说了读写锁的两个锁,一个是只对数据进行读操作的锁,另一个是只对数据进行写操作的锁。
  1. import java.util.concurrent.locks.ReentrantReadWriteLock;
  2. public class ReadWriteLockExample {
  3.     private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
  4.     private final Lock r = rwl.readLock();
  5.     private final Lock w = rwl.writeLock();
  6.     public void readMethod() {
  7.         r.lock();
  8.         try {
  9.             // 读操作
  10.         } finally {
  11.             r.unlock();
  12.         }
  13.     }
  14.     public void writeMethod() {
  15.         w.lock();
  16.         try {
  17.             // 写操作
  18.         } finally {
  19.             w.unlock();
  20.         }
  21.     }
  22. }
复制代码
线程控制

Java中,线程控制主要涉及到以下几个方面:


  • 线程的启动
  1. Thread t1 = new Thread(new Runnable() {
  2.     public void run() {
  3.         System.out.println("线程启动");
  4.     }
  5. });
  6. t1.start();
复制代码


  • 线程的期待
  1. Thread t2 = new Thread(new Runnable() {
  2.     public void run() {
  3.         try {
  4.             Thread.sleep(1000); // 休眠1秒
  5.         } catch (InterruptedException e) {
  6.             e.printStackTrace();
  7.         }
  8.         System.out.println("线程等待结束");
  9.     }
  10. });
  11. t2.start();
复制代码


  • 线程的中断
  1. Thread t3 = new Thread(new Runnable() {
  2.     public void run() {
  3.         while (!Thread.currentThread().isInterrupted()) {
  4.             // 执行任务
  5.         }
  6.         System.out.println("线程被中断");
  7.     }
  8. });
  9. t3.start();
  10. t3.interrupt();
复制代码


  • 线程的优先级控制
  1. Thread t4 = new Thread(new Runnable() {
  2.     public void run() {
  3.         System.out.println("线程的优先级: " + Thread.currentThread().getPriority());
  4.     }
  5. });
  6. t4.setPriority(Thread.MAX_PRIORITY); // 设置最高优先级
  7. t4.start();
复制代码


  • 线程的归并
  1. Thread t5 = new Thread(new Runnable() {
  2.     public void run() {
  3.         System.out.println("被合并的线程");
  4.     }
  5. });
  6. t5.start();
  7. t5.join(); // 等待t5线程结束,然后继续执行主线程
复制代码


  • 线程的同步与和谐
  1. final Object lock = new Object();
  2. Thread t6 = new Thread(new Runnable() {
  3.     public void run() {
  4.         synchronized (lock) {
  5.             try {
  6.                 lock.wait(); // 线程等待
  7.             } catch (InterruptedException e) {
  8.                 e.printStackTrace();
  9.             }
  10.         }
  11.         System.out.println("线程被唤醒");
  12.     }
  13. });
  14. Thread t7 = new Thread(new Runnable() {
  15.     public void run() {
  16.         synchronized (lock) {
  17.             lock.notify(); // 唤醒等待的线程
  18.         }
  19.     }
  20. });
  21. t6.start();
  22. // 确保t6线程已经开始等待
  23. try {
  24.     Thread.sleep(1000);
  25. } catch (InterruptedException e) {
  26.     e.printStackTrace();
  27. }
  28. t7.start();
复制代码
用户线程和保卫线程



  • 用户线程:也叫工作线程,当线程的任务实行完或通知方式竣事 。
  • 保卫线程:一样平常是为工作线程服务的,当所有的用户线程竣事,保卫线程自动竣事setDaemon(True) 。
  • 常见的保卫线程:垃圾回收机制 。
线程组



  • ThreadGroup 类,表示一个线程的集合,可以对一组线程进行集中管理(同时控制这批线程)
  • 在默认情况下,子线程和创建它的父线程处于同一个线程组内
线程让步



  • 让实行的线程停息,进入停当状态。
  • 当某个线程调用了 yield() 方法停息之后,只有优先级与当前线程相同,大概优先级比当前线程更高的处于停当状态的线程才会获得实行的机会。
同步机制与 ThreadLocal



  • 假如多个线程之间必要共享资源,以到达线程之间的通信功能,就利用同步机制。
  • 假如仅仅必要隔离多个线程之间的共享辩论,则可以利用 ThreadLocal。
线程通信

线程通信机制

并发模型
通信机制
同步机制
共享内存
线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信
同步是显式进行的,即必须显式指定某个方法或某段代码必要在线程之间互斥实行
消息传递
线程之间通过显式的发送消息来到达交互目标,如 Actor 模型
由于消息的发送必须在消息的接收之前,因此同步是隐式进行的


  • Java 的线程间通过共享内存的方式进行通信
利用 Object 类中的方法



  • Object 类中用于操作线程通信的实例方法

    • wait():调用该方法的当前线程会开释对该同步监督器(调用者)的锁定,JVM 把该线程存放到期待池中,期待其他的线程唤醒该线程(该方法声明抛出了 InterruptedException 异常)(为了防止虚假唤醒,此方法应始终在循环中利用,即被唤醒后必要再次判断是否满意唤醒条件)
    • notify():调用该方法的当前线程唤醒在期待池中的恣意一个线程,并把该线程转到锁池中期待获取锁
    • notifyAll():调用该方法的当前线程唤醒在期待池中的所有线程,并把该线程转到锁池中期待获取锁

  • 这些方法必须在同步块中利用,且只能被同步监督器对象来调用,否则会引发 IllegalMonitorStateException 异常
  1. public class ShareResource {
  2.     // 标识数据是否为空(初始状态为空)
  3.     private boolean empty = true;
  4.     // 需要同步的方法
  5.     public synchronized void doWork() {
  6.         try {
  7.             while (!empty) { // 不空,则等待
  8.                 this.wait();
  9.             }
  10.             ... // TODO
  11.             empty = false; // 修改标识
  12.             this.notifyAll(); // 通知其它线程
  13.         } catch (InterruptedException e) {
  14.             e.printStackTrace();
  15.         }
  16.     }
  17. }
复制代码
利用 Condition 接口中的方法



  • java.util.concurrent.locks 包中,Condition 接口中的 await()、signal()、signalAll() 方法替换了 Object 监督器方法的利用(await() 方法也声明抛出了 InterruptedException 异常)
  • 通过 Lock 对象调用 newCondition() 方法,返回绑定到此 Lock 对象的 Condition 对象
  1. public class ShareResource {
  2.     // 创建使用 private final 修饰的锁对象
  3.     private final Lock lock = new ReentrantLock();
  4.     // 获得指定 Lock 对象对应的 Condition
  5.     private final Condition cond = lock.newCondition();
  6.     // 标识数据是否为空(初始状态为空)
  7.     private boolean empty = true;
  8.     // 需要同步的方法
  9.     public void doWork() {
  10.         lock.lock(); // 进入方法后,立即获取锁
  11.         try {
  12.             while(!empty) { // 判断是否方法阻塞
  13.                 cond.await();
  14.             }
  15.             ... // TODO
  16.             empty = false; // 修改标识
  17.             cond.signalAll(); // 通知其它线程
  18.         } catch (InterruptedException e) {
  19.             e.printStackTrace();
  20.         } finally {
  21.             lock.unlock(); // 使用 finally 块释放锁
  22.         }
  23.     }
  24. }
复制代码
线程常用方法

Thread 对象调用的方法(实例方法)

public void start():使该线程开始实行;Java 虚拟机调用该线程的 run 方法,只能被处于新建状态的线程调用,否则会引发 IllegalThreadStateException 异常。
public void run():假如该线程是利用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不实行任何操作并返回。
public final void setName(String name):改变线程名称,使之与参数 name 相同,为线程设置名字,在默认情况下,主线程的名字为 main,用户启动的多个线程的名字依次为 Thread-0、Thread-1、Thread-2、…、Thread-n 等。
public final void setPriority(int priority):更改线程的优先级(范围是 1~10 之间)。
public final void setDaemon(boolean on):将该线程标记为保卫线程或用户线程,on 为"true"时,将该线程设置成保卫线程,该方法必须在 start() 之前调用,否则会引发 IllegalThreadStateException 异常。
boolean isDaemon():判断该线程是否为保卫线程。
public final void join(long millisec):期待该线程终止的时间最长为 millis 毫秒,而当前正在实行的线程进入壅闭状态(联合线程)(该方法声明抛出了 InterruptedException 异常)。
public void interrupt():中断线程。
public final boolean isAlive():测试线程是否处于活动状态。
Thread 类的静态方法(类方法)

public static void yield():停息当前正在实行的线程对象,并实行其他线程,转入停当状态(线程让步)。
public static void sleep(long millisec):在指定的毫秒数内让当前正在实行的线程休眠(停息实行),此操作受到体系计时器和调度程序精度和准确性的影响,并进入壅闭状态(线程睡眠)(该方法声明抛出了 InterruptedException 异常)。
public static boolean holdsLock(Object x):当且仅当,当前线程在指定的对象上保持监督器锁时,才返回 true。
public static Thread currentThread():返回对当前正在实行的线程对象的引用。
public static void dumpStack():将当前线程的堆栈跟踪打印至标准错误流。
线程相关方法

壅闭和唤醒的方法,是 Object 中的方法,必须在synchronized修饰的代码块大概方法内部才可以调用(因为要操作基于某个对象的锁的信息维护)。
wait():让获取 synchronized 锁资源的线程,进入锁的期待池,而且开释锁资源。
notify():让获取 synchronized 锁资源的线程,唤醒期待池中的线程,而且添加到锁池中。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

渣渣兔

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表