黑马程序员 多线程学习

----------- android培训java培训、期待与您交流! ------------

线程概念

进程:正在进行中的程序
线程:进程中的多条执行路径


线程控制着进程的执行进度。
一个进程中至少有一个线程(主线程)。


多线程的一个特性:随机性。
每个线程都获取CPU的执行权,到底谁执行、执行多久由CPU来决定。多个线程之间交替执行。
 
启动线程的唯一方法是:线程类对象.start();
用于开启线程并执行线程中的run();
 
线程类的run()方法,用于封装要运行的代码。

线程创建

下面是两种创建线程的方式:

public class MultiThread {

	public static void main(String[] args) {
		//创建并启动线程一
		//new Demo().start();
		
		//创建并启动线程二
		new Thread(new Demo2()).start();
		
		//主线程执行程序
		for(int i=0; i<1000; i++) {
			System.out.println(Thread.currentThread().getName() + ": The world " + i);
		}
	}
}
//定义线程的第一种方法:继承自Thread类
class Demo extends Thread {
	public void run() {	//封装要运行的代码
		for(int i=0; i<1000; i++) {
			System.out.println(Thread.currentThread().getName() + ": Hello java " + i);
		}
	}
}
//定义线程的第二种方法:实现Runnable接口
class Demo2 implements Runnable {
	public void run() {	//封装要运行的代码
		for(int i=0; i<1000; i++) {
			System.out.println(Thread.currentThread().getName() + ": Hello java " + i);
		}
	}
}

 

实现方式和继承方式的区别?
1、继承Thread,线程运行代码存放在Thread子类run方法中

实现Runnable,线程运行代码存放在Runnable接口子类run方法中

2、实现方式避免了java单继承的局限性。


在定义线程类时,建议使用实现方式

 

线程状态


线程的状态示意图:

多窗口卖票示例:

(若继承Thread,则Ticket的ticket属性应为static)

public class SaleTicket {

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

}

class Ticket implements Runnable {
	private int ticket = 100;
	public void run() {
		while(ticket > 0) {
			System.out.println(Thread.currentThread().getName() + "sale : " + ticket--);
		}
	}
}


多线程安全问题

问题出现的状况:

        当多个线程在操作同一个共享数据时,一个线程的多条语句中只执行了一部分,CPU执行权即分配到了其它的线程中执行。导致共享数据错误。

解决办法:

        单个线程在操作共享数据时,其它线程不能参与进来。

 

java用的1、同步代码块来实现安全:

synchronized(对象) {

       需要被同步的语句;

}

对象如同锁,没有锁的线程就算取得了CPU的执行权也不能执行。

线程加锁带来的弊端:要有锁对象,所以耗资源;要判断锁,所以效率稍减。

 

 2、同步函数

public synchronized void method(Type args) {

      需要被同步的语句; 

}

同步非静态函数用的锁是this。

如果同步静态函数:所用的锁不是this,因为静态方法中不能出现this。

用的是 类名.class是Class类型对象。

 

 

如果一个程序中有安全问题,使用同步时应注意:

1、明确多线程运行代码(一般为run方法里调用的语句,以及其附带语句(调用了其它的方法))有哪些 2、明确共享数据为何 3、明确运行代码中有哪些语句操作共享数据

 

使用同步还会出安全问题:

此时定为同步的两个前提条件中的一个不满足所造成:1、必须有两个或两个以上的线程 2、必须是多个线程使用同一把锁。

 

 

单例设计模式

饿汉式和懒汉式有什么区别?

1、懒汉式特点:实例的延时加载。

2、懒汉式在多线程访问时存在安全问题(为什么?答:获取实例的方法中有多条语句操作共享资源),如何解决?

答:用同步代码块和同步函数都能解决此问题,但效率稍低;可以用双重判断来减少判断锁的次数。

3、懒汉式加同步时使用的锁是哪个?

答:该类所属的字节码文件对象。

  

class ESingle {	//饿汉式
	private static final ESingle es = new ESingle();
	private ESingle() {}
	public static ESingle getInstance() {
		return es;
	}
}

class LSingle {	//懒汉式
	private static LSingle ls;
	private LSingle() {}
	public static LSingle getInstance() {
		if(ls == null) {	//双重判断减少对锁的判断次数
			synchronized(LSingle.class) {
				if(ls == null) {
					ls = new LSingle();
				}
			}
		}
		return ls;
	}
}


 

 死锁

死锁的产生:同步中嵌套同步,而使用的锁不一样。

死锁代码示例:

public class DeadLock {

	public static void main(String[] args) {
		Test a = new Test(true);
		Test b = new Test(false);
		Thread t1 = new Thread(a);
		Thread t2 = new Thread(b);
		t1.start();
		t2.start();
	}
}

class Test implements Runnable {
	private boolean flag;
	Test(boolean flag) {
		this.flag = flag;
	}
	
	public void run() {
		if(flag) {
			synchronized(MyLock.locka) {
				System.out.println("if...locka");
				synchronized(MyLock.lockb) {
					System.out.println("if...lockb");
				}
			}
		}
		else {
			synchronized(MyLock.lockb) {
				System.out.println("else...lockb");
				synchronized(MyLock.locka) {
					System.out.println("else...locka");
				}
			}
		}
	}
}
class MyLock {
	static Object locka = new Object();
	static Object lockb = new Object();
}


 

多线程通信

多线程通信:即多个线程操作同一个资源,但操作的动作不同。

等待唤醒机制:

public class ThreadCommunication{

	public static void main(String[] args) {
		Res r = new Res();
		new Thread(new Input(r)).start();
		new Thread(new Output(r)).start();
	}

}
//共享资源
class Res {
	private String name;
	private String sex;
	private boolean flag = false;
	//设置方法
	public synchronized void set(String name, String sex){
		if(flag)//flag=true表示设置过的还未打印,有数据则等待
			try{this.wait();}catch(InterruptedException e){e.printStackTrace();}
		this.name = name;
		this.sex = sex;
		flag = true;
		this.notify();
	}
	//打印方法
	public synchronized void out(){
		if(!flag)
			try{this.wait();}catch(InterruptedException e){e.printStackTrace();}
		System.out.println(name + ".........." + sex);
		flag = false;
		this.notify();
	}
}
//输入线程
class Input implements Runnable{
	private Res r;
	Input(Res r) {
		this.r = r;
	}
	public void run() {
		int x = 0;
		while(true) {
			if(x==0) {
				r.set("lisi", "man");					
			}
			else {
				r.set("丽丽", "女女女女");					
			}
			x = (x+1)%2;//控制x在0和1之间不断交替,从而让设置的内容不同。
		}
	}
}
//输出线程
class Output implements Runnable{
	private Res r;
	Output(Res r) {
		this.r = r;
	}
	public void run() {
		while(true) {
			r.out();
		}
	}
}

 

在线程池里等待的线程,往往先唤醒的是第一个。

wait()、notify()、notifyAll()。当前线程必须拥有此对象监视器(即锁),锁只有在同步中才有。

上述方法为何被定义在Object类中?

答:上述方法由锁调用,锁可以是任意对象,所以定义在任意对象的父类Object中。

wait()和sleep()有何区别?

答:wait()释放资源、释放锁;sleep()释放资源、不释放锁。

等待和唤醒的必须是同一个锁。而锁可以是任意对象,所以定义在Object中。

 

 

Lock接口和Condition接口

JDK1.5中提供了替代同步中隐式锁的synchronized为显式锁方式Lock接口和Condition接口
 * 将Object中的wait()、notify()、notifyAll()替换成了Condition对象的方法。
 * Condition对象可以由Lock获取
 * 实现了本方只会唤醒对方的操作。
 *
 * 生产者消费者问题有什么替代方案?
 * 答:1.5版本时,提供了显式的锁机制,以及显式的锁对象上的等待唤醒操作机制
 * 一个Lock锁,对应了多个Condition

public class ProducerConsumerJDK5 {

	public static void main(String[] args) {
		Resource res = new Resource();
		Producer p1 = new Producer(res);
		Consumer c1 = new Consumer(res);
		
		Thread t1 = new Thread(p1);
		Thread t2 = new Thread(p1);
		Thread t3 = new Thread(c1);
		Thread t4 = new Thread(c1);
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		
	}

}

class Resource {
	private String name;
	private int count =1;
	boolean flag = false;
	Lock lock = new ReentrantLock();
	Condition condition_pro = lock.newCondition();
	Condition condition_con = lock.newCondition();
	
	public void set(String name) throws Exception {
		lock.lock();
		try {
			while(flag) {
				condition_pro.await();//用while循环判断生产标记,让被唤醒的线程再次判断标记,确保不会出现生产一个而消费两个的情况
			}
			this.name = name + "..." + count++;
			//让生产进度变慢,消费进度也随之变慢
			Thread.sleep(100);
			System.out.println(Thread.currentThread().getName() +" 生产 "+ this.name);
			flag = true;//生产后将生产标记设为true,意为已经生产一个了,快来拿走。
			condition_con.signal();//激活对方线程。
		} finally {
			lock.unlock();//释放锁的动作一定要执行
		}
	}
	
	public void out() throws Exception {
		lock.lock();
		try {
			while(!flag) {
				condition_con.await();
			}
			System.out.println(Thread.currentThread().getName() +" 消费 "+ this.name);
			flag = false;
			condition_pro.signal();
		} finally {
			lock.unlock();
		  }
		}
		
	}

class Producer implements Runnable {
	private Resource res;
	public Producer(Resource res) {
		this.res = res;
	}
	public void run() {
		while(true) {	//不停的生产
			try {
				res.set("包子");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

class Consumer implements Runnable {
	private Resource res;
	public Consumer(Resource res) {
		this.res = res;
	}
	public void run() {
		while(true) {
			try {
				res.out();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

 

线程方法

让线程停止

1、直接结束run()方法。

2、如何线程调用了Object的wait()或Thread的join()、sleep()方法使线程进入阻塞状态时;

可以通过Interrupt()来终止线程的阻塞状态,从而转至就绪状态,此时会抛出InterruptedException,在对其抛出的异常处理中结束run()方法的运行。

(用毕老的一个例子来描述:一砖头拍在了一个正在打瞌睡的童鞋身上,他...受伤了...)

setDaemon(true):设置线程为守护线程(也可将其称为后台线程)。

当正在运行的线程都是守护线程时,jvm即退出。

该方法必须在启动线程前调用。

 

前台线程和后台线程:在执行过程中没有区别(同样抢占cpu的执行权),只在结束时有区别。

守护线程依赖于主线程;当主线程结束后,所有守护线程自动结束

主线程是前台线程。

 

join(),临时加入线程;当A线程执行到了B线程的B.join()方法,A线程就等待,直到B线程结束后,A线程才从阻塞状态回到就绪状态。

一般使用方式:当满足一定的条件时,让某一线程加入进来。

 

这里的main是线程组名(Thread-1由main开启),5是线程优先级(抢CPU的频率,默认为5,共10级),Thread-1是当前线程。

ThreadGroup,可以让程序员自己创建线程组。(几乎用不到)

 

setPriority(Thread.MAX_PRIORITY)设置优先级最高

Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。

如果有两个线程操作同一段代码,代码中加入Thread.yield(),运行的效果类似是a线程执行一次b线程执行一次,均匀交替的执行。

 

何时使用线程?

当某些代码需要同时执行时,就用线程进行封装。

 

class MultiThread {
        public static void main(String[] args) {
        	//第一个线程
        	new Thread() {
        		public void run() {
        			for(int i=0; i<1000; i++) {
        				System.out.println(Thread.currentThread().getName()  + " " + i);
        			}
        		}
        	}.start();
        	//第二个线程
        	Runnable r = new Runnable() {
        		public void run() {
        			for(int i=0; i<1000; i++) {
        				System.out.println(Thread.currentThread().getName()  + " " + i);
        			}
        		}
        	};
        	new Thread(r).start();
        	//第三个线程(依附主线程)
        	for(int i=0; i<1000; i++) {
        		System.out.println(Thread.currentThread().getName() + " " + i);
        	}
        }
}
                                        -----------------------  android培训java培训、期待与您交流! ----------------------

                                详情请查看:http://edu.csdn.net/heima


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值