java线程 订阅
《Java线程》是由Scott Oaks 编写, 中国电力出版社2003-5 出版的一本书籍。 展开全文
《Java线程》是由Scott Oaks 编写, 中国电力出版社2003-5 出版的一本书籍。
信息
页    数
337
作    者
Scott Oaks
定    价
39.00元
出版时间
2003-5
出版社
中国电力出版社
ISBN
9787508313184
Java线程内容介绍
线程并不是新的概念:许多操作系统
收起全文
精华内容
下载资源
问答
  • Java线程的6种状态及切换(透彻讲解)

    万次阅读 多人点赞 2016-12-24 16:57:03
    2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程...

    Java中线程的状态分为6种。

    1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
    2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
    线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
    3. 阻塞(BLOCKED):表示线程阻塞于锁。
    4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
    5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
    6. 终止(TERMINATED):表示该线程已经执行完毕。

    这6种状态定义在Thread类的State枚举中,可查看源码进行一一对应。

    一、线程的状态图     线程状态图

    二、状态详细说明

    1. 初始状态(NEW)

    实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。

    2.1. 就绪状态(RUNNABLE之READY)

    1. 就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。
    2. 调用线程的start()方法,此线程进入就绪状态。
    3. 当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
    4. 当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。
    5. 锁池里的线程拿到对象锁后,进入就绪状态。

    2.2. 运行中状态(RUNNABLE之RUNNING)

    线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一的一种方式。

    3. 阻塞状态(BLOCKED)

    阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。

    4. 等待(WAITING)

    处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。

    5. 超时等待(TIMED_WAITING)

    处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。

    6. 终止状态(TERMINATED)

    1. 当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。
    2. 在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

    三、等待队列

    • 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内。
    • 与等待队列相关的步骤和图

    1. 线程1获取对象A的锁,正在使用对象A。
    2. 线程1调用对象A的wait()方法。
    3. 线程1释放对象A的锁,并马上进入等待队列。
    4. 锁池里面的对象争抢对象A的锁。
    5. 线程5获得对象A的锁,进入synchronized块,使用对象A。
    6. 线程5调用对象A的notifyAll()方法,唤醒所有线程,所有线程进入同步队列。若线程5调用对象A的notify()方法,则唤醒一个线程,不知道会唤醒谁,被唤醒的那个线程进入同步队列。
    7. notifyAll()方法所在synchronized结束,线程5释放对象A的锁。
    8. 同步队列的线程争抢对象锁,但线程1什么时候能抢到就不知道了。 

    四、同步队列状态

    • 当前线程想调用对象A的同步方法时,发现对象A的锁被别的线程占有,此时当前线程进入同步队列。简言之,同步队列里面放的都是想争夺对象锁的线程。
    • 当一个线程1被另外一个线程2唤醒时,1线程进入同步队列,去争夺对象锁。
    • 同步队列是在同步的环境下才有的概念,一个对象对应一个同步队列
    • 线程等待时间到了或被notify/notifyAll唤醒后,会进入同步队列竞争锁,如果获得锁,进入RUNNABLE状态,否则进入BLOCKED状态等待获取锁。

    五、几个方法的比较

    1. Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
    2. Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。
    3. thread.join()/thread.join(long millis),当前线程里调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程一般情况下进入RUNNABLE状态,也有可能进入BLOCKED状态(因为join是基于wait实现的)。
    4. obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。
    5. obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
    6. LockSupport.park()/LockSupport.parkNanos(long nanos),LockSupport.parkUntil(long deadlines), 当前线程进入WAITING/TIMED_WAITING状态。对比wait方法,不需要获得锁就可以让线程进入WAITING/TIMED_WAITING状态,需要通过LockSupport.unpark(Thread thread)唤醒。

    六、疑问

    1. 等待队列里许许多多的线程都wait()在一个对象上,此时某一线程调用了对象的notify()方法,那唤醒的到底是哪个线程?随机?队列FIFO?or sth else?Java文档就简单的写了句:选择是任意性的(The choice is arbitrary and occurs at the discretion of the implementation)。
    展开全文
  • Java线程(七):Callable和Future

    万次阅读 多人点赞 2016-02-05 15:12:04
    Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,...

           接着上一篇继续并发包的学习,本篇说明的是Callable和Future,它俩很有意思的,一个产生结果,一个拿到结果。
           Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值,下面来看一个简单的例子:

    public class CallableAndFuture {
    	public static void main(String[] args) {
    		Callable<Integer> callable = new Callable<Integer>() {
    			public Integer call() throws Exception {
    				return new Random().nextInt(100);
    			}
    		};
    		FutureTask<Integer> future = new FutureTask<Integer>(callable);
    		new Thread(future).start();
    		try {
    			Thread.sleep(5000);// 可能做一些事情
    			System.out.println(future.get());
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		} catch (ExecutionException e) {
    			e.printStackTrace();
    		}
    	}
    }
    

           FutureTask实现了两个接口,Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值,那么这个组合的使用有什么好处呢?假设有一个很耗时的返回值需要计算,并且这个返回值不是立刻需要的话,那么就可以使用这个组合,用另一个线程去计算返回值,而当前线程在使用这个返回值之前可以做其它的操作,等到需要这个返回值时,再通过Future得到,岂不美哉!这里有一个Future模式的介绍:http://openhome.cc/Gossip/DesignPattern/FuturePattern.htm。
           下面来看另一种方式使用Callable和Future,通过ExecutorService的submit方法执行Callable,并返回Future,代码如下:

    public class CallableAndFuture {
    	public static void main(String[] args) {
    		ExecutorService threadPool = Executors.newSingleThreadExecutor();
    		Future<Integer> future = threadPool.submit(new Callable<Integer>() {
    			public Integer call() throws Exception {
    				return new Random().nextInt(100);
    			}
    		});
    		try {
    			Thread.sleep(5000);// 可能做一些事情
    			System.out.println(future.get());
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		} catch (ExecutionException e) {
    			e.printStackTrace();
    		}
    	}
    }
    

           代码是不是简化了很多,ExecutorService继承自Executor,它的目的是为我们管理Thread对象,从而简化并发编程,Executor使我们无需显示的去管理线程的生命周期,是JDK 5之后启动任务的首选方式。
           执行多个带返回值的任务,并取得多个返回值,代码如下:

    public class CallableAndFuture {
    	public static void main(String[] args) {
    		ExecutorService threadPool = Executors.newCachedThreadPool();
    		CompletionService<Integer> cs = new ExecutorCompletionService<Integer>(threadPool);
    		for(int i = 1; i < 5; i++) {
    			final int taskID = i;
    			cs.submit(new Callable<Integer>() {
    				public Integer call() throws Exception {
    					return taskID;
    				}
    			});
    		}
    		// 可能做一些事情
    		for(int i = 1; i < 5; i++) {
    			try {
    				System.out.println(cs.take().get());
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			} catch (ExecutionException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    } 
    

           其实也可以不使用CompletionService,可以先创建一个装Future类型的集合,用Executor提交的任务返回值添加到集合中,最后遍历集合取出数据,代码略。更新于2016-02-05,评论中就这个说法引发了讨论,其实是我没有讲清楚,抱歉。这里再阐述一下:提交到CompletionService中的Future是按照完成的顺序排列的,这种做法中Future是按照添加的顺序排列的。所以这两种方式的区别就像评论中fishjam**所描述的那样。

           本文来自:高爽|Coder,原文地址:http://blog.csdn.net/ghsau/article/details/7451464,转载请注明。

    展开全文
  • Java线程(二):线程同步synchronized和volatile

    万次阅读 多人点赞 2012-04-04 10:49:28
    要说明线程同步问题首先要说明Java线程的两个特性,可见性和有序性。多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现。拿上篇博文中的例子来说明,在多个线程之间共享了Count类的一个...

            要说明线程同步问题首先要说明Java线程的两个特性,可见性和有序性。多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现。拿上篇博文中的例子来说明,在多个线程之间共享了Count类的一个对象,这个对象是被创建在主内存(堆内存)中,每个线程都有自己的工作内存(线程栈),工作内存存储了主内存Count对象的一个副本,当线程操作Count对象时,首先从主内存复制Count对象到工作内存中,然后执行代码count.increment(),改变了num值,最后用工作内存Count刷新主内存Count。当一个对象在多个内存中都存在副本时,如果一个内存修改了共享变量,其它线程也应该能够看到被修改后的值,此为可见性。多个线程执行时,CPU对线程的调度是随机的,我们不知道当前程序被执行到哪步就切换到了下一个线程,一个最经典的例子就是银行汇款问题,一个银行账户存款100,这时一个人从该账户取10元,同时另一个人向该账户汇10元,那么余额应该还是100。那么此时可能发生这种情况,A线程负责取款,B线程负责汇款,A从主内存读到100,B从主内存读到100,A执行减10操作,并将数据刷新到主内存,这时主内存数据100-10=90,而B内存执行加10操作,并将数据刷新到主内存,最后主内存数据100+10=110,显然这是一个严重的问题,我们要保证A线程和B线程有序执行,先取款后汇款或者先汇款后取款,此为有序性。本文讲述了JDK5.0之前传统线程的同步方式,更高级的同步方式可参见Java线程(八):锁对象Lock-同步问题更完美的处理方式

            下面同样用代码来展示一下线程同步问题。

            TraditionalThreadSynchronized.java:创建两个线程,执行同一个对象的输出方法。

    public class TraditionalThreadSynchronized {
    	public static void main(String[] args) {
    		final Outputter output = new Outputter();
    		new Thread() {
    			public void run() {
    				output.output("zhangsan");
    			}
    		}.start();		
    		new Thread() {
    			public void run() {
    				output.output("lisi");
    			}
    		}.start();
    	}
    }
    class Outputter {
    	public void output(String name) {
    		// TODO 为了保证对name的输出不是一个原子操作,这里逐个输出name的每个字符
    		for(int i = 0; i < name.length(); i++) {
    			System.out.print(name.charAt(i));
    			// Thread.sleep(10);
    		}
    	}
    }

            运行结果:

    zhlainsigsan

            显然输出的字符串被打乱了,我们期望的输出结果是zhangsanlisi,这就是线程同步问题,我们希望output方法被一个线程完整的执行完之后再切换到下一个线程,Java中使用synchronized保证一段代码在多线程执行时是互斥的,有两种用法:

            1. 使用synchronized将需要互斥的代码包含起来,并上一把锁。

    {
        synchronized (this) {
            for(int i = 0; i < name.length(); i++) {
                System.out.print(name.charAt(i));
            }
        }
    }
    

            这把锁必须是需要互斥的多个线程间的共享对象,像下面的代码是没有意义的。

    {
        Object lock = new Object();
        synchronized (lock) {
            for(int i = 0; i < name.length(); i++) {
                System.out.print(name.charAt(i));
            }
        }
    }
    

            每次进入output方法都会创建一个新的lock,这个锁显然每个线程都会创建,没有意义。

            2. 将synchronized加在需要互斥的方法上。

    public synchronized void output(String name) {
        // TODO 线程输出方法
        for(int i = 0; i < name.length(); i++) {
            System.out.print(name.charAt(i));
        }
    }

            这种方式就相当于用this锁住整个方法内的代码块,如果用synchronized加在静态方法上,就相当于用××××.class锁住整个方法内的代码块。使用synchronized在某些情况下会造成死锁,死锁问题以后会说明。使用synchronized修饰的方法或者代码块可以看成是一个原子操作

            每个锁对象(JLS中叫monitor)都有两个队列,一个是就绪队列,一个是阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程,当一个线程被唤醒(notify)后,才会进入到就绪队列,等待CPU的调度,反之,当一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒,这个涉及到线程间的通信,下一篇博文会说明。看我们的例子,当第一个线程执行输出方法时,获得同步锁,执行输出方法,恰好此时第二个线程也要执行输出方法,但发现同步锁没有被释放,第二个线程就会进入就绪队列,等待锁被释放。一个线程执行互斥代码过程如下:

            1. 获得同步锁;

            2. 清空工作内存;

            3. 从主内存拷贝对象副本到工作内存;

            4. 执行代码(计算或者输出等);

            5. 刷新主内存数据;

            6. 释放同步锁。

            所以,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。

            volatile是第二种Java多线程同步的机制,根据JLS(Java LanguageSpecifications)的说法,一个变量可以被volatile修饰,在这种情况下内存模型(主内存和线程工作内存)确保所有线程可以看到一致的变量值,来看一段代码:

    class Test {
    	static int i = 0, j = 0;
    	static void one() {
    		i++;
    		j++;
    	}
    	static void two() {
    		System.out.println("i=" + i + " j=" + j);
    	}
    }

            一些线程执行one方法,另一些线程执行two方法,two方法有可能打印出j比i大的值,按照之前分析的线程执行过程分析一下:

            1. 将变量i从主内存拷贝到工作内存;

            2. 改变i的值;

            3. 刷新主内存数据;

            4. 将变量j从主内存拷贝到工作内存;

            5. 改变j的值;

            6. 刷新主内存数据;

            这个时候执行two方法的线程先读取了主存i原来的值又读取了j改变后的值,这就导致了程序的输出不是我们预期的结果,要阻止这种不合理的行为的一种方式是在one方法和two方法前面加上synchronized修饰符:

    class Test {
    	static int i = 0, j = 0;
    	static synchronized void one() {
    		i++;
    		j++;
    	}
    	static synchronized void two() {
    		System.out.println("i=" + i + " j=" + j);
    	}
    }

           根据前面的分析,我们可以知道,这时one方法和two方法再也不会并发的执行了,i和j的值在主内存中会一直保持一致,并且two方法输出的也是一致的。另一种同步的机制是在共享变量之前加上volatile:

    class Test {
    	static volatile int i = 0, j = 0;
    	static void one() {
    		i++;
    		j++;
    	}
    	static void two() {
    		System.out.println("i=" + i + " j=" + j);
    	}
    }

           one方法和two方法还会并发的去执行,但是加上volatile可以将共享变量i和j的改变直接响应到主内存中,这样保证了主内存中i和j的值一致性,然而在执行two方法时,在two方法获取到i的值和获取到j的值中间的这段时间,one方法也许被执行了好多次,导致j的值会大于i的值。所以volatile可以保证内存可见性,不能保证并发有序性。

           没有明白JLS中为什么使用两个变量来阐述volatile的工作原理,这样不是很好理解。volatile是一种弱的同步手段,相对于synchronized来说,某些情况下使用,可能效率更高,因为它不是阻塞的,尤其是读操作时,加与不加貌似没有影响,处理写操作的时候,可能消耗的性能更多些。但是volatile和synchronized性能的比较,我也说不太准,多线程本身就是比较玄的东西,依赖于CPU时间分片的调度,JVM更玄,还没有研究过虚拟机,从顶层往底层看往往是比较难看透的。在JDK5.0之前,如果没有参透volatile的使用场景,还是不要使用了,尽量用synchronized来处理同步问题,线程阻塞这玩意简单粗暴。另外volatile和final不能同时修饰一个字段,可以想想为什么。

            本文来自:高爽|Coder,原文地址:http://blog.csdn.net/ghsau/article/details/7424694,转载请注明。

    展开全文
  • Java基础:java线程状态

    万次阅读 2020-04-17 14:45:05
    Java线程具有五中基本状态 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread(); 就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就...

    线程的生命周期及五种基本状态

    在这里插入图片描述
    上图中基本上囊括了Java中多线程各重要知识点。掌握了上图中的各知识点,Java中的多线程也就基本上掌握了。主要包括:

    Java线程具有五中基本状态

    新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

    就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

    运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

    阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

    • 1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
    • 2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
    • 3.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

    死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

    就绪状态转换为运行状态:当此线程得到处理器资源;

    运行状态转换为就绪状态:当此线程主动调用yield()方法或在运行过程中失去处理器资源。

    运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。

    此处需要特别注意的是:当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。

    常用的线程函数

    sleep(long millis)

    在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

    join():指等待t线程终止

    join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。

    在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

    不加join:

    class Thread1 extends Thread{
    	private String name;
        public Thread1(String name) {
        	super(name);
           this.name=name;
        }
    	public void run() {
    		System.out.println(Thread.currentThread().getName() + " 线程运行开始!");
            for (int i = 0; i < 5; i++) {
                System.out.println("子线程"+name + "运行 : " + i);
                try {
                    sleep((int) Math.random() * 10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + " 线程运行结束!");
    	}
    }
     
    public class Main {
     
    	public static void main(String[] args) {
    		System.out.println(Thread.currentThread().getName()+"主线程运行开始!");
    		Thread1 mTh1=new Thread1("A");
    		Thread1 mTh2=new Thread1("B");
    		mTh1.start();
    		mTh2.start();
    		System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
     
    	}
    

    输出结果:

    main主线程运行开始!
    main主线程运行结束!
    B 线程运行开始!
    子线程B运行 : 0
    A 线程运行开始!
    子线程A运行 : 0
    子线程B运行 : 1
    子线程A运行 : 1
    子线程A运行 : 2
    子线程A运行 : 3
    子线程A运行 : 4
    A 线程运行结束!
    子线程B运行 : 2
    子线程B运行 : 3
    子线程B运行 : 4
    B 线程运行结束!
    发现主线程比子线程早结束

    加join:

    public class Main {
     
    	public static void main(String[] args) {
    		System.out.println(Thread.currentThread().getName()+"主线程运行开始!");
    		Thread1 mTh1=new Thread1("A");
    		Thread1 mTh2=new Thread1("B");
    		mTh1.start();
    		mTh2.start();
    		try {
    			mTh1.join();
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		try {
    			mTh2.join();
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
     
    	}
    }
    

    运行结果:

    main主线程运行开始!
    A 线程运行开始!
    子线程A运行 : 0
    B 线程运行开始!
    子线程B运行 : 0
    子线程A运行 : 1
    子线程B运行 : 1
    子线程A运行 : 2
    子线程B运行 : 2
    子线程A运行 : 3
    子线程B运行 : 3
    子线程A运行 : 4
    子线程B运行 : 4
    A 线程运行结束!
    主线程一定会等子线程都结束了才结束

    yield():暂停当前正在执行的线程对象,并执行其他线程。

    hread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。

    yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

    结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。可看上面的图。

    class ThreadYield extends Thread{
        public ThreadYield(String name) {
            super(name);
        }
     
        @Override
        public void run() {
            for (int i = 1; i <= 50; i++) {
                System.out.println("" + this.getName() + "-----" + i);
                // 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
                if (i ==30) {
                    this.yield();
                }
            }
    	
    }
    }
     
    public class Main {
     
    	public static void main(String[] args) {
    		
    		ThreadYield yt1 = new ThreadYield("张三");
        	ThreadYield yt2 = new ThreadYield("李四");
            yt1.start();
            yt2.start();
    	}
     
    }
    
    

    运行结果:

    第一种情况:李四(线程)当执行到30时会CPU时间让掉,这时张三(线程)抢到CPU时间并执行。

    第二种情况:李四(线程)当执行到30时会CPU时间让掉,这时李四(线程)抢到CPU时间并执行。

    sleep()和yield()的区别

    sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。

    sleep方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield方法使当前线程让出CPU占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把CPU的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程

    另外,sleep方法允许较低优先级的线程获得运行机会,但yield()方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用sleep方法,又没有受到I\O阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。

    setPriority(): 更改线程的优先级

    • MIN_PRIORITY = 1
    • NORM_PRIORITY = 5
    • MAX_PRIORITY = 10

    用法:

    Thread4 t1 = new Thread4("t1");
    Thread4 t2 = new Thread4("t2");
    t1.setPriority(Thread.MAX_PRIORITY);
    t2.setPriority(Thread.MIN_PRIORITY);
    

    interrupt()

    不要以为它是中断某个线程!它只是线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!

    wait()

    Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。

    单单在概念上理解清楚了还不够,需要在实际的例子中进行测试才能更好的理解。对Object.wait(),Object.notify()的应用最经典的例子,应该是三线程打印ABC的问题了吧,这是一道比较经典的面试题,题目要求如下:

    建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。代码如下:

    public class MyThreadPrinter2 implements Runnable {   
    	  
        private String name;   
        private Object prev;   
        private Object self;   
      
        private MyThreadPrinter2(String name, Object prev, Object self) {   
            this.name = name;   
            this.prev = prev;   
            this.self = self;   
        }   
      
        @Override  
        public void run() {   
            int count = 10;   
            while (count > 0) {   
                synchronized (prev) {   
                    synchronized (self) {   
                        System.out.print(name);   
                        count--;  
                        
                        self.notify();   
                    }   
                    try {   
                        prev.wait();   
                    } catch (InterruptedException e) {   
                        e.printStackTrace();   
                    }   
                }   
      
            }   
        }   
      
        public static void main(String[] args) throws Exception {   
            Object a = new Object();   
            Object b = new Object();   
            Object c = new Object();   
            MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);   
            MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);   
            MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);   
               
               
            new Thread(pa).start();
            Thread.sleep(100);  //确保按顺序A、B、C执行
            new Thread(pb).start();
            Thread.sleep(100);  
            new Thread(pc).start();   
            Thread.sleep(100);  
            }   
    }  
    

    输出结果: ABCABCABCABCABCABCABCABCABCABC

    先来解释一下其整体思路,从大的方向上来讲,该问题为三线程间的同步唤醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。为了控制线程执行的顺序,那么就必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是prev,就是前一个线程所持有的对象锁。还有一个就是自身对象锁。主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,终止当前线程,等待循环结束后再次被唤醒。运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A,C锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B,A锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁,唤醒A。看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。但是这种假设依赖于JVM中线程调度、执行的顺序。

    wait和sleep区别

    共同点:

    • 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
    • wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。 如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。 需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。

    不同点:

    • Thread类的方法:sleep(),yield()等 Object的方法:wait()和notify()等
    • 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。 sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
    • wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用

    所以sleep()和wait()方法的最大区别是:

    • sleep()睡眠时,保持对象锁,仍然占有该锁;
    • 而wait()睡眠时,释放对象锁。
    • 但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException(但不建议使用该方法)。

    sleep()方法

    • sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;
    • sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。
    • 在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。

    wait()方法

    • wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;
    • wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。
    • wiat()必须放在synchronized block中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。
    展开全文
  • java 线程线程 thread 设置线程名称 获取线程名称 public class Name extends Thread { @Override public void run() { //setName()方法设置线程名称 this.setName("线程A"); System.out.println(...
  • Java线程-守护线程与用户线程

    千次阅读 2019-08-27 16:19:35
    Java线程-守护线程与用户线程
  • JAVA线程详解

    千次阅读 2019-03-31 22:27:11
    JAVA线程调度 JAVA线程的状态 JAVA新建线程 JAVA线程Thread类 AVA线程属性 线程安全 线程简介 操作系统运行一个程序时,会为其创建一个进程,一个进程里可以创建多个线程,线程为操作系统调度的最小单位,每...
  • Java线程(九):Condition-线程通信更高效的方式

    万次阅读 多人点赞 2012-04-20 14:49:39
    接近一周没更新《Java线程》专栏了,主要是这周工作上比较忙,生活上也比较忙,呵呵,进入正题,上一篇讲述了并发包下的Lock,Lock可以更好的解决线程同步问题,使之更面向对象,并且ReadWriteLock在处理同步时更...
  • Java线程(一):线程安全与不安全

    万次阅读 多人点赞 2012-04-02 12:13:29
    作为一个Java web开发人员,很少也不需要去处理线程,因为服务器已经帮我们处理好了。记得大一刚学Java的时候,老师带着我们做了一个局域网聊天室,用到了AWT、Socket、多线程、I/O,编写的客户端和服务器,当时做...
  • 【一】Java线程初步了解

    万次阅读 2021-02-06 14:54:32
    Java线程 一、进程与线程 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,即进程空间或(虚空间)。进程不依赖于线程而独立存在,一个进程中可以启动多个线程。比如在Windows系统中,一个...
  • Java线程模型

    千次阅读 2018-12-04 21:54:37
    Java线程模型 JAVA大行其道的今天,已经距离JAVA产生已经二十多年,并不意味着JAVA就已经尽善尽美了,他依旧存在不足与值得思考的地方 线程三大模型 线程通常被定义为一个进程中代码的不同执行路线,一个进程可...
  • java线程篇-线程基本使用方法

    万次阅读 2019-08-14 17:24:03
    有做了一次安卓的项目开发,在安卓中那些乱糟糟的线程最后我也不知道是怎么把他们弄得能运行的,玄学啊 ,安卓的线程真的不能在吐槽了,总之,因为这些原因,我准备写7篇左右的java线程方面知识,当然每篇内容不会太...
  • java 线程1 线程分类

    千次阅读 2016-12-05 09:48:41
    java线程分两类:  守护线程和用户线程。 用户线程:  是用户创建的一般线程,如继承Thread类或实现Runnable接口等实现的线程。 守护线程:  是为用户线程提供服务的线程,如JVM的垃圾回收、内存...
  • JAVA线程实现原理、线程状态

    千次阅读 2018-08-20 14:24:27
    JAVA线程现在的实现是基于操作系统原生线程模型来实现的。因此,现在操作系统支持怎样的线程模型,在很大程度上决定了JAVA虚拟机的线程是怎样映射的。这点在不同的平台上没有办法达成一致。 对于Sun JDK来说,它的...
  • java线程模型

    千次阅读 2018-06-19 21:50:24
    java线程模型:内核线程( Kernel-Level Thread,KLT) 就是直接由操作系统内核( Kernel, 下称内核) 支持的线程, 这种线程由内核来完成线程切换, 内核通过操纵调度器( Scheduler) 对线程进行调度, 并负责将...
  • Java内存模型与Java线程的实现原理

    万次阅读 2016-07-15 23:52:18
    Java内存模型与Java线程的实现原理
  • Java线程片——线程的关闭

    千次阅读 2018-10-24 11:38:46
    在我们寻求Java线程的关闭关闭方式的时候,我们也许会按住Ctrl键进入到Thread类中阅读源码,很快的我们就能找到一个stop()方法。似乎stop()方法就是结束线程的方法,但是事实恰恰与我们的猜想相反。stop()方法太过于...
  • 1.java线程面试题 java线程面试题 1.实现线程的方法,有什么区别 继承Thread与实现Runnable接口。 启动方法不一样。Thread1继承,Thread2实现Runnable接口,则启动一 个Thread1线程可以使用new Thread1().start()...
  • Java 线程与并发

    千次阅读 2020-03-07 19:04:31
    --- 包含了所有与 Java 线程与并发相关的文章索引
  • Java线程状态、线程停止、线程阻塞

    千次阅读 2015-05-28 08:59:44
    Java线程状态、线程停止、线程阻塞
  • 1.分析一个Java线程的转储 为了可以理解/分析线程转储,首先要理解线程转储的各个部分。让我们先拿一个简单的线程堆栈为例,并且去了解他的每个部分。 "ExecuteThread: '1' " daemon prio=5 tid=0x628330 nid=0xf ...
  • Java线程详解(深度好文)

    万次阅读 多人点赞 2018-09-16 21:03:21
    Java线程:概念与原理 一、进程与线程    进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,即进程空间或(虚空间)。进程不依赖于线程而独立存在,一个进程中可以启动多个线程。比如...
  • Java线程:概念与原理

    千次阅读 2017-03-29 21:10:42
    Java线程创建与启动 Java线程线程栈模型与线程的变量 Java线程线程状态的转换 Java线程线程的同步与锁 Java线程线程的交互 Java线程线程的调度-休眠 Java线程线程的调度-优先级 Java线程线程的调度-让步 Java线程...
  • 关于Java线程,你必须掌握的知识点

    千次阅读 多人点赞 2020-03-01 17:42:53
    Java线程 我为什么要写这篇文章?主要有一下几点: 加深自己对Java线程的理解 勘误网上有些错误的知识和认知 通过写文章,来交流思想 网上关于Java线程的文章数不胜数,实在太多了,我又怎么写一篇让其他人一看...
  • 但是我突然想到了一个特性,就是众所周知,java线程的切换并不稳定,也就是同一优先级的线程并发,无法控制每个线程的执行次数.如果是java自己控制的用户线程,那么为什么不能控制线程切换呢?因此我觉得是cpu负责调度的,....
  • Java 线程的七种状态

    千次阅读 2019-09-26 19:31:10
    Java 线程的生命周期中,按复杂的并发场景考虑,可将 Java 线程的状态分为七种,分别是:新建(New),可运行(Runnable 或 Ready),运行(Running),无限期等待(Waiting),限期等待(Timed Waiting),阻塞...
  • Java线程面试题合集(含答案)

    万次阅读 多人点赞 2018-06-18 14:30:48
    来源:Java线程面试题下面是我自己收集整理的Java线程相关的面试题,可以用它来好好准备面试。 参考文档: 《Java核心技术 卷一》 Java线程面试题 Top 50:http://www.importnew.com/12773.html JAVA多线程和并发...
  • Java 线程是一个轻量级执行任务的处理单元。Java提供了Thread类来支持多线程,开发者在应用中可以创建多个线程来支持并发执行任务。在应用中存在两种类型的线程,用户线程和守护线程。当我们启动应用的时候,main...
  • java线程如何返回值

    千次阅读 2018-08-27 10:39:22
    java线程如何返回结果 解决方法: 在默认情况下,继承Thread或者实现Runnable接口所创建的线程对象不具有返回结果的功能,如果在需要取得返回的结果的情况下是极为不方便的,但在Java1.5的并发包中可以使用Future...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 301,451
精华内容 120,580
关键字:

java线程

java 订阅