多线程编程学习一(Java多线程的基础).
一、进程和线程的概念进程:一次程序的执行称为一个进程,每个 进程有独立的代码和数据空间,进程间切换的开销比较大,一个进程包含1—n个线程。进程是资源分享的最小单位。 线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小,线程是cpu调度的最小单位。 多进程:指操作系统能同时运行多个任务(程序)。 多线程:指同一个程序中有多个顺序流在执行,线程是进程内部单一控制序列流。 线程和进程一样包括:创建、就绪、运行、阻塞、销毁 五个状态: 1、新建状态(New):新创建了一个线程对象。 2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取cpu的使用权。 3、运行状态(Running):就绪状态的线程获取了cpu,执行程序代码。 4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃cpu使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁) (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。 (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁) 5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期? 二、多线程的优势? ? 单线程的特点就是排队执行,也就是同步。而多线程能最大限度的利用cpu的空闲时间来处理其他的任务,系统的运行效率大大提升,使用多线程也就是在执行异步。 三、使用多线程? ? 实现多线程编程的方式主要有两种,一种是继承Thread类,另一种是实现Runable接口。其实,使用继承Thread类的方式创建新线程时,最大的局限就是不支持多继承,因为Java语言的特点就是单根继承,所以为了支持多继承,完全可以实现Runnable接口,一边实现一边继承。但是这两种方式创建的线程在工作时的性质是一样的,没有本质的差别。 public class Thread1 extends Thread { private int count=5; @Override void run() { for (int i=0;i<2;i++){ System.out.println("现在是线程"+currentThread().getName()+"在执行:"+count--); } } } class Thread2 implements Runnable { run() { for(){ System.out.println("现在是线程"+Thread.currentThread().getName()+"在执行:"+count--class Test{ static main(String[] args){ //集成Thread类 Thread1 thread1=new Thread1(); Thread t1=new Thread(thread1,"A"); Thread t2=); Thread t3=); t1.start(); t2.start(); t3.start(); 实现Runable接口 Thread2 thread2= Thread2(); Thread t4=new Thread(thread2,"A2"); Thread t5=); Thread t6=); t4.start(); t5.start(); t6.start(); }} 演示这个结果是为了说明以下两点: 1、cpu对线程的调度具有不确定性,采用“抢占式”调度。 2、对于网上经常说的,实现 Runnable 接口的线程可以实现共享数据,而继承 Thread 的线程就不能。其实不然,它们两者的区别仅是单继承的限制以及一些用法的不同(比如?如果你想对这个Thread对象做点别的事情(比如getName),那么你就必须通过调用Thread.currentThread()方法得到对此线程的引用),没有实质的差别。 ? 多线程执行时为什么调用的是start()方法而不是run()方法? ? ? 如果调用代码thread.run()就不是异步执行了,而是同步,那么此线程对象就不会交给“线程规划器”来进行处理。而是由main主线程来调用run()方法,也就是说必须要等到run()方法中的代码执行完成后才可以执行后面的代码。start()用来启动一个线程,当调用start()方法时,系统才会开启一个线程,通过Thead类中start()方法来启动的线程处于就绪状态(可运行状态),此时并没有运行,一旦得到cpu时间片,就自动开始执行run()方法。?四、synchronized 关键字? ? 多线程的锁机制,通过在多线程要调用的方法前加入synchronized 关键字,使多个线程在执行方法时,要首先尝试去拿这把锁,如果能够拿到这把锁,那么这个线程就可以执行synchronize里面的代码。如果不能拿到这把锁,那么这个线程就会不断地尝试拿这把锁,直到拿到这把锁。synchronized 可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区” 或 “临界区”。 ? ? 使用synchronized关键字主要是为了保证当前线程在执行过程中,不被其他线程抢占并修改了共享的资源,从而导致线程不安全的情况出现。 五、常用线程方法1、Thread.currentThread()方法:返回代码段正在被哪个线程调用的信息。最常见的就是Thread.currentThread().getName()。 2、isAlive()方法:判断当前的线程是否处于活动状态。什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程正在运行或准备开始运行的状态,就认为线程是“存活”的。 3、Thread.sleep()方法:在指定的毫秒数内让"正在执行的线程"休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()返回的线程。 4、getId()方法:取得该线程的唯一标识。 5、Thread.interrupt()方法:用于中断线程,这里需要注意Thread.interrupt 的作用其实也不是中断线程,而是「通知线程应该中断了」,具体到底中断还是继续运行,应该由被通知的线程自己处理。具体来说,当对一个线程,调用 interrupt() 时, ① 如果线程处于被阻塞状态(例如处于sleep,wait,join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常,并且清除中断标志,使之变为false。 Thread thread = new Thread(() -> { ?值得一提的是,判断线程是否中断有两个办法:
?通过抛出异常来中断线程: class MyThread Thread { @Override run(){ try { int i = 0; i < 500000; i++) { if (this.interrupted()) { System.out.println("已经是停止状态了!我要退出了"); throw InterruptedException(); } System.out.println("i=" + (i + 1)); } } catch (InterruptedException e) { System.out.println("进入MyThread.java类run方法中的catch了!"); e.printStackTrace(); } } } ?另外,还可以通过retuen的方法来中断线程。不过还是建议"抛异常"的方法来实现线程的停止,因为在catch块中还可以将异常向上抛,使线程停止的事件得以传播。 6、Thread.yield()方法:放弃当前的cpu资源,将它让给其他的任务去占用cpu的执行时间。但放弃的时间不确定,有可能刚刚放弃,马上又获得cpu时间片。 7、setPriority()方法:为了程序的可移植性,建议植使用 MAX_PRIORITY,NORM_PRIORITY,MIN_PRIORITY 三个级别。设置优先级并不意味着优先级低的就得不到调用,只是cpu更倾向于让高的优先级先执行,但是cpu具体调用那个线程是无法确定的,设置优先级只能保证说这个线程被调用的频率比较高。 8、setDaemon(true):守护线程。守护线程是一个特殊的线程,它的特性有“陪伴”的含义,当进程中不存在非守护线程了,则守护线程自动销毁。典型的守护线程就是垃圾回收线程,当进程中没有非守护线程了,则垃圾回收线程也就没有存在的必要了。 9、join()方法:等待该线程终止。join() 方法主要是让调用该方法的thread完成run方法里面的东西后, 再执行join()方法后面的代码,对join()方法的调用可以被中断,做法是调用线程上的的interrupt()方法。 六、其他1、stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。 2、suspend()方法暂停线程。resume()方法恢复线程的执行。在使用 suspend() 和resume()时,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题 。这是一个典型的线程对立的例子,如果有两个线程同时持有一个线程对象,一个尝试去中断线程,另一个尝试去恢复线程,如果并发进行的话,无论调用时是否进行了同步,目标线程都是存在死锁风险的。 3、以上提到的原因导致 stop()、suspend()、resume() 被废弃,不建议使用。 Daemon 线程是一种支持型线程,主要用作程序中后台调度以及支持性工作。JVM 不存在非 Daemon 线程的时候,Java 虚拟机将会退出。所以不能依靠 Daemon 线程的 finally 块来确保执行关闭或清理资源的逻辑。 4.? 启线程前,最好为这个线程设置线程名字,因为这样在使用 jstack 分析程序或者问题排查时,能够找到一个切入点。 (编辑:北几岛) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |