进程与线程
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,即进程空间或(虚空间)。进程不依赖于线程而独立存在,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,线程没有自己的虚拟地址空间,与进程内的其他线程一起共享分配给该进程的所有资源。
“同时”执行是人的感觉,在线程之间实际上轮换执行。
进程在执行过程中拥有独立的内存单元,进程有独立的地址空间,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程包含以下内容:
线程共包括以下 5 种状态:1. 新建状态(New):线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
2. 就绪状态(Runnable):也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3. 运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
- (01) 等待阻塞 — 通过调用线程的wait()方法,让线程等待某工作的完成。
- (02) 同步阻塞 — 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
- (03) 其他阻塞 — 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
线程的常用方法
sleep()方法、wait()方法、yeild()方法、interrupt()方法、notify()、notifyAll()方法;
1、sleep()方法:sleep方法为Thread的静态方法;
sleep方法的作用是让线程休眠指定时间,在时间到达时自动恢复线程的执行;
sleep方法不会释放线程锁;
2、wait()方法:
wait方法是Object的方法;
任意一个对象都可以调用wait方法,调用wait方法会将调用者的线程挂起,使该线程进入一个叫waitSet 的等待区域,直到其他线程调用同一个对象的notify
方法才会重新激活调用者;
当wait方法被调用时,它会释放它所占用的锁标记,从而使线程所在对象中的synchronize数据可以被别的线程所使用,
所以wait()方法必须在同步块中使用,notify()和notifyAll()方法都会对对象的“锁标记”进行修改,所以都需要在同步块中进行调用,
如果不在同步块中调用,虽然可以编辑通过,但是运行时会报IllegalMonitorStateException(非法的监控状态异常);
3、yeild()方法:
yeild()方法表示停止当前线程,使该线程进入可执行状态,让同等优先级的线程运行,如果没有同等优先级的线程,那么yeild()方法不
会起作用;
4、notify()和nofityAll()方法;
notify()会通知一个处在wait()状态的线程;如果有多个线程处在wait状态,他会随机唤醒其中一个;
notifyAll()会通知过所有处在wait()状态的线程,具体执行哪一个线程,根据优先级而定;
1)、sleep():强迫一个线程睡眠N毫秒;2)、isAlive():判断一个线程是否存活;
3)、join():线程插队;
4)、activeCount():程序中活跃的线程数;
5)、enumerate():枚举程序中的线程;
6)、currentThread():得到当前线程;
7)、isDeamon():一个线程是否为守护线程;
8)、setName():为线程设置一个名字;
9)、wait():线程等待;
10)、notify():唤醒一个线程;
11)、setPriority():设置一个线程的优先级;
线程的实现
java实现多线程的方法:继承Thread类,实现runnable接口,实现Callable接口
优缺点:1)、继承Thread类为单继承,实现Runnable方法为多实现,所以在灵活性上来说,使用实现Runnable方法更灵活;
2)、通过实现Runnable接口的方式可以实现多线程内的资源共享;
3)、增加代码的健壮性,代码可以被多个线程共享,代码和数据独立;
4)、线程池只能放实现Runnable或callable类的线程,不能直接放入继承Thread类的线程;
线程调度
线程调度1、调整现场优先级:Java线程有优先级,优先级高的线程获得较多的运行机会(运行时间);
static int Max_priority 线程可以具有的最高优先级,值为10;
static int MIN_PRIORIYT 线程可以具有的最低优先级,值为1;
static int NORM_PRIORITY 分配给线程的默认优先级,值为5;
Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级;
2、线程睡眠:Thread.sleep(long millins)使线程转到阻塞状态;
3、线程等待:Object.wait()方法,释放线程锁,使线程进入等待状态,直到被其他线程唤醒(notify()和notifyAll());
4、线程让步:Thread.yeild()方法暂停当前正在执行的线程,使其进入等待执行状态,把执行机会让给相同优先级或更高优先级的线程,如果没有较高优先级
或相同优先级的线程,该线程会继续执行;
5、线程加入:join()方法,在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,知道另一个进程运行结束,当前线程再有阻塞状态转
线程同步
线程同步主要使用synchronized关键字;具体有两种实现方式:1、作为关键字修饰类中一个方法;2、修饰方法中的一块区域(代码); 1、把synchronized当做方法(函数)的修饰符:
public class Name{//类名为Name
//getName方法
public synchronized void getName(){
system.out.println(“123”);
}
}
类Name 有两个实例对象n1和n2,此时有两个线程t1和t2;
n1在线程t1中执行getName()方法(获得这个方法的锁),那么n1就不能在线程t2中执行getName()方法,但是n2可以在t1线程中执行getName()方法,同理n2 不能同时在t2线程中执行getName()方法;
所以说实际上synchronized锁的是getName()这个方法的对象(n1和n2),而不是锁的这个方法,这个需要理解;
同步块,实例代码如下:
public void getName(Object o){
synchronized(o){
//TODO
}
}
这里表示锁住这个变量o;这里做一个线程安全的单例模式,
public class Car{
//构造方法私有化
private Car();
//创建一个静态的私有的空的常量car
private static Car car = null;
//对外开放一个静态的共有的方法用来获取实例
public static getInstance(){
if(car == null){
synchronized(Car.getClass()){
if(car == null){
car = new Car();
}
}
}
return car;
}
}
线程数据传递
在传统的开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果,但是在多线程的异步开发模式下,数据的传递和返回同同步开发模式有很大区别。由于线程的运行和结果是不可预料的,因此在传递和返回数据时就无法像函数一样通过函数参数和return语句来返回数据;
通过构造方法传递参数
package mythread;
public class MyThread1 extends Thread
{
private String name;
public MyThread1(String name)
{
this.name = name;
}
public void run()
{
System.out.println("hello " + name);
}
public static void main(String[] args)
{
Thread thread = new MyThread1("world");
thread.start();
}
}
由于这种方法是在创建线程对象的同时传递数据的,因此,在线程运行之前这些数据就就已经到位了,这样就不会造成数据在线程运行后才传入的现象。如果要传递更复杂的数据,可以使用集合、类等数据结构。使用构造方法来传递数据虽然比较安全,但如果要传递的数据比较多时,就会造成很多不便。由于Java
没有默认参数,要想实现类似默认参数的效果,就得使用重载,这样不但使构造方法本身过于复杂,又会使构造方法在数量上大增。因此,要想避免这种情况,就得通过类方法或类变量来传递数据。
通过变量和方法传递数据
向对象中传入数据一般有两次机会,第一次机会是在建立对象时通过构造方法将数据传入,另外一次机会就是在类中定义一系列的public的方法或变量(也可称之为字段)。然后在建立完对象后,通过对象实例逐个赋值。下面的代码是对MyThread1类的改版,使用了一个setName方法来设置 name变量:
package mythread;
public class MyThread2 implements Runnable
{
private String name;
public void setName(String name)
{
this.name = name;
}
public void run()
{
System.out.println("hello " + name);
}
public static void main(String[] args)
{
MyThread2 myThread = new MyThread2();
myThread.setName("world");
Thread thread = new Thread(myThread);
thread.start();
}
}
最后是线程之间的数据共享,以下贴出生产者消费者模式的实现:
实现场景:
有一个馒头房,生产者生产馒头,消费者消费馒头,当馒头数量为0时要停止消费,开始生产,当生产的馒头大于5时停止生产,开始消费;
分析:
1、馒头类:变量:mt(馒头),num(馒头数量),eatMantou()(吃馒头方法),produceMantou()(生产馒头方法),此处通过构造方法传递对象
2、消费者类:吃馒头,即用来调用馒头对象的吃馒头方法;
3、生产者类:生产馒头,即用来调用馒头对象的生产馒头方法;
4、Test类:用来测试;
具体代码如下:
package com.yp.producerAndconsumer;
/**
* @prama 生产者消费者问题中的产品
*/
public class Mantou {
private String mt;
private int num;
public String getMt() {
return mt;
}
public void setMt(String mt) {
this.mt = mt;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public Mantou(String mt, int num) {
this.mt = mt;
this.num = num;
}
/**
* 消费馒头
*/
public synchronized void eatMantou(){
//如果馒头数量大于0,则消费馒头
while(num >0){
System.out.println("消费后有:---"+this.num+"\n");
num--;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
while(num ==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
this.notify();
}
}
/**
* 生产馒头
*/
public synchronized void produceMantou(){
while(num<5){
System.out.println("生产后有:---"+this.num+"个"+"\n");
num ++;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
while(num ==5){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
this.notify();
}
}
}
消费者类;
package com.yp.producerAndconsumer;
public class Consumer implements Runnable {
private Mantou m ;
public Consumer(Mantou m) {
this.m = m;
}
@Override
public void run() {
m.eatMantou();
}
}
生产者类:
package com.yp.producerAndconsumer;
public class Producer implements Runnable{
Mantou m ;
public Producer(Mantou m) {
this.m = m;
}
@Override
public void run() {
m.produceMantou();
}
}
测试类:
package com.yp.producerAndconsumer;
/**
* @param 生产者消费者问题
* @author YangPeng
*
*/
public class ProducerAndConsumer {
public static void main(String[] args) {
Mantou mantou = new Mantou("花卷",4);
Producer producer = new Producer(mantou);
Consumer consumer = new Consumer(mantou);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
t1.start();
t2.start();
}
}
死锁
同步可以保证资源共享操作的正确性,但是过多同步也会产生问题。例如,现在张三想要李四的画,李四想要张三的书,张三对李四说“把你的画给我,我就给你书”,李四也对张三说“把你的书给我,我就给你画”两个人互相等对方先行动,就这么干等没有结果,这实际上就是死锁的概念。
所谓死锁,就是两个线程都在等待对方先完成,造成程序的停滞,一般程序的死锁都是在程序运行时出现的。