首页 > 编程 > Java > 正文

【java总结】多线程(基础篇)

2019-11-08 02:16:53
字体:
来源:转载
供稿:网友

java的线程分为5种状态:创建、就绪、运行、阻塞和死亡

创建:

在java种创建线程的方式有两种,一种是通过继承Thread类并且重写run方法,run方法中执行的代码便是线程执行的代码。另一种是通过实现Runnable接口,并将该接口实例传入一个Thread实例。通过对Thread的引用调用start()方法,即可让线程进入就绪状态。如果直接调用run方法,并不会生成线程,而是在当前线程中把run()当做一个普通方法执行。

public class Thread1 extends Thread{	/*	 * 实现线程的方法一:通过继承Thread并覆盖run()方法来实现多线程。	 */	@Override	public void run(){		System.out.PRintln(Thread.currentThread().getName()+"线程开始!");		for(int i=0;i<10;i++){			System.out.println(Thread.currentThread().getName()+" "+i);			try{				sleep((int)Math.random()*10);			}catch(InterruptedException e){				e.printStackTrace();			}		}		System.out.println(Thread.currentThread().getName()+"线程结束!");	}}

public class Thread2 implements Runnable{	/*	 * 实现线程的方法二:通过实现Runnable接口来实现多线程	 * 实现Runnable接口比继承Thread类所具有的优势:	 * 1):适合多个相同的程序代码的线程去处理同一个资源	 * 2):可以避免java中的单继承的限制	 * 3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立	 */	@Override	public void run(){		System.out.println(Thread.currentThread().getName()+"线程开始!");		for(int i=0;i<10;i++){			System.out.println(Thread.currentThread().getName()+" "+i);			try{				Thread.sleep((int)Math.random()*10);			}catch(InterruptedException e){				e.printStackTrace();			}		}		System.out.println(Thread.currentThread().getName()+"线程结束!");	}}

就绪:

处于就绪状态的线程随时可以被JVM的线程调度器调度,进入运行状态。对于处于就绪状态的线程,我们并不能对他们被调度的顺序进行任何估计,也就是说,线程的执行顺序是不可预测的。处于运行状态的线程,通过调用yield()方法,可以返回到就绪状态,然而它有可能瞬间被再次调度。yield()方法把运行机会让给了同等优先级的其他线程。

public class ThreadYield extends Thread{	@Override	public void run(){		 for (int i = 1; i <= 50; i++) {	            System.out.println("" +Thread.currentThread().getName() + "-----" + i);	            // 当i==25时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)	            if (i==25) {	                this.yield();	            }	        }	}}

public class ThreadYieldTest {	/*	 * Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。	 * 该方法让当前线程回到可运行状态,以允许其他具有相同优先级的线程获得运行机会。	 * 但是实际中无法保证yield()达到让步目的,因为当前线程有可能被线程调度程序再次选中。	 */	public static void main(String[] args){		ThreadYield thread1=new ThreadYield();		ThreadYield thread2=new ThreadYield();		thread1.start();		thread2.start();	}}

运行:

处于运行状态的线程随时有可能被线程调度器换下,进入到就绪状态。想要规定线程的顺序,需要调用join方法,对某个线程 的调用join方法,则主线程会阻塞到该线程执行完后再继续执行。或者使用一种叫做锁的机制(下文会提及)。当一个线程完成它run()里面的所有工作时,线程会自动死亡。调用sleep(),线程会进入休眠,并且在一段时间内不会被再度调用。睡眠时间过后,线程才再次进入就绪队列中。

public class ThreadJoinTest {	/*	 * join是Thread类的一个方法,作用是等待该线程终止。例如对子线程A调用join()方法,	 * 主线程将等待子线程A终止后才能继续后面的代码。	 */	public static void main(String[] args){		System.out.println("主线程开始!");		Thread1 thread1=new Thread1();		Thread1 thread2=new Thread1();		thread1.start();		thread2.start();		try{			thread1.join();		}catch(InterruptedException e){			e.printStackTrace();		}		try{			thread2.join();		}catch(InterruptedException e){			e.printStackTrace();		}		System.out.println("主线程结束!");	}}

死亡:

线程因为代码执行完毕而正常结束自身线程,或者因为某些异常而结束线程。

public class ThreadInterrupt extends Thread{	/*	 * 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 。 	 */	@Override	public void run(){			try{				for(int i=0;i<50;i++){					System.out.println("i="+i);					Thread.sleep(100);				}			}catch(InterruptedException e){				System.out.println("线程被终结!!!!");				//return;			}				}}

锁机制

在介绍阻塞之前,先了解一下java的锁机制。java的锁机制通过synchronized来实现。所有的Java对象都有一个与synchronzied关联的监视器对象(monitor),允许线程在该监视器对象上进行加锁和解锁操作。

a、静态方法:Java类对应的Class类的对象所关联的监视器对象。

b、实例方法:当前对象实例所关联的监视器对象。

c、代码块:代码块声明中的对象所关联的监视器对象。

当锁被释放,对共享变量的修改会写入主存。

public class ThreadSynchronizedTest {		private static int value = 0;		//获得该类对应的Class类的对象所关联的监视器的锁		public synchronized static int getNext(){  		      return value++;  	   } 	   //获得当前实例所关联的监视器的锁	   public synchronized int getNext2(){  	      return value++;  	   }  	   //获得当前实例所关联的监视器的锁	   public int getNext3(){	      synchronized(this){  	         return value++;  	      }  	   }	   public static void main(String[] args){		   for(int i=0;i<3;i++){			   new Thread(new Runnable(){				   @Override				   public void run(){					   ThreadSynchronizedTest x=new ThreadSynchronizedTest();					   System.out.println("value="+x.getNext());					   System.out.println("value="+x.getNext2());					   System.out.println("value="+x.getNext3());				   }			   }).start();		   }	   }}

阻塞:

阻塞跟Obj.wait(),Obj.notify()方法有关。当调用wait方法时,线程释放对象锁,进入阻塞状态,直到其他线程唤醒它。

Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用

Obj.notify()作用:对对象锁的唤醒操作。notify()调用后,并不是马上就释放对象锁的,而

是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线

程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。

Obj.wait()作用:线程在获取对象锁后,主动释放对象锁,同时本线程休眠,直到有其它线程调用

对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。

下面我们通过一道题目来加深理解。

问题:建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。

代码如下:

public class ThreadPrintABCTest {	/*	 * 建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。	 * 	 */	   public static void main(String[] args) throws Exception {   	        Object a = new Object();   	        Object b = new Object();   	        Object c = new Object();   	        ThreadPrintABC pa = new ThreadPrintABC("A", c, a);   	        ThreadPrintABC pb = new ThreadPrintABC("B", a, b);   	        ThreadPrintABC pc = new ThreadPrintABC("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);  	        }   }
public class ThreadPrintABC implements Runnable{	private String data;	private Object pre;	private Object self;	public ThreadPrintABC(String data,Object pre,Object self){		this.data=data;		this.pre=pre;		this.self=self;	}	@Override	public void run(){		int count=10;		while(count>0){			synchronized(pre){				synchronized(self){					if(data=="C"){						System.out.println(data);					}else{						System.out.print(data);					}					count--;					self.notify();				}				try{					pre.wait();				}catch(InterruptedException e){					e.printStackTrace();				}			}		}	}}

死锁:

两个或两个以上的线程在执行过程当中,由于竞争资源或者彼此之间通信而造成的一种阻塞现象。比如,当线程A调用wait()方法等待线程B的唤醒,而线程B同时也调用wait方法等待线程A的唤醒,这时两个线程将陷入僵持状态,永远处在阻塞状态, 成为死锁进程,即两个线程永远也不会被执行。

sleep方法与wait方法的区别及细节:

sleep()睡眠时,保持对象锁,仍然占有该锁;而wait()睡眠时,释放对象锁。

sleep()使当前线程进入停滞状态(阻塞当前线程),让出CPU的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;

sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。

在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。

wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;

wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。

wiat()必须放在synchronizedblock中,否则扔出”java.lang.IllegalMonitorStateException“异常。

 


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表