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

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“异常。






 

你可能感兴趣的:(学习笔记,JAVA)