进程和线程的例子很多,因为是学习笔记,那就说一种我自己感觉很好理解的,就是我们天天使用的QQ,但我们运行QQ.exe程序时,进程就开始,我们可以同时打开对个聊天窗口,可以多人视频过,甚至可以一边视频一边手动聊天(可能音响坏了吧 。。。),其中每一个任务完全可以理解成是“线程”在工作,传音乐,图片表情,传文件等功能都有对应的线程在后台默默运行。
在回答上面的问题之前;我们首先要认识一个类和一个借口,Thread类和Runnable借口。实现多线程编程的方法有两种
1)继承Thread类(java的特点就是单继承,如果一个类既要继承别的类,还想实现线程,那么用Thread类肯定做不到)
2)实现Runnable借口(支持“多继承”就靠Runnable借口了,一边实现一边继承,但不过是那种方法创建的线程工作的性质是一样的)
让我们先来看看源码Thread类的结构(其实Thread类也是实现的Runnable借口)
public class Thread implements Runnable
下面用一段简单的代码实现一个使用继承Thread类的线程
package test;
public class ThreadDemo01 {
	public static void main(String[] args) {
		
		TestThread tt=new TestThread();//创建线程对象
		tt.start();//激活一个线程
		for(int i=0;i<5;i++){
			System.out.println("main线程正在运行");
			try {
				Thread.sleep(2000);//睡眠2s
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
	}
	
}
class TestThread extends Thread{
	
	 public void run(){//重写父类run方法
		 for(int i=0;i<5;i++){
			 System.out.println("TestThread正在运行");
			 try {
				Thread.sleep(1000);//睡眠1秒
			} catch (Exception ex) {
				// TODO: handle exception
				ex.printStackTrace();
			}
			 
		 }
		 
	 }
	
}
从上面的代码需要注意的是:1)进程Thread类的类必须从写run方法 。2)激活一个线程使用的是Start方法(实际是改变了线程的状态)
3)如果你多运行两遍这个程序你会发现结果不一样,这体现了多线程执行顺序是不确定的(主要看CPU时间片的长短)
实现Runnable借口
直接上代码
package test;
public class ThreadDemo02 {
	public static void main(String[] args) {
		TestThread tt=new TestThread();
		new Thread(tt).start();//激活一个线程
		for(int i=0;i<5;i++){
			System.out.println("main正在运行");
			try {
				Thread.sleep(1000);
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
	}
	
	
}
 class TestThread02 implements Runnable {
	@Override
	public void run() {
		for(int i=0;i<5;i++){
			System.out.println("TestThread正在运行");
			try {
			Thread.sleep(1000);
				
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		
	}
	 
 }
实现Runnable借口需要注意:1)我虽然实现了Runnable借口,但用的仍然是Thread类的Start方法激活线程(原因在于查看jdk文档得知Runnable只有一个run方法没有其他方法)2)Thread类的构造函数 public Thread(Runnable target)可以将借口的实现类对象作为参数,实例化出一个Thread类的对象
自定义线程类中的实例变量针对其他线程可以有共享和不共享之分。这也是多线程交互时很重要的知识点。
这里举一个买票的例子,火车票有很多的代受点,现在要求四个代受点共要卖出去5张票,
方案1 使用继承Thread类的方法
package test;
public class ThreadDemo03 {
	 public static void main(String[] args) {
		 TestThread03 t=new TestThread03();
//一个线程对象对象只能启动一个线程
		t.start();
		t.start();
		t.start();
		t.start();
	}
	 
}
class TestThread03 extends Thread{
	private  int i=5;
	public void run(){
		while(i>0){
			System.out.println(Thread.currentThread().getName()+"出售票"+i);
			i-=1;
		}
	}
}
执行一下会发现报了异常java.lang.IllegalThreadStateException这就说明这种写法不能起到“票源共享”,也说明一个对象只能启动一个线程
再来看方案2
package test;
public class ThreadDemo03 {
	 public static void main(String[] args) {
		 //TestThread03 t=new TestThread03();
		 new TestThread03().start();
		 new TestThread03().start();
		 new TestThread03().start();
		 new TestThread03().start();
	}
	 
}
class TestThread03 extends Thread{
	private  int i=5;
	public void run(){
		while(i>0){
			System.out.println(Thread.currentThread().getName()+"出售票"+i);
			i-=1;
		}
	}
}
从执行结果看是四个线程了,但票数不对了,这样看来是每个线程5张票,和我们的初衷4个线程共卖5张票严重不符,那么我们再来做一个小小的改动,将变量i设置成静态变量
方案3
package test;
public class ThreadDemo03 {
	 public static void main(String[] args) {
		 //TestThread03 t=new TestThread03();
		 new TestThread03().start();
		 new TestThread03().start();
		 new TestThread03().start();
		 new TestThread03().start();
	}
	 
}
class TestThread03 extends Thread{
	private static int i=5;
	public void run(){
		while(i>0){
			System.out.println(Thread.currentThread().getName()+"出售票"+i);
			i-=1;
		}
	}
}
从执行的结果看出似乎完成了资源共享的要求,但是我们发现有的票出现了“一票多卖”的现象,这就非线程安全问题,这个稍后解决,先来看看使用Runnable借口怎么完成资源共享
运行如下代码
方案4
package test;
public class ThreadDemo03 {
	 public static void main(String[] args) {
		 TestThread03 t=new TestThread03();
		 new Thread(t).start();
		 new Thread(t).start();
		 new Thread(t).start();
		 new Thread(t).start();
	}
	 
}
class TestThread03 implements Runnable{
	private  int i=5;
	public void run(){
		while(i>0){
			System.out.println(Thread.currentThread().getName()+"出售票"+i);
			i-=1;
		}
	}
}
从方案4和方案3对比来看 使用Runnable借口似乎比使用Thread类有优势
现在讨论一下非线程安全问题
前面的方案3和方案4解决了资源共享问题,但出现了多个线程共卖一张票的问题(原因:假设票数现在是5,线程1看见了,执行了run方法,本应票数减1但是这时线程1的CPU时间片到了,必须退出,换别的线程,这时线程2看见的仍然是i=5的票数,于是它有卖了一遍)
要想解决这个问题实现一票一买必须要引用关键字synchronized(使。。。同步)
同不代码块的语法格式:
synchronized(对象){
需要同步的代码
}
还是先执行代码
package test;
public class ThreadDemo04 {
	
	public static void main(String[] args) {
		TestThread04 t=new TestThread04();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
	}
	
	
}
 class TestThread04 implements Runnable{
	 private int i =5;
	@Override
	public void run() {
	  while(true){
		 synchronized (this) {
			 if(i<=0){
				  break;
			  }
			  System.out.println(Thread.currentThread().getName()+"出售票"+i);
			  i-=1;
		}
	  }
		
	}
	 
 }
使用synchronized代码块的目的就是保证其中内容的原子性(要么不执行,要执行就一定执行完,不存在执行一半中断的情况)
且同一时刻进入临界区(synchronized代码块的)只能有一个线程
不光能使用代码块可也是用synchronized修饰的方法来解决“非线程安全问题”
执行如下代码:
package test;
public class ThreadDemo06 {
	public static void main(String[] args) {
		TestThread06 t=new TestThread06();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
	}
}
class TestThread06 implements Runnable{
	private int i=5;
	@Override
	public void run() {
		
		while(i>0){
			sale();
		}
		
	}
	public synchronized void sale(){
		if(i>0)
		System.out.println(Thread.currentThread().getName()+"出售票"+i);
	
		try {
			Thread.sleep(1000);
		} catch (Exception e) {
			// TODO: handle exception
		}
		i-=1;
	}
	
	
}
待续。。媳妇让我睡觉
原文:http://www.cnblogs.com/zwynb/p/5866948.html