1.什么是线程?
线程是计算机中程序中独立运行的单位,每个java程序程序被启动时,java虚拟机都会创建一个主线程即(main函数)来使程序运行、即我们经常用到的main()函数,不管是在java还是c语言中都可以被理解成是一个线程。
2.线程和进程的区别?
进程:通俗来讲可以和一个程序的概念画上等号,一个程序就是一个进程。
线程:如上面所说是程序中一个独立运行的单位,也便是进程中的一部分,一个进程可以包含很多个线程。
多个进程的内部数据和状态是完全独立的,而一个进程中的多个线程是共享一块内存空间和一组系统资源的,在程序内部可以互相调用,而进程间的通信大多都要通过网络实现。
3.线程的创建
1.写一个java主函数(我觉得这个也是线程创建的一种方式
2.继承java.lang.Thread类
3.实现 java.lang.Runnable接口
4.匿名内部类的形式创建
以上几种方法这边就不在细说,详细请见博客中的线程总结(1)。
4.线程的状态
这个很多资料上都说法不一,有的说三种、有的说四种、也有五种的,要比较完整的话我觉得是五种。
1.创建状态:在创建了线程对象,但没有调用start()方法。
2.就绪状态:调用了start()方法后,就进入了就绪状态,但此时线程调度程序并没有把线程设定为当前线程,即run()方法还没开始运行。或者在线程从等待或者休眠中醒来,此刻线程也处于是就绪状态。
3.运行状态:线程调度程序将线程设定为当前线程,线程开始运行,即run()方法开始运行。
4.等待(阻塞)状态:当线程运行的时候被暂停。如sleep()等方法的调用是运行出现了阻塞的情况。
5.死亡状态:线程run()方法运行结束,该线程就进入死亡状态了。
以上是对线程一些基本知识点的回顾,下面讲的才是此篇博客的重点。
二。线程同步问题的产生、
1.为什么会产生线程同步的问题?
记得我第一次接触到线程同步问题是在听设计模式中的单例模式中碰到的。这里简单说一下单例模式:顾名思义,就是只能有一个例子,即某个类对象中只能产生一个对象。这样的类要怎么去编写?
当时用的是一种叫做懒汉式的方法,代码如下:
public static Student getStudent() { if (stu == null) { stu = new Student(); } return stu; }
分析:当student对象不为null时我们就new一个student对象,这个做法貌似看起来可行,但这样是否万无一失呢?
是的,在单线程中这样的单例模式是可行的,但多线程中是不可行的,当两个线程同时去访问这个判断条件的时候,或者说在student对象还没有被创建的时候有多个线程同时进入到了这个判断条件,那么这个条件显然是成立的,此时的student对象为null那么多个线程调用这个方法的时候也就产生了多个student对象,已经违反了单例模式的原则。
所以从上面的例子我们可以看出产生线程同步的问题的原因:就是多个线程之间去共享一个资源。如果线程之间没有共享的资源,同步问题也就不存在。
线程同步的问题还有很多的例子:比如对一个全局变量的处理,就拿整形数据i来讲,
在多个线程中同时对这个数据去处理,比如在一个线程中对它进行i++,在另外一个线程中进行i--,那么最后i的值将是无法预料的。因为1.你不知道那个线程先对i处理,这是我们无法控制的,如上面所讲线程运行时受线程调度程序控制的,并不能简单地理解为我们先start了哪一个线程那个线程就先运行。2.你不知道那个线程运行的快,两个线程之间的调用顺序。所以i的结构也就无法预料。这些都是线程的同步问题类似的例子还有很多。
2.如何解决线程同步问题?
就拿上面那个单例模式产生的线程问题来讲,我们可以这么想,要是使这个判断方法不能同时被两个线程或者多个线程同时访问,那么问题是不是就解决了。
那么该如何做到?Java线程正是提供这种机制的。用Synchronized(同步的意思)关键字。有以下两种做法来用这个关键字。
第一种方法:直接在方法前加入synchronized关键字
public static synchronized Student getStudent(){ if(stu==null){ stu=new Student(); } return stu; }
第二种方法:
public static synchronized Student getStu() { synchronized (Student.class) { stu = new Student(); return stu; } }
这样就使这个方法或者是某些代码语句在某个时刻只能被一个线程访问
3.线程中的等待/通知(wait/notify)
线程消费模式:
有这样一个场景,一个手机工厂要生产手机,公司规定只有当库存为0的时候,工厂才能在生产手机,而消费者来买手机当手机公司库存里面有手机的时候他就可以买。以下代码实现。
/** * 手机类 * @author Administrator * */ public class Phone { private String type; public Phone(String type){ this.type=type; } public String toString(){ return type; } }
import java.util.List; public class Producer extends Thread{ //手机库存list private List<Phone> list; private int count=0; public Producer(List<Phone> list){ this.list=list; } public void run(){ while(true){ try { sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } if(list.size()==0){ Phone phone=new Phone("phone"+count); System.out.println("工厂生产了手机"+phone); list.add(phone); count++; } } } }
import java.util.List; /** * 消费者类 * @author Administrator * */ public class Customer extends Thread{ //手机库存list private List<Phone> list; public Customer(List<Phone> list){ this.list=list; } public void run(){ while(true){ System.out.println("消费者在访问"); try { sleep(200); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } if(list.size()>0){ Phone phone=list.remove(0); System.out.println("消费者消费了手机"+phone); } } } }
/** * 主函数测试 * @author Administrator * */ public class Mian { /** * @param args */ public static void main(String[] args) { List<Phone> list=new LinkedList<Phone>(); //创建一个生产线程对象并start Producer producer=new Producer(list); producer.start(); //创建一个消费者线程并start Customer customer=new Customer(list); customer.start(); } }
这个代码可以这样解释:手机工厂和消费者一直在看手机库存,不停地看,工厂发现库里没有手机了就生产一台,消费者看都有手机了就买了一台,这样的做法很显然可以满足上面的需求,但这样消耗内存资源太大了,需要两方同时不停地去查看。从打印中也可以看出来。
我们会有这样的想法,当工厂生产出了一台手机的时候就去通知消费者,消费者就来买手机,买了手机后,消费者也去通知工厂说手机库存没了,此时工厂在去生产手机,除了接收到通知的其他时间工厂和消费者都可以休息。即等待wait,这样资料消耗就减少很多了。这就是非常典型的线程生产消费模型
synchronized(list){ if(list.size()==0){ Phone phone=new Phone("phone"+count); System.out.println("工厂生产了手机"+phone); list.add(phone); count++; //生产完通知消费者 list.notify(); } try { //等待消费者通知 list.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
synchronized(list){ //消费者进入等待 try { list.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } if(list.size()>0){ Phone phone=list.remove(0); System.out.println("消费者消费了手机"+phone); //消费者通知生产这已经消费了 list.notify(); } }
这样从打印中可以看出,当执行wait()的时候,就阻塞了,知道有notify()的通知时候,阻塞才解除。生产者和消费者之间的访问都大大减少,每次都是有效地访问。
注意点:可能都注意到了关键字synchronized括号后面的参数了。这个参数可以这样去理解:它是去绑定不同线程之间的一把锁。两个线程之间的交互是必须用同一把锁的。当wait的时候即相当于这把锁被锁起来了,要去解锁必须是去解这个锁而不是去解另外的锁,即notify也必须要是这个对象去调用。
举个例子:当你上厕所的时候里面有人了,他把门锁着,那么你只能等,当它出来了,把这个锁给解开了,你才能进厕所。要是别的测锁锁开了,你也是进不了这个门的。所以锁和解是必须要对同一个对象而言的。至于什么对象,在能实现这个功能上来讲是没有什么区别的。但是从其他方面,如占用的内存等等,运行的效率来讲就有区别了。这边就不在细究。