Java线程

目录

一、线程与进程

二、线程调度

三、同步与异步

四、并发与并行

五、Java线程的使用

1、继承Thread

2、实现Runnable

 3、通过匿名内部类实现

六、Thread常用方法 

1、设置和获取线程名称 

 2、线程休眠sleep

 3、线程阻塞

 4、线程中断

七、线程的六种不同状态

八、守护线程

九、线程安全

1、为什么会出现线程安全问题?  

2、 解决方案一:同步代码块sychronized

3、 解决方案二:同步方法

4、解决方案三:显式锁

5、Java公平锁和非公平锁

6、Java线程死锁

十、Java多线程通信问题

十一、带返回值的线程Callable

1、Runnable 与 Callable

2、Callable使用步骤

3、Runnable 与 Callable的相同点

4、Runnable 与 Callable的不同点

5、Callable获取返回值

6、FutureTask类

十二、Java线程池

1、线程池 Executors

2、线程池的好处

3、Java中的四种线程池 . ExecutorService

十三、Lambda表达式


一、线程与进程

进程

  • 是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间 。

线程

  • 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行。一个进程最少有一个线程。
  • 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。

二、线程调度

分时调度

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

抢占式调度

  • 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
  • CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻, 只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

三、同步与异步

同步:排队执行 , 效率低但是安全。

异步:同时执行 , 效率高但是数据不安全。

四、并发与并行

并发:指两个或多个事件在同一个时间段内发生。

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

五、Java线程的使用

1、继承Thread

Java线程

Java线程

 运行结果:

  • 第一次:

Java线程

  •  第二次:

Java线程

 程序线程执行的时序图:

Java线程

2、实现Runnable

Java线程

Java线程

 运行结果:

Java线程

实现 Runnable与继承Thread比较

Java线程

 3、通过匿名内部类实现

Java线程

 运行结果:

Java线程

六、Thread常用方法 

1、设置和获取线程名称 

Java线程

 2、线程休眠sleep

每隔一秒输出一次:

Java线程

 3、线程阻塞

 https://blog.csdn.net/sunshine_2211468152/article/details/87299708

https://baike.baidu.com/item/%E7%BA%BF%E7%A8%8B%E9%98%BB%E5%A1%9E/2233470?fr=aladdin

4、线程中断

Java线程

Java线程

Java线程

Java线程

 修改run方法,发现中断标记后自杀:(可以先释放占用的资源后自杀)

Java线程

Java线程

七、线程的六种不同状态

Java线程

 Java线程

 详见:https://blog.csdn.net/pange1991/article/details/53860651

八、守护线程

 线程:分为守护线程和用户线程

  • 用户线程:当一个进程不包含任何的存活的用户线程时,进程结束。
  • 守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。

代码示例:

Java线程

Java线程

 代码改造:

Java线程

 运行结果:

Java线程

九、线程安全

1、为什么会出现线程安全问题?  

实例:

Java线程

Java线程

Java线程

 运行结果出现了余票为负的情况

分析:

线程1 线程2 线程3 count
while(count>0) 就绪 1
system.out.println("正在准备卖票") 就绪 1
Thread.sleep(1000) while(count>0) 1
system.out.println("正在准备卖票") 1
Thread.sleep(1000) 1
while(count>0) 1
system.out.println("正在准备卖票") 1
Thread.sleep(1000) 1
count-- 0
system.out.println("出票成功,余额:"+count) 0
while(count>0) 0
count-- -1
system.out.println("出票成功,余额:"+count) -1
count-- -2
system.out.println("出票成功,余额:"+count) -2

2、 解决方案一:同步代码块sychronized

要看同一把锁!

Java线程

Java线程

 把该任务分配给三个子线程,争夺同一个Object对象o的锁:排队执行

Java线程

3、 解决方案二:同步方法

1、同步方法的锁是?

非静态方法:this

静态方法:类名.class

2、一个类里面有多个同步方法?

只要一个同步方法运行,其他同步方法就不能运行

Java线程

Java线程

4、解决方案三:显式锁

线程分配到同一个Ticket任务,争夺同一把锁l

Java线程

Java线程

5、Java公平锁和非公平锁

 锁Lock分为公平锁和非公平锁。

  • 公平锁:表示线程获取锁的顺序是按照加锁的顺序来分配的,及先来先得,先进先出的顺序。
  • 非公平锁:表示获取锁的抢占机制,是随机获取锁的,和公平锁不一样的就是先来的不一定能拿到锁,有可能一直拿不到锁,所以结果不公平。 

公平锁,就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁, 否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。

非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式

Java线程

以下内容转自链接:https://www.imooc.com/article/284585

(1)什么是非公平锁?Java线程

如上图,现在线程1加了锁,然后线程2尝试加锁,失败后进入了等待队列,处于阻塞中。然后线程1释放了锁,准备来唤醒线程2重新尝试加锁。

注意一点,此时线程2可还停留在等待队列里啊,还没开始尝试重新加锁呢!然而,不幸的事情发生了,这时半路杀出个程咬金,来了一个线程3!线程3突然尝试对ReentrantLock发起加锁操作,此时会发生什么事情?

很简单!线程2还没来得及重新尝试加锁呢。也就是说,还没来得及尝试重新执行CAS操作将state的值从0变为1呢!线程3冲上来直接一个CAS操作,尝试将state的值从0变为1,结果还成功了!一旦CAS操作成功,线程3就会将“加锁线程”这个变量设置为他自己。给大家来一张图,看看这整个过程:

Java线程

明明人家线程2规规矩矩的排队领锁呢,结果你线程3不守规矩,线程1刚释放锁,不分青红皂白,直接就跑过来抢先加锁了。这就导致线程2被唤醒过后,重新尝试加锁执行CAS操作,结果毫无疑问,失败!

原因很简单啊!因为加锁CAS操作,是要尝试将state从0变为1,结果此时state已经是1了,所以CAS操作一定会失败!一旦加锁失败,就会导致线程2继续留在等待队列里不断的等着,等着线程3释放锁之后,再来唤醒自己,真是可怜!先来的线程2居然加不到锁!

同样给大家来一张图,体会一下线程2这无助的过程:

Java线程

上述的锁策略,就是所谓的非公平锁

如果你用默认的构造函数来创建ReentrantLock对象,默认的锁策略就是非公平的。在非公平锁策略之下,不一定说先来排队的线程就就先会得到机会加锁,而是出现各种线程随意抢占的情况。那如果要实现公平锁的策略该怎么办呢?也很简单,在构造ReentrantLock对象的时候传入一个true即可:

ReentrantLock lock = new ReentrantLock(true)

此时就是说让他使用公平锁的策略,那么公平锁具体是什么意思呢?

(2)什么是公平锁?

咱们重新回到第一张图,就是线程1刚刚释放锁之后,线程2还没来得及重新加锁的那个状态。
Java线程

同样,这时假设来了一个线程3,突然杀出来,想要加锁。

如果是公平锁的策略,那么此时线程3不会跟个愣头青一样盲目的直接加锁。他会先判断一下:咦?AQS的等待队列里,有没有人在排队啊?如果有人在排队的话,说明我前面有兄弟正想要加锁啊!如果AQS的队列里真的有线程排着队,那我线程3就不能跟个二愣子一样直接抢占加锁了。因为现在咱们是公平策略,得按照先来后到的顺序依次排队,谁先入队,谁就先从队列里出来加锁!所以,线程3此时一判断,发现队列里有人排队,自己就会乖乖的排到队列后面去,而不会贸然加锁!

同样,整个过程我们用下面这张图给大家直观的展示一下:

Java线程

上面的等待队列中,线程3会按照公平原则直接进入队列尾部进行排队。接着,线程2不是被唤醒了么?他就会重新尝试进行CAS加锁,此时没人跟他抢,他当然可以加锁成功了。然后呢,线程2就会将state值变为1,同时设置“加锁线程”是自己。最后,线程2自己从等待队列里出队。

整个过程,参见下图:

Java线程

 这个就是公平锁的策略,过来加锁的线程全部是按照先来后到的顺序,依次进入等待队列中排队的,不会盲目的胡乱抢占加锁,非常的公平。
 

6、Java线程死锁

(1)死锁的定义

     多线程以及多进程改善了系统资源的利用率并提高了系统 的处理能力。然而,并发执行也带来了新的问题——死锁。所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。

     所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

(2)死锁产生的原因

1、系统资源的竞争

    通常系统中拥有的不可剥夺资源,其数量不足以满足多个进程运行的需要,使得进程在运行过程中,会因争夺资源而陷入僵局,如磁带机、打印机等。只有对不可剥夺资源的竞争才可能产生死锁,对可剥夺资源的竞争是不会引起死锁的。

2、进程推进顺序非法

    进程在运行过程中,请求和释放资源的顺序不当,也同样会导致死锁。例如,并发进程 P1、P2分别保持了资源R1、R2,而进程P1申请资源R2,进程P2申请资源R1时,两者都会因为所需资源被占用而阻塞。

  从上面两个例子中,我们可以得出结论,产生死锁可能性的最根本原因是:线程在获得一个锁L1的情况下再去申请另外一个锁L2,也就是锁L1想要包含了锁L2,也就是说在获得了锁L1,并且没有释放锁L1的情况下,又去申请获得锁L2,这个是产生死锁的最根本原因。另一个原因是默认的锁申请操作是阻塞的

Java线程

3、死锁产生的必要条件:

产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生。

(1)互斥:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

(2)不剥夺:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。

(3)请求和保持:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

(4)循环等待:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, ..., pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的资源被P0占有,如图1所示。

(3)产生死锁的实例

看下面一段代码:

public class DeadLock {      public static void main(String[] args) {         Police p = new Police();         CulPrit c = new CulPrit();         new MyThread(c,p).start();         c.say(p);     }      static class MyThread extends Thread {          private CulPrit c;         private Police p;          public MyThread(CulPrit c,Police p) {             this.c = c;             this.p = p;         }          @Override         public void run() {             p.say(c);         }     }       static class Police {          public synchronized void say(CulPrit c) {             System.out.println("警察:你放走人质,我放走你");             c.fun();         }          public synchronized void fun() {             System.out.println("警察:罪犯放走了人质,警察也放走了罪犯");         }      }       static class CulPrit{          public synchronized void say(Police p){             System.out.println("罪犯:你放走我,我放走人质");             p.fun();         }          public synchronized void fun(){             System.out.println("罪犯:警察放走了罪犯,罪犯也放走了人质");         }     }  } 

运行结果:警察和罪犯互相等待对方的回复fun,但是警察的say占用了this锁,罪犯的say也占用了this锁。

Java线程

运行多次后出现了另外一种输出:此时在子线程执行p.say(c)之前主线程抢先执行了p.fun(),程序正常结束。

 Java线程

 该部分参考链接:

https://www.cnblogs.com/xiaoxi/p/8311034.html

十、Java多线程通信问题

 Object类的一些方法:

void notify()

唤醒正在此对象监视器上等待的单个线程。

void notifyAll()

唤醒等待此对象监视器的所有线程。

void wait()

导致当前线程等待它,通常是 通知中断

void wait​(long timeoutMillis)

导致当前线程等待它,通常是 通知中断 ,或者直到经过一定量的实时。

void wait​(long timeoutMillis, int nanos)

导致当前线程等待它,通常是 通知中断 ,或者直到经过一定量的实时。

import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;  public class Demo4  {      /**      * 多线程通信问题, 生产者与消费者问题      * @param args      */     public static void main(String[] args) {         Food f = new Food();         new Cook(f).start();         new Waiter(f).start();     }      //厨师     static class Cook extends Thread{         private Food f;         public Cook(Food f) {             this.f = f;         }          @Override         public void run() {             for(int i=0;i<100;i++){                 if(i%2==0){                     f.setNameAndSaste("老干妈小米粥","香辣味");                 }else{                     f.setNameAndSaste("煎饼果子","甜辣味");                 }             }         }     }     //服务生     static class Waiter extends Thread{         private Food f;         public Waiter(Food f) {             this.f = f;         }         @Override         public void run() {             for(int i=0;i<100;i++){                 try {                     Thread.sleep(100);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }                 f.get();             }         }     }     //食物     static class Food{         private String name;         private String taste;          public synchronized void setNameAndSaste(String name,String taste){             if(flag) {                 this.name = name;                 try {                     Thread.sleep(100);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }                 this.taste = taste;             }         }         public synchronized void get(){             System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);         }     } } 

运行结果:

Java线程

 服务员端走的菜口味与厨师设置的口味不一样。

分析:i%2==0时厨师做完老干妈小米粥,还没等到服务员端走菜,又“回手掏” 开始了做菜,这次i%2==1厨师setName("煎饼果子")然后睡眠100ms这时被服务员线程抢占了锁,服务员把菜端走了,因此菜品的名称是“煎饼果子”,口味却是”香辣味“。

这里厨师是生产者线程,而服务员是消费者线程,生产者生产一个单位的产品还未结束,就被消费者线程抢先运行,导致了产品的数据错乱。

对Food进行修改:

Java线程Java线程Java线程

 运行结果:菜品数据皆正确

Java线程

十一、带返回值的线程Callable

1、Runnable Callable

Java线程

2、Callable使用步骤

Java线程

3、Runnable Callable的相同点

Java线程

4、Runnable Callable的不同点

Java线程

5、Callable获取返回值

        Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

6、FutureTask类

构造器 描述
FutureTask​(Runnable runnable, V result)

创建一个 FutureTask ,在运行时执行给定的 Runnable ,并安排 get在成功完成时返回给定的结果。

FutureTask​(Callable<V> callable)

创建一个 FutureTask ,在运行时将执行给定的 Callable

变量和类型 方法 描述
protected void done()

当此任务转换到状态 isDone (无论是正常还是通过取消),调用受保护的方法。

V get()

如果需要等待计算完成,然后检索其结果。

V get​(long timeout, TimeUnit unit)

如果需要,最多等待计算完成的给定时间,然后检索其结果(如果可用)。

protected boolean runAndReset()

执行计算而不设置其结果,然后将此未来重置为初始状态,如果计算遇到异常或被取消则无法执行此操作。

protected void set​(V v)

将此future的结果设置为给定值,除非已设置或已取消此未来。

protected void setException​(Throwable t)

导致此未来报告带有给定throwable的ExecutionException作为其原因,除非此未来已设置或已取消。

String toString()

返回此FutureTask的字符串表示形式。

 实例:

import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask;  public class DemoCallable {      public static void main(String[] args) throws ExecutionException, InterruptedException {         Callable callable = new MyCallable();         FutureTask<Integer> futureTask = new FutureTask<>(callable);         new Thread(futureTask).start();         Integer j = futureTask.get();//获取返回值         System.out.println(j);         for(int i=0;i<10;i++){             try{                 Thread.sleep(100);             }catch (Exception e){                 e.printStackTrace();             }             System.out.println(i);         }      }      static class MyCallable implements Callable{          @Override         public Integer call() throws Exception {             for(int i=0;i<10;i++){                 try{                     Thread.sleep(100);                 }catch (Exception e){                     e.printStackTrace();                 }                 System.out.println(i);             }             return 100;         }     } }

 运行结果:可以看到在子线程运行结束以后主线程才执行后续的代码

Java线程
运行结果

十二、Java线程池

1、线程池 Executors

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

2、线程池的好处

  • 降低资源消耗。
  • 提高响应速度。
  • 提高线程的可管理性。

Java线程

3、Java中的四种线程池 . ExecutorService

(1) 缓存线程池

/*** 缓存线程池.     * (长度无限制)     * 执行流程:     * 1. 判断线程池是否存在空闲线程     * 2. 存在则使用     * 3. 不存在,则创建线程并放入线程池, 然后使用  */  ExecutorService service = Executors.newCachedThreadPool();    //向线程池中加入新的任务  service.execute(new Runnable() {      @Override      public void run() {          System.out.println("线程的名称:"+Thread.currentThread().getName());      }  });   service.execute(new Runnable() {     @Override      public void run() {          System.out.println("线程的名称:"+Thread.currentThread().getName());      }  });   service.execute(new Runnable() {      @Override      public void run() {          System.out.println("线程的名称:"+Thread.currentThread().getName());      }  });
Java线程

(2) 定长线程池

/** * 定长线程池。 * (长度是指定的数值) * 执行流程: * 1. 判断线程池是否存在空闲线程 * 2. 存在则使用 * 3. 不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用 * 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程 */  Executorservice service = Executors.newFixedThreadPool(2) ;  service.execute(new Runnable() {     @override     pub1ic void run() {         System.out.println("线程的名称:"+Thread.currentThread().getName();     } });  service.execute(new Runnable() {     @override     pub1ic void run() {         System.out.println("线程的名称:"+Thread.currentThread().getName()) ;     } });  

3. 单线程线程池

效果与定长线程池创建时传入数值1效果-致. /**  * 单线程线程池.  * 执行流程: * 1. 判断线程池的那个线程是否空闲 * 2. 空闲则使用 * 3. 不空闲, 则等待池中的单个线程空闲后使用 */ ExecutorService service = Executors.newsingleThreadexecutor();  service.execute(new Runnable(){     @override     public void run() {         System.out.println("线程的名称:"+Thread.currentThread().getName());     } });  service.execute(new Runnable() {     @override     public void run() {         System.out.println("线程的名称:"+Thread.currentThread().getName()) ;     } }); 

4. 周期性任务定长线程池

pub1ic static void main(String[] args) {     /**     * 周期任务定长线程池.     * 执行流程:     * 1. 判断线程池是否存在空闲线程     * 2. 存在则使用     * 3. 不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用     * 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程     * 周期性任务执行时:     * 定时执行,当某个时机触发时,自动执行某任务.     */      SchedledExecutorservice service = Executors.newscheduledThreadPool(2) ;     /**      * 定时执行      * 参数1.    runnable类型的任务      * 参数2.    时长数字      * 参数3.    时长数字的单位      */      /*service.schedule(new Runnable(){          @override          public void run() {              System.out.println("俩人相视一笑~嘿嘿嘿");          },5,TimeUnit.SECONDS);     */      /**      *周期执行      *参数1.   runnable类型的任务      *参数2.   时长数字(延迟执行的时长)      *参数3.   周期时长(每次执行的间隔时间)      *参数4.   时长数字的单位      */     service.scheduleAtFixedRate(new Runnable(){         @override         public void run(){             System.out.println("俩人相视一笑~嘿嘿嘿") ;         },5,2,TimeUnit.SECONDS); } 

十三、Lambda表达式

使用方法: 

Java线程

Java线程

面向对象的一般代码: 

Java线程

 使用Lambda表达式:

Java线程

Java线程

版权声明:玥玥 发表于 2021-07-25 15:32:49。
转载请注明:Java线程 | 女黑客导航