浅谈Java中的多线程
来源:互联网 发布:数据库设计规则 编辑:程序博客网 时间:2024/04/30 03:04
导论
并发和并行
我们知道,在单核机器上,“多进程”并不是真正的多个进程在同时执行,而是通过CPU时间分片,操作系统快速在进程间切换而模拟出来的多进程。我们通常把这种情况成为并发,也就是多个进程的运行行为是“一并发生”的,但不是同时执行的,因为CPU核数的限制(PC和通用寄存器只有一套,严格来说在同一时刻只能存在一个进程的上下文)。
1 基本概念
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。
同时”执行是人的感觉,在线程之间实际上轮换执行。
在Java中线程指两件不同的事情:
1)java.lang.Thread类的一个实例;
2)线程的执行
关于Java中线程的生命周期,下面给出的这幅图总结的非常详细生动,它包含了Java多线程中的所有重要知识点。
Java线程具有5种基本状态:
新建状态(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处理完毕时,线程重新转入就绪状态。
Thread类中的方法
sleep方法,这是一个静态方法,作用是让当前线程进入休眠状态(但线程不会释放已获取的锁),这个休眠状态其实就是我们上面提到过的Time Waiting状态,从休眠状态“苏醒”后,线程会进入到Runnable状态。
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
join方法实现是通过wait(小提示:Object 提供的方法)。当main线程调用t.join时候,main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程,比如退出后。这就意味着main 线程调用t.join时,必须能够拿到线程t对象的锁。
消亡状态(dead):线程执行完了,或者因异常退出了run()方法,该线程结束它的生命周期。
yield方法,这是一个静态方法,作用是让当前线程“让步”,目的是为了让优先级不低于当前线程的线程有机会运行,这个方法不会释放锁。
interrupt方法,这是一个实例方法。每个线程都有一个中断状态标识,这个方法的作用就是将相应线程的中断状态标记为true,这样相应的线程调用isInterrupted方法就会返回true。通过使用这个方法,能够终止那些通过调用可中断方法进入阻塞状态的线程。常见的可中断方法有sleep、wait、join,这些方法的内部实现会时不时的检查当前线程的中断状态,若为true会立刻抛出一个InterruptedException异常,从而终止当前线程。
2 Java多线程的创建与启动
Java中线程的创建有以下三种基本形式
1)继承Thread类,重写该类的run()方法。
class MyThread extends Thread {@Overridepublic void run(){//重写run()方法,这是线程的执行体System.out.println(Thread.currentThread().getName());}}public class MyThreadTest{ public static void main(String []args){ Thread m1=new MyThread();//创建线程1,进入新建状态 Thread m2=new MyThread();//创建线程2,进入新建状态 m1.start();//线程1进入就绪状态 m2.start();//线程2进入就绪状态 } }
2)实现Runnable接口,并重写该接口的run()方法,该方法同样是线程的执行体,先创建MyRunnable对象,并将该对象作为Thread类的target来创建Thread对象,该对象才是真正的线程的对象。
class MyRunnable implements Runnable{@Overridepublic void run(){//重写run()方法,这是线程的执行体System.out.println(Thread.currentThread().getName());}}public class MyThreadTest{ public static void main(String []args){ Runnable myRunnable=new MyRunnable();//创建Runnable对象 Thread t1=new Thread(myRunnable);//将myRunnable作为Thread类的target创建线程1,该对象才是真正的线程对象 Thread t2=new Thread(myRunnable);//创建线程2 t1.start();//线程1进入就绪状态 t2.start();//线程2进入就绪状态 } }那么Thread类和Runnable接口二者有什么联系呢?其实,Thread类本身也实现了Runnable接口,run()方法最先是在Runnable接口中定义的。
public interface Runnable { public abstract void run();}Thread类中对run()方法进行了重写(源于Thread源码)。
@Override public void run() { if(target!=null){ target.run(); }}也就是说当执行到Thread类中的run()方法时,会先判断target(构造函数中的Runnable对象)是否存在,如果存在则执行target中的run方法,也就是实现了Runnable接口并重写了run()方法的类中的run()方法。
上面两种方法它们有一个共同的缺点,那就是异步方法run没有返回值,也就是说我们无法直接获取它的执行结果,只能通过共享变量或者线程间通信等方式来获取。下面这种方法有返回值。
3)使用Callable和Future接口创建线程。具体方法是:首先创建Callable接口的实现类,并在该类中重写call()方法,然后使用FutureTask类来包装Callable实现类的对象,并以此FutureTask类的对象作为Thread类的target来创建线程。
class MyCallable implements Callable<Integer>{@Overridepublic Integer call(){//区别于run(),call()方法具有返回值System.out.println(Thread.currentThread().getName());return 1;}}public class MyThreadTest{ public static void main(String []args){ Callable<Integer> myCallable=new MyCallable();//创建Callable实现类对象 FutureTask<Integer> ft=new FutureTask<Integer>(myCallable);//使用FutureTask类来包装Callable实现类对象 Thread thread=new Thread(ft);//把ft作为Tread类的target来创建线程 thread.start();//线程进入就绪状态 try{ int sum=ft.get();//获取新创建线程的call()返回的结果 System.out.println(sum); }catch (InterruptedException e) {//当一个线程处于等待,睡眠,或者占用,也就是说阻塞状态,而这时线程被中断就会抛出这类错误thread.interrupt();//手动把自己的线程中断,这样可以保证线程一定能够被及时中断 e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
3 Java多线程的就绪、运行和消亡状态
就绪->运行:当此线程得到cpu资源;
运行->就绪:此线程主动调用yield()方法或在运行过程中失去cpu资源
运行->消亡:此线程的执行体执行完毕或者发生异常。
注意:当线程调用yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。
由于实际的业务需要,常常会遇到需要在特定时机终止某一线程的运行,使其进入到死亡状态。目前最通用的做法是设置一boolean型的变量,当条件满足时,使线程执行体快速执行完毕。
class MyRunnable implements Runnable{ private Boolean stop;@Overridepublic void run() {if(!stop){System.out.println(Thread.currentThread().getName());}}public void stopThread() {this.stop = true;}}public class ThreadTest {public static void main(String[] args) {MyRunnable m=new MyRunnable();Thread t=new Thread(m);t.start();//进入就绪状态m.stopThread();//使线程消亡}}
- 浅谈Java中的多线程
- 浅谈java中的多线程
- 浅谈Android和java中的多线程下载
- 浅谈Android和java中的多线程下载
- 浅谈多线程在java程序中的应用
- 浅谈java多线程中的内存可见性
- 浅谈多线程在java程序中的应用
- 浅谈.net中的多线程
- 浅谈iOS中的多线程
- 黑马程序员——浅谈java中的多线程
- 浅谈java中的多线程及synchronized锁重入的含义
- 浅谈Java多线程机制
- 浅谈java多线程
- 浅谈Java多线程
- 浅谈java的多线程
- 浅谈Windows中的多线程编程
- 浅谈多线程中的懒汉式单例
- 浅谈java中的引用
- Java String中的indexof 和 substring 用法
- css属性列表 和 属性值含义
- 快速排序
- 浅谈算法和数据结构
- Delphi FMX Grid列头样式设定
- 浅谈Java中的多线程
- 网络编程(陈硕)
- android 百度地图系列之结合方向传感器的地图定位
- Redheat本地yum源配置
- 推荐 IIS7.0下ThinkPHP提示“缓存文件写入失败!” 需要设置user的权限即可 Home/Runtime/Cache/
- 轮滑加小圆点的适配器
- 发布一个基于 Reactor 模式的 C++ 网络库
- 【杭电oj】2081 - 手机短号(水)
- 【JZOJ3466】选课