黑马程序员————线程学习

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

线程知识总结

一、什么是线程?

1、线程是组成进程(也就是一个个正在执行的程序)的小单元,具有原子性,不可拆分。

2、线程控制进程的执行,一个进程至少有一个线程(main主线程)

3、进程只要一启动就会分配空间,而真正执行的是线程。

二、创建线程的方法

1.继承Thread类,复写run()方法。

2.实现Runnable接口类,复写run()方法。

注意:一般最好采用实现Runnable接口,因为继承是单继承,如果实现了,那么还能继承其他类。

run()方法的作用:

就是将你要执行的代码存储到这里,那么这部分代码就会被新开启的线程执行。



三、启动线程

先创建一个线程,然后启动它,即调用start方法。

如:MyThread mt=new MyThread();

mt.start();

第二种:

New Thread(new Runnable()).start();


四、线程怎么体现?

当多个线程在执行时,都在抢劫CPU资源,谁抢到了,就执行谁。没有必须执行某一个线程。


五、线程的几种状态

1.被创建------------->等待被start();

2.运行----------->抢劫CPU资源

3.临时状态------->具备运行资格,但不具备执行权(就是当该线程sleep后被唤醒了,但是它并没有马上抢到CPU资源,因此阻塞)

4.冻结状态-------->放弃执行权(该线程被sleep(time) 或 wait,除非时间到,或被唤醒,否则一直处于这种休眠状态)

5.消亡------->当run方法执行完毕


六、多线程安全问题

问题:当共享数据时,多个线程同时操作同样的数据,那么这个数据到底该怎么变化呢?所以就会造成共享数据错误。

解决办法:如果多个线程共享同一组数据,那么我们就想让单个线程执行完对数据的操作后,再执行其他线程。------------------同步代码块 


七、线程同步

·同步代码块Synchronized(对象){

需要被同步的代码

}

·同步函数   Synchroinzed写在一个方法的修饰符之前。

  对象的作用

在这里相当于一把锁,只要拿到锁的线程,才能执行,那么这时其他线程无权执行代码。

  需要同步代码

有哪些语句操作了共享数据,那么它就是需要被同步的代码。

 什么是锁:每个线程都只认这把锁,当它被其他线程拿着时,你因为没有持有锁,因此不能执行代码。

同步函数的锁是什么呢?是this,因为方法一定会被对象调用,所以函数就有一个所属对象 的引用了。

那么静态函数没有对象调用的话,锁又是什么呢?该类对应的字节码文件对象。静态函数加载进内存后,内存中并没有本类对象,但一定有字节码文件,因此拿它当作锁是比较好的选择。

八、线程典型例子

单例设计模式---------饿汉式

class Single{
	private static Single s=null;//静态共享数据
	private Single(){};//构造函数私有化
	
	public static Single getInstance(){//双重判断,提高效率
		if(s==null){
			synchronized(Single.class){
			 if(s==null){
				 s=new Single();
			 }
			}
		}
		return s;
	}
}

 

九、线程死锁问题

如何会出现死锁?同步中嵌套同步,但持有锁不同。也就是说,你持有a锁,我持有b锁,你想我请求b锁,我同时也想你请求a锁,就这样僵持不下,造成程序的死锁。

代码举例:

if(flag)
		{
			synchronized(MyLock.loacka){
				system.out.println("if  locka");
				synchronized(MyLock.lockb){
				   system.out.println("if lockb")
				}
			}

		}
		}else{
			synchronized(MyLock.loackb){
				system.out.println("else  lockb");
				synchronized(MyLock.locka){
				   system.out.println("else locka")
				}
			}

		}

那么怎么解决死锁?知道了怎么造成死锁的,我们就知道避免它。

第一、同步中尽量不要使用同步代码块嵌套。

第二、尽量不使用同一把锁。

第三、尽量使用tryLock(long timeout,TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时,超时可以退出,防止死锁。

第四、尽量使用java.util.concurrnet包的包的并发类代替手写控制并发。



十、线程间通信

线程间通信其实就是多个线程操作同一组资源。

经典问题:生产者---消费者(商品)

分析:使用同一组资源,生产者生产商品,消费者消费此商品。

  (①简单情况)当只用一个线程负责生产,一个线程负责消费,那么需要做这样的处理:当生产完一个商品,立刻让消费者线程去消费它,然后又到生产者线程生产一个,消费者线程消费一个。。。。。如此循环。

那么怎么控制线程相互交替呢?我们知道wait(),notify();

当一个线程wait时,就会释放资源,释放锁。那么另一个线程得到资源就会开始执行,执行完毕后notify一下,能唤醒刚才的线程,那它就会开始执行。------所以这样的方法是的生产和消费交互执行。

补充:wait(),notify(),notifyAll()这些方法都在Object中。

  1、这些方法存在同步中。

  2、使用这些方法必须标识所属的同步的锁

  3、锁可以是任意对象,因此任意对象能调用的方法一定定义在Object中。


 wait() 和sleep()区别:

 sleep()释放资源,不释放锁

 wait()释放资源,释放锁。

只用同一个锁上的被等待线程能被同一个锁上的notify唤醒。等待和唤醒必须是同一把锁,但锁是任意的。


代码:

public class InputOuptutDemo {
	public static void main(String[] args){
		Resc r=new Resc();
		Input in=new Input(r);
		Output out=new Output(r);
		new Thread(in).start();
		new Thread(out).start();
	}
}
class Resc
{
	private String name;
	private String numId;
	boolean flag=true;//标记仓库是否有商品
	public synchronized void in(String name,String numId)
	{
		if(flag)//如果仓库已有商品,那么就wait,否则生产一件商品
			try{this.wait();}catch(Exception e){}
		this.name=name;
		this.numId=numId;
		flag=true;//当商品生产完,仓库有商品,flag设置为true
		this.notify();//唤醒另一个线程
		
	}
	public synchronized void out(){
		if(!flag)//如果仓库没有商品,就等待,否则就输出商品
			try{this.wait();}catch(Exception e){}
		System.out.println("name:"+name+"   numId:"+numId);
		flag=false;//消费完商品,设置仓库为false
		this.notify();
	}
}

class Input implements Runnable{

	private Resc r;//资源就共享,静态消耗内存,用引用
	Object obj=new Object();
	Input(Resc r){
		this.r=r;
	}
	public void run() {
		int x=0;
		while(true){
			if(x==0)
				r.in("电脑","一号" );
			else
				r.in("book", "002");
			x=(x+1)%2;//交替生产
		}
	}
}
class Output implements Runnable{
	private Resc r;//资源就共享,静态消耗内存,用引用
	Object obj=new Object();
	Output(Resc r){
		this.r=r;
	}
	public void run() {
		while(true){
			r.out();
		}
	}
	
}

(②模拟现实)多个线程负责生产,多个线程负责消费。JDK1.5新特性:将同步synchronized替换成现实lock操作。将Object中的wait,notigy notifyAll替换了Condition对象。该对象可以通过Lock进行获取。实现了本方值唤醒对方的操作。

实例代码:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ProducerConsumerDemo {
	public static void main(String[] args){
	Resc r=new Resc();
	Producer pro=new Producer(r); 
	Customer cus=new Customer(r);
	new Thread(pro).start();//2个线程负责生产
	new Thread(pro).start();
	
	new Thread(cus).start();//2个线程负责消费
	new Thread(cus).start();
	
	}
}

class Resc {
	String name;
	int count = 1;
	boolean flag = true;

	Lock lock = new ReentrantLock();// 实例化一个锁
	Condition condition_con = lock.newCondition();// 定义锁机制
	Condition condition_pro = lock.newCondition();

	//生产者生产商品
	public void set(String name) {
		lock.lock();// 拿到锁
		try {
			while (flag) {
				condition_pro.await();
			}
			this.name=name+"...."+count++;
			System.out.println(Thread.currentThread().getName()+"....生产者..."+this.name);
			flag=true;//已经有商品
			condition_con.signal();//唤醒对方线程
		}catch(Exception e){
			e.printStackTrace();
			}
		finally{
			lock.unlock();//释放锁资源
		}
	}
	
	public void out(){
		lock.lock();
		try{
			while(!flag){
				condition_con.await();
			}
			System.out.println(Thread.currentThread().getName()+"。。。。消费者..."+this.name);
			flag=false;//商品消费完了
			condition_pro.signal();//唤起对方线程
		}catch(Exception e){
			e.printStackTrace();
		}finally{//一定要释放资源。
			lock.unlock();
		}
	}
}

class Producer implements Runnable{
	private Resc r;//共享资源
	Producer(Resc r){
	this.r=r;
	}
	public void run(){
		while(true){
			r.set("商品");
		}
	}
}
class Customer implements Runnable{
	private Resc r;//共享资源
	Customer(Resc r){
	this.r=r;
	}
	public void run(){
		while(true){
			r.out();
		}
	}
}

总结:

线程间通信注意事项:共享资源一定要线程同步。用while循环语句判断,这样避免了某个线程被唤醒后直接执行,仍然应该判断一下是否有商品存在。唤醒线程时,可以用notifyAll()或signalAll(),这样可以避免死锁。

  

 十一、停止线程

线程是运行在run方法中的代码,只要让run方法中代码执行完,线程就会停止,我们发现run方法中经常是while循环,因此只要控制循环的结束,线程也就停止了。

特殊:当线程处在冻结状态时,是不能读取标记的,那么就不能停止了。

java中提供了一个方法interrupt();该方法可以是冻结的线程返回到运行状态。


十二、线程的一些方法

守护线程:在后台运行的线程,当前台线程运行完毕后,后台线程自动结束。

setDaemon();定义守护线程。必须在线程运行之前进行定义


join方法是临时加入一个线程,当这个线程执行完后,释放cup,其他线程抢劫cup执行权。


最后,为了方便,可以多用匿名线程。






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值