彻底搞清楚Java并发 (一) 基础

长期以来,多线程问题颇为受到面试官的青睐。虽然我个人认为我们当中很少有人能真正获得机会开发复杂的多线程应用(在过去的七年中,我得到了一个机会),但是理解多线程对增加你的信心很有用。之前,我讨论了一个wait()和sleep()方法区别的问题,这一次,我将会讨论join()和yield()方法的区别。坦白的说,实际上我并没有用过其中任何一个方法,所以,如果你感觉有不恰当的地方,请提出讨论。

多线程编程是为了让程序运行得更快,但是不是说,线程创建地越多越好,线程切换的时候上下文切换,以及受限于硬件和软件资源的限制问题

Java入门——(4)多线程,java入门多线程

  关键词:线程、Thread、Runnable、sleep()、yield()、join()、同步  
一、线程的概述

   
 在一个操作系统中,每个独立执行的程序都可以称为一个进程,也就是“正在运行的程
序”。而在进程中还可以有多个执行单元同时执行,这些执行单元可以看作程序执行的一条
条线索,被称为线程。Java
运行环境是一个包含了不同的类和程序的单一进程。线程可以被
称为轻量级进程。线程需要较少的资源来创建和驻留在进程中,并且可以共享进程中的资源。

二、线程的创建 

1、Java提供了两个多线程实现方式: ①一种继承java.lang
包下的Thread类,复写了Thread类的run( )方法,在run(
)方法中实现运行在线程上的代码,提供一个start()方法用于启动新线程。
创建对象示例如:MyThread myTread = new MyTread();

 1 public class Example02 {
 2     public static void main(String[] args) {
 3         MyThread myThread = new MyThread();//创建线程MyThread的线程对象
 4         myThread.start();//开启线程
 5         while (true){       //通过死循环语句打印输出
 6             System.out.println("main()方法在运行");
 7         }
 8     }
 9 }
10 class MyThread extends Thread{
11     public void run(){
12         while (true){  
13             System.out.println("MyThread 类的run()方法");
14         }
15     }
16 }

②一种实现java.lang.Runnable接口,同样在run(
)方法中实现运行在线程上的代码,提供一个start()方法用于启动新线程。
创建对象示例如:MyThread myTread = new MyTread(); Thread thread = new
Thread(myTread);

 1 public class Example03 {
 2     public static void main(String[] args) {
 3         MyThread myThread = new MyThread();//创建线程MyThread的线程对象
 4         Thread thread = new Thread(myThread);//创建线程对象
 5         thread.start();//开启线程,执行线程中的run()方法
 6         while (true){       //通过死循环语句打印输出
 7             System.out.println("main()方法在运行");
 8         }
 9     }
10 }
11 class MyThread implements Runnable{
12     public void run(){  //线程的代码段,当调用start()方法时,线程从此处开始
13         while (true){
14             System.out.println("MyThread 类的run()方法");
15         }
16     }
17 }

  2、实现Runnable接口相对于继承Thread类来说有如下好处:
①适合多个相同的程序代码的线程去处理同一个资源的情况,把线程同程序代码、数据有效的分离;
②可以避免由于java的单继承带来的局限性(一个类一旦继承了某个父类就无法再继承Thread类)。
 
3、所谓的后台线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。因此当所有的非后台线程结束时,程序也就终止了,同时会杀死所有后台线程。反过来说,只要有任何非后台线程(用户线程)还在运行,程序就不会终止。后台线程在不执行finally子句的情况下就会终止其run方法。后台线程创建的子线程也是后台线程。
在某个线程对象在启动之前(start()方法之前)调用setDaemon(true)语句,这个线程就变成一个后台线程。
  三、线程的生命周期及状态转换      
 图片 1

 

1、新建状态(new) 此时不能运行,仅仅由Java虚拟机为其分配内存。
2、就绪状态(Runnable)
当线程对象调用start()方法后,该线程就进入就绪状态(也称可运行状态),位于运行池中,此时已具备运行条件,能否获得CPU的使用权开始运行,还需要等待系统的调度。
3、运行状态(Running)
就绪线程获得CPU使用权,开始执行run()方法中的线程执行体,则该线程处于运行状态。只有处于就绪状态的线程才能转换到运行状态。
4、阻塞状态(Blocked) 由阻塞状态状态转换成就绪状态:
①当线程试图获取某个对象的同步锁时,如果该锁被其他线程所持有,则当前线程会进入阻塞状态,如果想从阻塞状态进入就绪状态必须得获取到其他线程所持有的锁。
②当线程调用了一个阻塞式IO方法时,该线程就会进入阻塞状态,如果想进入就绪状态就必须要等到这个阻塞的IO方法返回。
③当线程调用了某个对象的wait()方法时,也会使线程进入阻塞状态,如果想进入就绪状态就需要使用notify()方法唤醒该线程。
④当线程调用了Thread的sleep(long
mills)方法时,也会使线程进入阻塞状态,在这种情况下,只需等到线程睡眠的时间到以后,线程就会自动进入就绪状态。
⑤当一个线程中调用了另一个线程的join()方法时,会使当前线程进入阻塞状态,在这种情况下,需要等到新加入的线程运行结束后才会结束阻塞状态,加入就绪状态。
注意:线程从阻塞状态只能进入就绪状态而不能直接进入运行状态。
5、死亡状态(Terminated)
线程run()执行完或线程抛出未捕获异常、错误,线程就会进入死亡状态。一旦进入死亡状态,线程将不再拥有运行的资格,也不能转换到其他状态。
  四、线程的调度 1、线程的优先级 每一个 Java
线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。 Java
线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) – 10
(Thread.MAX_PRIORITY )。如:thread.setPriority(10);
默认情况下,每一个线程都会分配一个普通优先级 NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
  2、线程休眠 Thread.sleep(long
millis)方法声明抛出InerruptedException异常,因此在调用此方法时应该捕获异常,或者声明抛出该异常。该方法静态方法,只能控制当前线程休眠。
 

 1 public class Example08 {
 2     public static void main(String[] args) throws Exception{
 3 //        创建一个线程
 4         new Thread(new SleepThread()).start();
 5         for (int i= 1;i<=10;i++){
 6             if (i==5){
 7                 Thread.sleep(2000);//当前线程休眠2秒
 8             }
 9             System.out.println("主线程正在输出:"+i);
10             Thread.sleep(500);//当前线程休眠500毫秒
11         }
12     }
13 }
14 class SleepThread implements Runnable{
15     @Override
16     public void run() {
17         for (int i= 1;i<=10;i++){
18             if (i==3){
19                 try{
20                     Thread.sleep(2000);
21                 }catch (InterruptedException e ){
22                     e.printStackTrace();
23                 }
24             }
25             System.out.println("线程一正在输出:"+i);
26             try{
27                 Thread.sleep(500);
28             } catch (Exception e ){
29                 e.printStackTrace();
30             }
31         }
32    

3、线程让步
线程让步可以通过yield()方法实现,该方法与sleep()方法相似,都是可以让当前正在运行的线程暂停,区别在于yield()方法不会阻塞该线程,它只是将该线程转换成就绪状态,让系统的调度器重新调度一次。当某个线程调用yield()方法之后,只有与当前线程优先级相同或者更高的线程才能获得执行的机会。

 1 //定义YieldThread类继承Thread类
 2 class YieldThread extends Thread{
 3 //    定义一个有参构造方法
 4     public YieldThread(String name){
 5         super(name);//调用父类的构造方法
 6     }
 7 
 8     @Override
 9     public void run() {
10         for (int i= 0;i<5;i++){
11             System.out.println(Thread.currentThread().getName()+"___"+i);
12             if (i==3){
13                 System.out.print("线程让步");
14                 Thread.yield();//线程运行至此,做出让步
15             }
16         }
17     }
18 }
19 public class Example09 {
20     public static void main(String[] args) {
21         //创建两个线程
22         Thread t1= new YieldThread("线程A");
23         Thread t2= new YieldThread("线程B");
24     //开启两个线程
25         t1.start();
26         t2.start();
27     }
28 
29 }

  4、线程插队
当某个线程中调用其他线程的join()方法时,调用的线程将被阻塞,直到被join()方法加入的线程执行完成后它才会继续运行。

 1 public class Example10 {
 2     public static void main(String[] args) throws Exception{
 3 //        创建线程
 4         Thread t = new Thread(new EemergencyThread(),"线程一");
 5         t.start();
 6         for (int i=1;i<6;i++){
 7             System.out.println(Thread.currentThread().getName()+"输入:"+i);
 8             if (i==2){
 9                 t.join();  //调用join()方法
10             }
11             Thread.sleep(500);
12         }
13     }
14 }
15 class EemergencyThread implements Runnable{
16     @Override
17     public void run() {
18         for (int i =1;i<6;i++){
19             System.out.println(Thread.currentThread().getName()+"输入:"+i);
20             try{
21                 Thread.sleep(500);
22             }catch (InterruptedException e){
23                 e.printStackTrace();
24             }
25         }
26     }
27 }

五、多线程同步
1、同步代码块:lock是一个锁的对象,它是同步代码块的关键,它可以是任意类型的对象,但多个线程共享的锁对象必须是唯一的。“任意”说的是共享锁对象的类型。
  synchronized(lock){ 操作共享资源代码块 } 
2、同步方法:

synchronized 返回值类型 方法名([参数1,......]){ }

3、同步代码块和同步方法解决多线程同时访问共享数据时的线程安全问题,只要加上同一个锁,在同一时间内只能有一条线程执行,但是线程在执行同步代码时每次都会判断锁的状态,非常消耗资源,效率较低。
      示例:

 1 //定义Ticket1类实现Runnable接口
 2 class Ticket1 implements Runnable{
 3     private int tickets=10;   //定义变量tickets,并赋值10
 4 /*   同步代码块用法
 5      Object lock = new Object();//定义任意一个对象,用做同步代码块的锁
 6      public void run(){
 7         while (true){
 8            synchronized(lock){
 9                try {
10                     Thread.sleep(10);
11                 } catch (InterruptedException e) {
12                     e.printStackTrace();
13                 }
14                 if(tickets>0){ 
15                 System.out.println(Thread.currentThread().getName() 
16                      + "---卖出的票" + tickets--);
17                 }else{
18                      break;
19                 }   
20            }
21         }
22 
23     }*/
24     
25     public void run(){
26         while (true){
27             saleTicket();  //调用售票方法
28             if (tickets<=0){
29                 break;
30             }
31         }
32 
33     }
34     //定义一个同步方法saleTicket()
35     private synchronized void saleTicket (){
36         if (tickets>0) {
37             try {
38                 Thread.sleep(10);
39             } catch (InterruptedException e) {
40                 e.printStackTrace();
41             }
42             System.out.println(Thread.currentThread().getName() + 
43                     "---卖出的票" + tickets--);
44         }
45     }
46 }
47 public class Example12 {
48     public static void main(String[] args) {
49         Ticket1 ticket =new Ticket1();
50         new Thread(ticket,"线程1").start();
51         new Thread(ticket,"线程2").start();
52         new Thread(ticket,"线程4").start();
53         new Thread(ticket,"线程3").start();
54     }
55 }

      4、死锁

死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。 

java 死锁产生的四个必要条件:

①互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用

②不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

③请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。

④循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

六、多线程通信      
在Object类中提供了wait(),notify(),notifyAll()方法用于解决线程的通信问题,即让线程按照一定的顺序轮流执行。
      唤醒线程的方法:

方法声明 功能描述
void wait() 使当前线程放弃同步锁并进入等待,直到其他线程进入此同步锁,并调用notify()方法或notifyAll()方法唤醒该线程为止
void notify() 唤醒此同步锁上等待的第一个调用wait()方法的线程
void notifyAll() 唤醒此同步锁上调用wait()方法的所有线程

     
 wait(),notify(),notifyAll()这三个方法的调用者都应该是同步锁对象,如果这三个方法的调用者不是同步锁对象,Java虚拟机就会抛出IllegalMonitorStateException
。          示例存取数据:

 1 class Storage{
 2     private int[] cells =new int[10];//    数组存储数组
 3     private int inPos,outPos;//    inPos表示存入时数组下标,outPos表示取出时数组下标
 4     private int count;//存入或取出数据的数量
 5     public synchronized void put(int num){    //定义一个put()方法向数组中存入数据
 6         try {
 7             //如果放入数据等于cells的长度,此线程等待
 8             while (count == cells.length) {
 9                 this.wait();
10             }
11             cells[inPos] = num;//向数组中放入数据
12             System.out.println("cells[" + inPos + "]中放入数据---" + cells[inPos]);
13             inPos++;//存完元素让位置加1
14             if (inPos == cells.length)
15                 inPos = 0;//当inPos为数组长度是10,将其置为0
16             count++;
17             this.notify();
18         }catch (Exception e){
19             e.printStackTrace();
20         }
21     }
22     //定义一个get()方法从数组中取出数据
23     public synchronized void get(){
24         try{
25             while (count==0){
26                 this.wait();
27             }
28             int data=cells[outPos];
29             System.out.println("从cells["+outPos+"]中取出数据"+data);
30             cells[outPos]=0;//取出后,当前数据置零
31             outPos++;  //取完元素后让位置加1
32             if (outPos==cells.length)
33                 outPos=0;
34             count--;//每取出一个元素count减1
35             this.notify();
36         }catch (Exception e){
37             e.printStackTrace();
38         }
39     }
40 }
41 class Input implements Runnable{   //输入线程类
42     private Storage st;
43     private int num; //定义一个变量num
44     Input(Storage st){    //通过构造方法接收一个Storage对象
45         this.st=st;
46     }
47     public void run(){
48         while (true){
49             st.put(num++);//将num存入数组,每次存入后num自增
50         }
51     }
52 }
53 class Output implements Runnable{  //输出线程类
54     private Storage st;
55     Output(Storage st){    //通过构造方法接收一个Storage对象
56         this.st=st;
57     }
58     public void run(){
59         while (true){
60             st.get();//循环取出元素
61         }
62     }
63 }
64 public class Example17 {
65     public static void main(String[] args) {
66         Storage st=new Storage();//创建数组存储类对象
67         Input input=new Input(st);//创建Input对象传入Storage对象
68         Output output=new Output(st);//创建Output对象传入Storage对象
69         new Thread(input).start();//开启新线程
70         new Thread(output).start();//开启新线程
71 
72     }
73 }

运行结果

cells[1]中放入数据---214311
cells[2]中放入数据---214312
cells[3]中放入数据---214313
cells[4]中放入数据---214314
cells[5]中放入数据---214315
cells[6]中放入数据---214316
cells[7]中放入数据---214317
cells[8]中放入数据---214318
cells[9]中放入数据---214319
cells[0]中放入数据---214320
从cells[1]中取出数据214311
从cells[2]中取出数据214312
从cells[3]中取出数据214313
从cells[4]中取出数据214314
从cells[5]中取出数据214315
从cells[6]中取出数据214316
从cells[7]中取出数据214317
从cells[8]中取出数据214318
从cells[9]中取出数据214319
从cells[0]中取出数据214320

多线程累加例子:

模拟10个线程,第一个线程从1加到10,第二个线程从11加到20…..第十个线程从91加到100,最后在把10个线程结果相加。

 1 public class Accumulator extends Thread {
 2     private int stratNum;
 3     public static int sum;
 4     public Accumulator(int startNum) {
 5         this.stratNum = startNum;
 6     }
 7     public static synchronized void add(int num) {
 8         sum += num;
 9     }
10     public void run() {
11         int sum = 0;
12         for (int i = 0; i < 10; i++) {
13             sum += stratNum + i;
14         }
15          add(sum); 
16     }
17     public static void main(String[] args) throws Exception {
18         Thread[] threadList = new Thread[10];
19         for (int i = 0; i < 10; i++) {
20             threadList[i] = new Accumulator(10 * i + 1);
21             threadList[i].start();
22         }
23         for (int i = 0; i < 10; i++) {
24             threadList[i].join();
25         }
26                 System.out.println("Sum is : " + sum);
27     }
28 }

 

关键词:线程、Thread、Runnable、sleep()、yield()、join()、同步一、线程的概述
在一个操作系统中,每…

Java线程调度的一点背景

在各种各样的线程中,Java虚拟机必须实现一个有优先权的、基于优先级的调度程序。这意味着Java程序中的每一个线程被分配到一定的优先权,使用定义好的范围内的一个正整数表示。优先级可以被开发者改变。即使线程已经运行了一定时间,Java虚拟机也不会改变其优先级

优先级的值很重要,因为Java虚拟机和下层的操作系统之间的约定是操作系统必须选择有最高优先权的Java线程运行。所以我们说Java实现了一个基于优先权的调度程序。该调度程序使用一种有优先权的方式实现,这意味着当一个有更高优先权的线程到来时,无论低优先级的线程是否在运行,都会中断(抢占)它。这个约定对于操作系统来说并不总是这样,这意味着操作系统有时可能会选择运行一个更低优先级的线程。(我憎恨多线程的这一点,因为这不能保证任何事情)

style=”color: #888888;”>注意Java并不限定线程是以时间片运行,但是大多数操作系统却有这样的要求。在术语中经常引起混淆:抢占经常与时间片混淆。事实上,抢占意味着只有拥有高优先级的线程可以优先于低优先级的线程执行,但是当线程拥有相同优先级的时候,他们不能相互抢占。它们通常受时间片管制,但这并不是Java的要求。

上下文切换

单核CPU同样支持多线程编程,CPU通过给每个线程分配CPU时间片来实现这个机制,时间片是CPU分配给各个线程的时间,这个时间片非常短,所以就不得不通过切换线程来执行(时间片一般是几十毫秒)

当前任务执行一个时间片后,会切换到下一个任务,但是,在切换前会保存上一个任务的状态,这样的话下次这条线程获取到时间片之后就可以恢复这个任务的状态

理解线程的优先权

接下来,理解线程优先级是多线程学习很重要的一步,尤其是了解yield()函数的工作过程。

  1. 记住当线程的优先级没有指定时,所有线程都携带普通优先级。
  2. 优先级可以用从1到10的范围指定。10表示最高优先级,1表示最低优先级,5是普通优先级。
  3. 记住优先级最高的线程在执行时被给予优先。但是不能保证线程在启动时就进入运行状态。
  4. 与在线程池中等待运行机会的线程相比,当前正在运行的线程可能总是拥有更高的优先级。
  5. 由调度程序决定哪一个线程被执行。
  6. t.setPriority()用来设定线程的优先级。
  7. 记住在线程开始方法被调用之前,线程的优先级应该被设定。
  8. 你可以使用常量,如MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY来设定优先级

现在,当我们对线程调度和线程优先级有一定理解后,让我们进入主题。

协程

协程说通俗一点就是由线程调度的线程,操作系统创建一个进程,进程再创建若干个线程并行线程的切换由操作系统负责调度,Java语言等线程其实与操作系统线程是1:1的关系,每个线程都有自己的Stack,Java在64位操作系统默默人stack大小为1024kb,所以一个进程也是不能够开启上万个线程的

基于J2EE项目都是基于每个请求占用一个线程去完成完整的业务逻辑,。所以系统的吞吐能力取决于每个线程的操作耗时。如果遇到很耗时的I/O行为,则整个系统的吞吐立刻下降,比如JDBC是同步阻塞的,这也是为什么很多人都说数据库是瓶颈的原因。这里的耗时其实是让CPU一直在等待I/O返回,说白了线程根本没有利用CPU去做运算,而是处于空转状态。

Java的JDK里有封装很好的ThreadPool,可以用来管理大量的线程生命周期,但是本质上还是不能很好的解决线程数量的问题,以及线程空转占用CPU资源的问题。

先阶段行业里的比较流行的解决方案之一就是单线程加上异步回调。其代表派是node.js以及Java里的新秀Vert.x。他们的核心思想是一样的,遇到需要进行I/O操作的地方,就直接让出CPU资源,然后注册一个回调函数,其他逻辑则继续往下走,I/O结束后带着结果向事件队列里插入执行结果,然后由事件调度器调度回调函数,传入结果

协程的本质上其实还是和上面的方法一样,只不过他的核心点在于由他进行调度,遇到阻塞操作,立刻yield掉,并且记录当前栈上的数据,阻塞完后立刻再找一个线程恢复栈并把阻塞的结果放到这个线程上去跑,这样看上去好像跟写同步代码没有任何差别,这整个流程可以称为coroutine,而跑在由coroutine负责调度的线程称为Fiber。比如Golang里的go关键字其实就是负责开启一个Fiber,让func逻辑跑在上面。而这一切都是发生的用户态上,没有发生在内核态上,也就是说没有切换上下文的开销。

yield()方法

理论上,yield意味着放手,放弃,投降。一个调用yield()方法的线程告诉虚拟机它乐意让其他线程占用自己的位置。这表明该线程没有在做一些紧急的事情。注意,这仅是一个暗示,并不能保证不会产生任何影响。

在Thread.java中yield()定义如下:

/**
  * A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore
  * this hint. Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilize a CPU.
  * Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect.
  */

public static native void yield();

让我们列举一下关于以上定义重要的几点:

  • Yield是一个静态的原生(native)方法
  • Yield告诉当前正在执行的线程把运行机会交给线程池中拥有相同优先级的线程。
  • Yield不能保证使得当前正在运行的线程迅速转换到可运行的状态
  • 它仅能使一个线程从运行状态转到可运行状态,而不是等待或阻塞状态

Java线程调度

JVM必须维护一个有优先权,基于优先级的调度模式,优先级的值很重要,因为Java虚拟机和下层的操作系统之间的约定是操作系统必须选择有最高优先权的Java线程运行,所以我们说Java实现了一个基于优先权的调度程序该调度程序使用一种有优先权的方式实现,这意味着当一个有更高优先权的线程到来时,无论低优先级的线程是否在运行,都会中断它(JVM会这么做)。这个约定对于操作系统来说并不总是这样,这意味着操作系统有时可能会选择运行一个更低优先级的线程。

yield()方法使用示例

在下面的示例程序中,我随意的创建了名为生产者和消费者的两个线程。生产者设定为最小优先级,消费者设定为最高优先级。在Thread.yield()注释和非注释的情况下我将分别运行该程序。没有调用yield()方法时,虽然输出有时改变,但是通常消费者行先打印出来,然后事生产者。

调用yield()方法时,两个线程依次打印,然后将执行机会交给对方,一直这样进行下去。

package test.core.threads;

public class YieldExample
{
   public static void main(String[] args)
   {
      Thread producer = new Producer();
      Thread consumer = new Consumer();

      producer.setPriority(Thread.MIN_PRIORITY); //Min Priority
      consumer.setPriority(Thread.MAX_PRIORITY); //Max Priority

      producer.start();
      consumer.start();
   }
}

class Producer extends Thread
{
   public void run()
   {
      for (int i = 0; i < 5; i++)
      {
         System.out.println("I am Producer : Produced Item " + i);
         Thread.yield();
      }
   }
}

class Consumer extends Thread
{
   public void run()
   {
      for (int i = 0; i < 5; i++)
      {
         System.out.println("I am Consumer : Consumed Item " + i);
         Thread.yield();
      }
   }
}

yield()方法

理论上,yield意味着放手,放弃,投降。一个调用yield()方法的线程告诉虚拟机它乐意让其他线程占用自己的位置。这表明该线程没有在做一些紧急的事情。注意,这仅是一个暗示,并不能保证不会产生任何影响。

/**  * A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore  * this hint. Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilize a CPU.  * Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect.  */ public static native void yield();
  • Yield是一个静态的本地方法
  • Yield告诉当前正在执行的线程把运行机会交给线程池中拥有相同优先级的线程。
  • Yield不能保证使得当前正在运行的线程迅速转换到可运行的状态
  • 它仅能使一个线程从运行状态转到可运行状态,而不是等待或阻塞状态

上述程序在没有调用yield()方法情况下的输出:

I am Consumer : Consumed Item 0
 I am Consumer : Consumed Item 1
 I am Consumer : Consumed Item 2
 I am Consumer : Consumed Item 3
 I am Consumer : Consumed Item 4
 I am Producer : Produced Item 0
 I am Producer : Produced Item 1
 I am Producer : Produced Item 2
 I am Producer : Produced Item 3
 I am Producer : Produced Item 4

join()方法

如果一个线程A执行了thread.join()方法目的是,当前线程A等待thread线程终止之后才从thread.join()返回,

/**     * Waits for this thread to die.     *     * <p> An invocation of this method behaves in exactly the same     * way as the invocation     *     * <blockquote>     * {@linkplain #join join}{@code }     * </blockquote>     *     * @throws  InterruptedException     *          if any thread has interrupted the current thread. The     *          <i>interrupted status</i> of the current thread is     *          cleared when this exception is thrown.     */    public final void join() throws InterruptedException {        join;    }/**     * Waits at most {@code millis} milliseconds for this thread to     * die. A timeout of {@code 0} means to wait forever.     *     * <p> This implementation uses a loop of {@code this.wait} calls     * conditioned on {@code this.isAlive}. As a thread terminates the     * {@code this.notifyAll} method is invoked. It is recommended that     * applications not use {@code wait}, {@code notify}, or     * {@code notifyAll} on {@code Thread} instances.     *     * @param  millis     *         the time to wait in milliseconds     *     * @throws  IllegalArgumentException     *          if the value of {@code millis} is negative     *     * @throws  InterruptedException     *          if any thread has interrupted the current thread. The     *          <i>interrupted status</i> of the current thread is     *          cleared when this exception is thrown.     */    public final synchronized void join(long millis)    throws InterruptedException {        long base = System.currentTimeMillis();        long now = 0;        if (millis < 0) {            throw new IllegalArgumentException("timeout value is negative");        }        if (millis == 0) {            while ) {                wait;            }        } else {            while ) {                long delay = millis - now;                if (delay <= 0) {                    break;                }                wait;                now = System.currentTimeMillis() - base;            }        }    }

上述程序在调用yield()方法情况下的输出:

I am Producer : Produced Item 0
 I am Consumer : Consumed Item 0
 I am Producer : Produced Item 1
 I am Consumer : Consumed Item 1
 I am Producer : Produced Item 2
 I am Consumer : Consumed Item 2
 I am Producer : Produced Item 3
 I am Consumer : Consumed Item 3
 I am Producer : Produced Item 4
 I am Consumer : Consumed Item 4

如何减少上下文切换

  • 无锁并发编程,多线程竞争锁的时候,会引起上下文切换,如将数据的ID按照Hash算法取模分段,不同线程处理不同段的数据
  • CAS算法,Java的Atomic包下使用的同步类都是使用CAS和Volatile实现的无锁
  • 线程数量的控制
  • 协程:单线程内实现多任务的调度,在单线程中维持多个任务间的切换

join()方法

线程实例的方法join()方法可以使得在另一个线程的执行结束后再开始执行这个线程。如果join()方法被在一个线程实例上调用,当前运行着的线程将阻塞直到线程实例完成了执行。

//Waits for this thread to die.

public final void join() throws InterruptedException

在join()方法内设定超时,使得join()方法的影响在特定超时后无效。当超时时,主方法和任务线程申请运行的时候是平等的。然而,当涉及sleep时,join()方法依靠操作系统计时,所以你不应该假定join()方法将会等待你指定的时间。

像sleep,join通过抛出InterruptedException对中断做出回应。

如何避免死锁

  • 避免一个线程同时获得多个锁
  • 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
  • 尝试使用定时锁,tryLock代替内部锁的机制
  • 对于数据库来说,加锁和解锁必须在同一条连接中,否则将会出现问题

join()方法使用示例

package test.core.threads;

public class JoinExample
{
   public static void main(String[] args) throws InterruptedException
   {
      Thread t = new Thread(new Runnable()
         {
            public void run()
            {
               System.out.println("First task started");
               System.out.println("Sleeping for 2 seconds");
               try
               {
                  Thread.sleep(2000);
               } catch (InterruptedException e)
               {
                  e.printStackTrace();
               }
               System.out.println("First task completed");
            }
         });
      Thread t1 = new Thread(new Runnable()
         {
            public void run()
            {
               System.out.println("Second task completed");
            }
         });
      t.start(); // Line 15
      t.join(); // Line 16
      t1.start();
   }
}

Output:

First task started
Sleeping for 2 seconds
First task completed
Second task completed

这是一些很小却很重要的概念。在评论部分让我知道你的想法。

发表评论

电子邮件地址不会被公开。 必填项已用*标注