------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执行权。
最后,为了方便,可以多用匿名线程。