java多线程简单案例入门

本文讲解3点:

一. 对比单线程和多线程的区别

二. 创建多线程的2种方式:1. extends Thread  2.implements Runnable

三. 线程同步


 一. 对比单线程和多线程的区别

1. 单线程

TestThread.java

public class TestThread {
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new ThreadDemo().run();
		while(true){
			
			System.out.println("main():"+Thread.currentThread().getName());
		}

	}

}

class ThreadDemo{
	
		public void run(){
			while(true){
				
				System.out.println("run():"+Thread.currentThread().getName());
			}
		};
		
}

运行结果截图:

结果分析:其实这只是一个普通的单线程程序,所以main()方法里 while()的代码不会被执行到。而且,new ThreadDemo().run(); 是运行在main 线程内。

2. 多线程  (TestThread.java 改成一个多线程,对比运行结果)

   TestThread2.java

public class TestThread2 {
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new ThreadDemo2().start();
		while(true){
			
			System.out.println("main():"+Thread.currentThread().getName());
		}

	}

}

class ThreadDemo2 extends Thread{
	
		public void run(){
			while(true){
				
				System.out.println("run():"+Thread.currentThread().getName());
			}
		};
		
}

运行结果截图:

  

分析结果: 线程ThreadDemo2 和 线程main 交替执行,无序。

跟上面的TestThread.java对比,2点区别:1. TestThread.java 中ThreadDemo不是线程,因为没有extends Thread 也没有implements Runnable , 所以 TestThread.java 是普通的单线程程序。 而 TestThread中ThreadDemo2是线程,因为extends Thread .  2, ThreadDemo2 的启动线程的方法是start(),不是run().

-----------------------------------------------------------------------------------------------------------

二. 创建多线程的2种方式:1. extends Thread  2.implements Runnable


一). 用Thread类创建线程

1. 要将一段代码在一个新的线程上运行,该代码应该在一个类的run()函数中,并且run()函数所在的类是Thread的子类。倒过来看,我们要实现多线程,必须编写一个继承了Thread类的子类,子类要覆盖Thread类中的run()函数,在子类的run()函数中调用想在想在新线程上运行的程序代码。

2. 启动线程,要start()方法,而不会用run()方法。

3. 由于线程的代码段在run方法中,那么该方法执行完以后线程也就响应结束了,因而可以通过控制run方法中循环的条件来控制线程的结束。

1)前台线程,守护线程(也叫后台线程)和联合线程

  1. 如果对某个线程对象在启动(调用start方法)之前调用了setDaemon(true)方法,这个线程就变成了守护线程。

  2. 对于java程序来说,只要还有一个前台线程在运行,这个进程就不会结束;如果一个进程中只有守护线程运行,这个进程就会结束。‘

  3. pp.join()的作用是把pp所对应的线程合并到调用pp.join();语句的线程中。

3.1 TestThread3.java

public class TestThread3 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new ThreadDemo3().start();

	}	
	

}

class ThreadDemo3 extends Thread{
	
	public void run(){
		while(true){
			
			System.out.println("run():"+Thread.currentThread().getName());
		}
	};
}

运行结构截图:

程序分析:main()方法执行完 new ThreadDemo3().start(); 这条语句后,main方法就就结束了,也就是main方法所在的main线程就结束了。注意:虽然main线程结束了,但是java程序并没有结束,因为前台线程ThreadDemo3 还在执行。此程序说明:java程序中,只要还有一个前台线程在运行,那么java程序都不会结束(虽然main线程已经结束了)。


3.2 TestThread4.java

public class TestThread4 {
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Thread tt = new ThreadDemo4();
		tt.setDaemon(true);//设置线程为 守护线程
		tt.start();

	}	
	

}

class ThreadDemo4 extends Thread{
	
	public void run(){
		while(true){
			
			System.out.println("run():"+Thread.currentThread().getName());
		}
	};
}

运行结果截图:


程序结果分析:ThreadDemo4为守护线程,java程序中只有守护线程时,java程序马上结束。


4. TestThread5.java (解析 tt.join() , tt.join(10000) 的意义)

public class TestThread5 {

	public static void main(String[] args) {
		
		ThreadDemo5 tt = new ThreadDemo5();
		tt.start();
		
		int index = 0 ;
		
		while(true){
			
			if (index ++ == 100) {
				try {
					<span style="color:#cc0000;">tt.join();</span>
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			
			System.out.println("main():"+Thread.currentThread().getName());
		}

	}e.printStackTrace();

}

class ThreadDemo5 extends Thread{
	
		public void run(){
			while(true){
				
				System.out.println("run():"+Thread.currentThread().getName());
			}
		};
		
}

程序分析:

1. tt.join() .  运行结果是: 刚开始 ThreadDemo5 tt线程 和 main线程交替运行,当main线程的 index 达到100时 , 因为tt.join,则表示 tt线程加入main线程,即tt线程和main线程合并成一个新的单线程。程序运行结果是: 刚开始 thread-0 , main 交替 ,  当main线程的 index 达到100时,一直是 thread-0 到最后。 因为 当main线程的 index 达到100时,tt线程已经加入main线程,变成一个单线程,所以执行到tt线程时,要等到tt线程执行完才继续执行main线程。

2. tt.join(10000) . 运行结果是:刚开始 tt线程和main线程交替运行,当main线程的index 达到100时,则tt线程加入main线程成为一个新的单线程(10秒),10秒过后,tt线程和main线程再次各自成为单独的线程,形成刚开始的多线程。即:tt.join(10000) 表示:tt线程加入main线程形成新的单线程10秒钟。


二) . 用 实现Runnable 接口的方式创建线程

class ThreadDemo implements Runnable { public void run(){}};

main(){  Thread tt = new Thread(new ThreadDemo()); tt.start(); }

三). 两种方式创建 线程的区别

eg: 火车站卖票100张,分四个窗口同时卖。比较使用extends Thread 和 implements Runnable 的区别。

对比结果:推荐使用Runnable ,几乎所有要实现多线程的程序,都推荐使用implements Runnable。

1. 使用extends Thread,java程序会默认尽量使用1个thread来卖票。

2. 使用 implements Runnable , java 程序会自动调用4个thread来卖票。 

4.1 TestThread6.java

package com.thread;

public class TestThread6 {

	
	public static void main(String[] args) {
		
		new ThreadDemo6().start();
		new ThreadDemo6().start();
		new ThreadDemo6().start();
		new ThreadDemo6().start();
	}

}

class ThreadDemo6 /*implements Runnable*/ extends Thread{
	
	int tickets = 100 ;
		public void run(){
			while(tickets>0){
			//	if(tickets>0)
				{
					System.out.println("run():"+Thread.currentThread().getName()+"is saling ticket "+tickets--);
				}
				
			}
		};
		
}

运行结果:4个线程分别各卖100张票。

4.2 TestThread7.java

package com.thread;

public class TestThread7 {

	public static void main(String[] args) {
		ThreadDemo7 t = new ThreadDemo7();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		
	}

}

class ThreadDemo7 implements Runnable{
	
	int tickets = 100 ;
		public void run(){
			while(tickets>0){
			//	if(tickets>0)
				{
					System.out.println("run():"+Thread.currentThread().getName()+"is saling ticket "+tickets--);
				}
				
			}
		};
		
}

运行结果:


---------------------------------------------------------------------------------------------------------------------------

三. 线程同步

线程同步的方法:使用synchronized 来实现 要确保线程安全的代码块 的原子性操作。

先看一段线程不安全的代码,以卖票100张为例。本例中4个线程卖100张票,则可能会打印出0和负数。正常应该是(100~1).

出现异常的原因是:当线程1执行到 while(tickets>0)这一句时,当时tickets = 1 ,这时操作系统突然切换到线程2(线程1并没有执行system.out.print(tickets--)),线程2此时判断tickets=1,并打印了“卖出票1”,tickets-- 变成了 0 ,这时 操作系统又切换到线程1让它继续执行system.out....(tickets--) ,打印出 "卖出票0" 。 同理,若线程3之前执行到"while(tickets>0)”时当时tickets=2,还没有执行system.out()这一句就被操作系统切换走了,而后来当又切换到线程3执行 system.out.print(tickets--) 时,tickets 可能当时已经变为-1了,所以,线程3 打印“卖出票-1” , 同理,可以退出打印“卖出票-2” 的情况。

分析原因:

因为 while(tickets>0)这一句 没有 和 循环体内的 代码块{system.out.println(tickets--)} 保持同步操作。即应该保持while(tickets>0)和{system.out.println(tickets--)}代码块操作的原子性。

注意:Thread.sleep(10) 这一句只是为了模拟出更容易出现线程不安全的状况,不加这一句,程序也是线程不安全的。

5.1 TestThread8.java

package com.thread;

public class TestThread8 {
	
	public static void main(String[] args) {
	
			ThreadDemo8 t = new ThreadDemo8();
			new Thread(t).start();
			new Thread(t).start();
			new Thread(t).start();
			new Thread(t).start();
			
		}

	}

	class ThreadDemo8 implements Runnable{
		
		int tickets = 100 ;
			public void run(){
				
				while(tickets>0){					
						
					/*线程休眠10毫秒,模拟出线程不安全的情况出现。不加sleep()也是线程不安全的,加了sleep()则不安全的结果会更容易出现。*/
					try{Thread.sleep(10);}catch(Exception e){e.printStackTrace();};
					System.out.println("run():"+Thread.currentThread().getName()+"is saling ticket "+tickets--);
					
					
				}
			}
			
	}

结果截图:


为了确保while(tickets>0)和{system.out.println(tickets--)}代码块操作的原子性,用synchronized(对象)来实现,看下面程序。

5.2 TestThread9.java

package com.thread;

public class TestThread9 {

	
	public static void main(String[] args) {
		ThreadDemo9 t = new ThreadDemo9();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		
	}

}

class ThreadDemo9 implements Runnable{
	
	int tickets = 100 ;
	String str = "";
		public void run(){
			
			<span style="color:#ff0000;">synchronized (str)</span> { /* <span style="color:#ff0000;">synchronized (任意对象)可实现代码块的原子性*/</span>
				
				while(tickets>0){						
					//线程休眠10毫秒,模拟出线程不安全的情况出现。不加sleep()也是线程不安全的,加了sleep()则不安全的结果会更容易出现。
					try{Thread.sleep(10);}catch(Exception e){e.printStackTrace();};
					System.out.println("run():"+Thread.currentThread().getName()+"is saling ticket "+tickets--);			
				}
			}
			
		}
		
}

运行结果:

注意 :用synchronized(任一对象名){代码块} ,可实现代码块原子性操作,确保代码块的同步。因为每一个对象都有一个标志位,0或者1. 

当str标志位为1时,线程进入代码块,str标志位立刻变为0,变为阻塞状态,其他线程就被阻塞。直到synchronized代码块执行完,str的标志位变为1,才会取消阻塞状态,java程序才能切换到其他线程执行。

str的标志位又称:监视器的锁旗标。

当其他线程访问到str监视器时,若str监视器的锁旗标为0,则其他线程将会进入一个因str监视器锁旗标而产生的等待线程池中。直到str监视器的锁旗标变为1时,才可能享有str监视器的锁旗标。

注意:String str = "" ; 要放在run()方法外。

还可以使用同步函数达到同步效果,看代码:

5.3 TestThread10.java

package com.thread;

public class TestThread10 {

	
	public static void main(String[] args) {
		ThreadDemo10 t = new ThreadDemo10();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		
	}

}

class ThreadDemo10 implements Runnable{
	
	int tickets = 100 ;
	String str = "";
	public void run(){
		sale();
		
	}
	public <span style="color:#ff0000;">synchronized</span> void sale(){
		
		while(tickets>0){						
			//线程休眠10毫秒,模拟出线程不安全的情况出现。不加sleep()也是线程不安全的,加了sleep()则不安全的结果会更容易出现。
			try{Thread.sleep(10);}catch(Exception e){e.printStackTrace();};
			System.out.println("run():"+Thread.currentThread().getName()+"is saling ticket "+tickets--);			
		}
		
	}
		
}

注意: 在 方法名前加上 synchronized 关键字,则 sale()方法是线程安全的,监视器是 this 对象。

即可使用同步代码块来实现线程之间的同步,也可以使用同步函数来实现线程之间的同步。

怎样使得同步代码块和同步函数保持同步? 使用同一个监视器this即可。看下面的程序。

5.4 TestThread11.java

package com.thread;

public class TestThread11 {
	
	public static void main(String[] args) {
		
		ThreadDemo11 t = new ThreadDemo11();				
		new Thread(t).start();
		try { Thread.sleep(1);} catch (Exception e) { e.printStackTrace();}
		t.str = "method";		
		new Thread(t).start();
		
	}

}

class ThreadDemo11 implements Runnable{
	
	int tickets = 100 ;
	String str = "";
	public void run(){
		
		if (str.equals("method")) {
			sale();
		}else {
			
			synchronized (<span style="color:#ff0000;">this</span>) {
				
				while(tickets>0){						
					//线程休眠10毫秒,模拟出线程不安全的情况出现。不加sleep()也是线程不安全的,加了sleep()则不安全的结果会更容易出现。
					try{Thread.sleep(10);}catch(Exception e){e.printStackTrace();};
					System.out.println("run():"+Thread.currentThread().getName()+"is saling ticket "+tickets--);			
				}
			}
			
		}
		
		
	}
	public synchronized void sale(){
		
		while(tickets>0){						
			//线程休眠10毫秒,模拟出线程不安全的情况出现。不加sleep()也是线程不安全的,加了sleep()则不安全的结果会更容易出现。
	//		try{Thread.sleep(10);}catch(Exception e){e.printStackTrace();};
			System.out.print("sale-");
			System.out.println("run():"+Thread.currentThread().getName()+"is saling ticket "+tickets--);			
		}
		
	}
		
}

注意:让同步代码块 和 同步函数同步 的方法:让他们共用一个监视器。本例中共同的监视器是this对象,不能是str。因为同步函数的监视器是this对象,所以必须是的同步代码块的监视器也为this对象。synchronized (this) 是正确的,若写成 synchronized(str) ,则同步代码块和同步函数不能同步。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值