Java多线程基础(面试向)
2021-06-15 08:07
public class ThreadTest04 { public static void main(String[] args) { Runnable runnable = new Processor(); Thread thread_1 = new Thread(runnable,"r1"); thread_1.start(); Thread thread_2 = new Thread(runnable,"r2"); thread_2.start(); } } class Processor implements Runnable{ @Override public void run() { for(int i=0; i){ System.out.println(Thread.currentThread().getName() + "," + i); if(i%10 == 0){ try {
//睡眠100毫秒,将CPU时间片交给其他线程使用 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
结果:当哪个线程运行时自己的i的值能被10整除的,那么这个线程就进入休眠状态,让另外一个线程运行。多次运行发现,也有可能依旧是当前的线程休眠好后仍然占用资源继续运行,估计是睡眠时间比较短的问题
3、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态
为什么要用join()方法
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
public class ThreadTest05 { 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) { // TODO Auto-generated catch block e.printStackTrace(); } try { mTh2.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ "主线程运行结束!"); } } 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 ) { System.out.println("子线程"+name + "运行 : " + i); try { sleep((int) Math.random() * 10); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + " 线程运行结束!"); } }
结果:每次运行,主线程都要等子线程运行结束后自己才结束运行
main主线程运行开始! B 线程运行开始! A 线程运行开始! 子线程B运行 : 0 子线程A运行 : 0 子线程B运行 : 1 子线程A运行 : 1 子线程B运行 : 2 子线程A运行 : 2 子线程B运行 : 3 子线程A运行 : 3 子线程B运行 : 4 子线程A运行 : 4 B 线程运行结束! A 线程运行结束! main主线程运行结束!
4、线程让步:Thread.yield() 方法
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中
结论:大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
public class ThreadTest06 { public static void main(String[] args) { MyThread_2 yt1 = new MyThread_2("张三"); MyThread_2 yt2 = new MyThread_2("李四"); yt1.start(); yt2.start(); } } class MyThread_2 extends Thread{ MyThread_2(String s){ super(s); } @Override public void run() { for(int i=0; i){ System.out.println(Thread.currentThread().getName()+":"+i); if(i==30){ // 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行) this.yield(); } } } }
5、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
~~~各种区别
sleep()、wait()?
- . 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 - sleep()是Thread类的方法,wait()是Object类的方法
- sleep()方法正在执行的线程主动让出CPU(然后CPU就可以去执行其他任务),在sleep指定时间后CPU再回到该线程继续往下执行(注意:sleep方法只让出了CPU,而并不会释放同步资源锁!!!);wait()方法则是指当前线程让自己暂时退让出同步资源锁,以便其他正在等待该资源的线程得到该资源进而运行,只有调用了notify()方法,之前调用wait()的线程才会解除wait状态,可以去参与竞争同步资源锁,进而得到执行。(注意:notify的作用相当于叫醒睡着的人,而并不会给他分配任务,就是说notify只是让之前调用wait的线程有权利重新参与线程的调度)
- sleep()方法可以在任何地方使用;wait()方法则只能在同步方法或同步块中使用
-
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态
举个栗子
package com.xxg.thread; public class ThreadTest07 { public static void main(String[] args) { new Thread(new Thread01()).start(); try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } new Thread(new Thread02()).start(); } } class Thread01 implements Runnable { @Override public void run() { synchronized (ThreadTest07.class) { System.out.println("enter thread1..."); System.out.println("thread1 is waiting..."); try { // 调用wait()方法,线程会放弃对象锁,进入等待此对象的等待锁定池 ThreadTest07.class.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("thread1 is going on ...."); System.out.println("thread1 is over!!!"); } } } class Thread02 implements Runnable { @Override public void run() { synchronized (ThreadTest07.class) { System.out.println("enter thread2..."); System.out.println("thread2 is sleeping..."); // 只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。 ThreadTest07.class.notify(); // 区别 // 如果我们把代码:TestD.class.notify();给注释掉,即TestD.class调用了wait()方法,但是没有调用notify() // 方法,则线程永远处于挂起状态。 try { // sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程, // 但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。 // 在调用sleep()方法的过程中,线程不会释放对象锁。 Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("thread2 is going on ...."); System.out.println("thread2 is over!!!"); } } }
结果:
注释掉 ThreadTest07.class.notify();,运行结果如下,且程序出于挂起状态
sleep()、yield()?
- sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行
- sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程
- 另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。
- sleep()方法比yield()方法有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行。
----? 线程常见名词
线程类的一些常用方法:
sleep(): 强迫一个线程睡眠N毫秒。
isAlive(): 判断一个线程是否存活。
join(): 等待线程终止。
activeCount(): 程序中活跃的线程数。
enumerate(): 枚举程序中的线程。
currentThread(): 得到当前线程。
isDaemon(): 一个线程是否为守护线程。
setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)
setName(): 为线程设置一个名称。
wait(): 强迫一个线程等待。
notify(): 通知一个线程继续运行。
setPriority(): 设置一个线程的优先级。
----? 线程同步
多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线程的处理的数据,而B线程又修改了A线程处理的数理。显然这是由于全局资源造成的,有时为了解决此问题,优先考虑使用局部变量,退而求其次使用同步代码块,出于这样的安全考虑就必须牺牲系统处理性能,加在多线程并发时资源挣夺最激烈的地方,这就实现了线程的同步机制
要跨线程维护正确的可见性,只要在几个线程之间共享非 final 变量,就必须使用synchronized(或 volatile)以确保一个线程可以看见另一个线程做的更改。
为了在线程之间进行可靠的通信,也为了互斥访问,同步是必须的。这归因于java语言规范的内存模型,它规定了:一个线程所做的变化何时以及如何变成对其它线程可见。
同步和多线程关系:没多线程环境就不需要同步;有多线程环境也不一定需要同步。
锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。
互斥 即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。
可见性 要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题
小结:为了防止多个线程并发对同一数据的修改,所以需要同步,否则会造成数据不一致(就是所谓的:线程安全。如java集合框架中Hashtable和Vector是线程安全的。我们的大部分程序都不是线程安全的,因为没有进行同步,而且我们没有必要,因为大部分情况根本没有多线程环境)。
--->同步机制实现方法
1)synchronized
synchronized的作用域有两个,在对象实例内,如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
在某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法。