对象技术提供了一种把程序划分为若干个独立部分的方式。通常,还需要把程序转换为彼此分离的,能够独立运行的子任务。
进程:是一个自包含的运行程序,有自己独立的内存空间,是一个程序的执行过程。“多任务”操作系统通过周期性的将CPU切换到不同的任务,即
CPU时间片轮转,使得能够同时运行的多个进程,而每个进程都像是在连续的运行,一气呵成。
线程:是一个进程的不同执行路径,是进程内部的一个控制序列流,因此,一个进程内可以包含有多个并发执行的线程。
1、基本线程:
Java 中实现线程类的方发有两种
1、继承Thread类,并重写run()方法
2、实现Runnable接口,并实现run()方法
注意:在创建一个新线程的后,启动一个新线程使用的是start()方法,而不是run()方法
两者的区别在于
使用start()方法是启动一个已经创建好的线程,是真正意义上的线程启动
而使用run()方法则是一个已经实例化对象对对象方法的调用,因此还是在当前线程中,并没有启动一个新 的线程
1、继承Thread类,并重写run()方法
2、实现Runnable接口,并实现run()方法
注意:在创建一个新线程的后,启动一个新线程使用的是start()方法,而不是run()方法
两者的区别在于
使用start()方法是启动一个已经创建好的线程,是真正意义上的线程启动
而使用run()方法则是一个已经实例化对象对对象方法的调用,因此还是在当前线程中,并没有启动一个新 的线程
整个步骤是:首先调用构造器来构造对象,在构造器中调用了start()方法来配置线程,然后由线程执行机制调用run()方法。如果不调用start()方法,那么线程将永远不会启动。
虽然线程的实现有两种方式,但是由于Java是单继承的,所以有限推荐使用实现Runnable接口,这样此类仍然可以继承其他类
虽然线程的实现有两种方式,但是由于Java是单继承的,所以有限推荐使用实现Runnable接口,这样此类仍然可以继承其他类
对于Thread中的run()方法,事实上总是以某种形式的循环,使得线程一直运行下去直到不再需要,所以要设定跳出循环的条件。通常,run()方法被 写成无限循环的形式,这就意味着,除非有终止条件来终止run()方法,否则它将永远运行下去。
对于Java的垃圾回收机制对线程的操作:
当在main()中创建若干个线程对象的时候,并没有获得它们其中任何一个的引用。对于普通对象来说,这会使得其成为垃圾回收器回收器的回
收目标,但是对于Thread对象则不会,每个Thread对象都需要“注册”自己,所以实际上在某个地方存在着对它们的引用,垃圾回收器只有在线程
离开run()并且死亡之后才会将它们清理掉。
对于Runnable接口,只有run()方法,在实现接口的时候需要对方法进行实现,如果想对这个Thread对象进行其他操作的时候,就需要通过调用 Thread,currentThread()方法明确获取当前线程的引用。在实现Runnable接口的方法实现现成的时候,操作流程:
先单独创建一个Thread对象,并把Runnable对象传给专门的Thread构造器,然后可以对这个线程对象调用start()以执行通常的初始化动作。
事实上,我们观察java.lang.Thread的源码可以发现,Thread类也是实现的Runnable接口。
public class Thread implements Runnable { ………… }
2、线程的方法:
2.1 中断线程
* 1、使用interrupt()方法,但是这里必须还要进行内部中断,实现繁琐
* 2、自定义中断标记
* 此方法的有点在于,不用在对线程类的run()方法进行内部的二次中断
* 两个在与安全线程的一个区别
* 第二种更加适用于实际的开发
* 中断线程的最重要的原则是:让线程自己进行判断中断(即判断循环的执行情况),而不是采用硬编码的是否用户来结束线程
* 2、自定义中断标记
* 此方法的有点在于,不用在对线程类的run()方法进行内部的二次中断
* 两个在与安全线程的一个区别
* 第二种更加适用于实际的开发
* 中断线程的最重要的原则是:让线程自己进行判断中断(即判断循环的执行情况),而不是采用硬编码的是否用户来结束线程
package com.Thread;
/**
* @author Defens
* 此类测试和使用中断线程
* 中断线程:
* 1、使用interrupt()方法,但是这里必须还要进行内部中断,实现繁琐
* 2、自定义中断标记
*/
public class InterruptMethod {
public static void main(String[] args)
{
MyThread3 myThread = new MyThread3();
MyThread4 myThread2 = new MyThread4();
Thread thread = new Thread(myThread, "Interrupt方法式:");
Thread thread2 = new Thread(myThread2, "中断标记式:");
thread.start();
thread2.start();
for(int i=0; i<10; i++)
{
System.out.println(Thread.currentThread().getName() + "** " + i);
if(5 == i)
{
//thread.interrupt(); //中断线程,设置了一个中断标记,(中断状态为true),但并不是真正意义上的中断
myThread2.setInterruptSign(false);
System.out.println("中断标记式已经中断……");
}
try
{
Thread.sleep(500);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
class MyThread3 implements Runnable
{
public void run()
{
int i = 0;
while( !Thread.interrupted() && i<15) //当线程中断时,标记的中断状态会是true
{
//线程上次从什么地方中断的 这次还从什么地方开始执行,并不是完全重新的执行run()
System.out.println(Thread.currentThread().getName() + "-- " + i);
i++;
try
{
Thread.sleep(1000);
}catch(InterruptedException e)
{
e.printStackTrace();
Thread.currentThread().interrupt();
/*
* 如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,
* 或者该类的 join()、join(long)、join(long, int)、sleep(long)
* 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,即中断状态会设为false
* 以此同时会抛出 InterruptedException。
*
* 这里就解释了为什么在catch子句中会有一个中断操作,
* 因为在try中的sleep()会清除中断状态,并抛出中断异常
* 这个中断操作是将中断状态再次设为true,只有这样才能退出while循环
*/
}
}
}
}
//采用自定义标记的方式中断线程
class MyThread4 implements Runnable
{
private boolean interruptSign = true;
public boolean isInterruptSign()
{
return interruptSign;
}
public void setInterruptSign(boolean interruptSign)
{
this.interruptSign = interruptSign;
}
public void run()
{
int i = 0;
while(interruptSign)
{
System.out.println(Thread.currentThread().getName() + "++ " + i);
i++;
try
{
Thread.sleep(1000);
}catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
}
2.2 常用方法
* 线程类的方法
* Thread.currentThread() :获取当前线程
* getName() :获取线程的名字
* getID():获取线程的标记
* isAlive():测试线程是否处于活动状态
* 重要方法:
* static void sleep(long millis):休眠制定毫秒数后继续执行
* 原理:让当前线程进入休眠状态,让出当次执行的CPU时间,但是该线程不丢失任何监视器的所属权,即当前线程所占 用的资源并不释放
此时,引用当前线程同一线程对象的另一个现在会因缺少资源而处于阻塞状态
* Thread.currentThread() :获取当前线程
* getName() :获取线程的名字
* getID():获取线程的标记
* isAlive():测试线程是否处于活动状态
* 重要方法:
* static void sleep(long millis):休眠制定毫秒数后继续执行
* 原理:让当前线程进入休眠状态,让出当次执行的CPU时间,但是该线程不丢失任何监视器的所属权,即当前线程所占 用的资源并不释放
此时,引用当前线程同一线程对象的另一个现在会因缺少资源而处于阻塞状态
注意:在调用sleep()方法的时候,必须将它放入try块中,这是因为sleep()方法在休眠的时间到期之前有可能 被中断
* static void sleep(long millis, int nanos):休眠指点毫秒和纳秒之后继续执行
* void join():等待该线程终止,加入到某个线程
* void join(long millis):等待该线程终止的时间最长为millies毫秒
* void join(long millis, int nanos):等待该线程终止的时间最长毫秒和纳秒
* void join():等待该线程终止,加入到某个线程
* void join(long millis):等待该线程终止的时间最长为millies毫秒
* void join(long millis, int nanos):等待该线程终止的时间最长毫秒和纳秒
2.3 守护线程
* 线程分为用户线程和守护线程两种
* 当不存在用户线程的时候,全部的守护线程将自动终且设置为守护线程的方法必须在启动线程前调用。
* 当不存在用户线程的时候,全部的守护线程将自动终且设置为守护线程的方法必须在启动线程前调用。
3、共享受限资源
在多线程环境中,可以同时做多件事,但是,由于线程获取时间片是我们无法预期的,所以多个线程同时使用一个受限资源的问题就出现
了。必须防止这种资源访问的冲突。
3.1 不正确的访问资源
想象一下,我们坐在桌子旁边,手里有一双筷子,准备夹起盘子里最后一点食物时,当筷子刚要夹起的时候,食物消失了(因为当前你的线程
被挂起了,另一个线程进来偷走了食物),这就是我们在写并发程序的时候需要面临的重要问题。
在试图使用某个资源的同时,有时并不关心它是否正在被访问,但是为了让多线程能正确工作,就需要采用某种方法来防止两个线程访问同一
个资源,至少是在某个关键时刻避免这种问题。
要防止这类冲突,只要在线程使用资源的时候给它加一把锁就行了,访问资源的第一个线程给资源加锁,然后其他线程就只能等到锁被解除以
后才能访问该资源,解锁的同时另一个线程就可以对该资源加锁并访问。
3.2 资源冲突
对于一个资源,最糟糕的莫过于客户线程可能会发现在它处于不稳定的中间状态,尽管对象最终被观察处于合法状态,而且其内部一致性能够
得到维护。但是如果两个线程确实是在修改同一个对象,共享资源冲突将变得更加糟糕,因为这有可能把对象设置为不正确的状态。
下面考虑简单的“信号量”概念,它可以看做是在两个或多个线程之间进行通信的标志对象。如果信号量的值为零,则它监视的资源是可用的,但
如果这个值为非零的,则被监 监视的资源是不可用的,所以线程必须等待。当资源可用的时候,线程增加信号量的的值,然后继续执行并使用
这个被监视的资源。因为把增加操作和减少操作当作为原子操作(不能被中断),所以信号量能够保证多个线程同时访问一个资源的时候不至于
冲突。
3.3 解决共享资源竞争
除上面说的信号量的监视机制,Java本身提供了一种解决机制,即使用 synchronized关键字进行同步设置。
3.3.1 线程的同步:
* 多线程共享数据的安全性:使用同步来解决
* 1、同步代码块,其中一共要同步一个对象,此对象是用来标记的,不具有其他意义
* 2、同步方法,此时并没有给出一个同步的标记对象,实际上,同步方法同步的是当前对象,因此当前对象就是一个标记对象
* 当操作中的所有代码都需要同步时,使用同步方法
* 当操作中部分代码不需要进行同步的时候,使用同步代码块
* 在使用同步方法时,由于默认使用的是当前对象(this),因此当有一个以上的同步方法的时候是不能执行的,会出现问题
* 线程的同步会带来性能的降低,但提高数据的安全性
* 同步准则:
* 1、使代码块保持简短,吧不随线程变化的预处理和后处理移出synchronized块
* 2、不要使用阻塞式方法,如InputStream.read();
* 3、在持有锁的时候,不要对其他对象调用方法,但当确信的对象不会出现同步操作的时候是可以使用的
* 多线程共享数据的安全性:使用同步来解决
* 1、同步代码块,其中一共要同步一个对象,此对象是用来标记的,不具有其他意义
* 2、同步方法,此时并没有给出一个同步的标记对象,实际上,同步方法同步的是当前对象,因此当前对象就是一个标记对象
* 当操作中的所有代码都需要同步时,使用同步方法
* 当操作中部分代码不需要进行同步的时候,使用同步代码块
* 在使用同步方法时,由于默认使用的是当前对象(this),因此当有一个以上的同步方法的时候是不能执行的,会出现问题
* 线程的同步会带来性能的降低,但提高数据的安全性
* 同步准则:
* 1、使代码块保持简短,吧不随线程变化的预处理和后处理移出synchronized块
* 2、不要使用阻塞式方法,如InputStream.read();
* 3、在持有锁的时候,不要对其他对象调用方法,但当确信的对象不会出现同步操作的时候是可以使用的
3.3.2 原子操作
一个常被提到的知识是:“原子操作不需要进行同步控制”。原子操作,即不能被线程调度机制中断操作:一旦 开始,那么它一定
可以在可能发生的“上下文切换”之前执行完毕。
除此之外还有一个知识是,如果在问题中的变量类型除long或double以外的基本类型,对这种变量进行简单的 赋值或返回值操作
的时候,才能算是原子操作。不包括long和double的原因是因为它们比其他基本类型要大,所以JVM不能把它的读取和赋值当成一种
单一的原子操作,但是,只有在long和double前加上volatile,操作就会变成原子的。如果把一个变量定义成volatile的,就等于告诉编译器不
要做任何形式的优化,这些优化可能会移除那些使字段与线程里的本地数据复本保持完全同步的读写操作。而原子操作也很容易访问到对象
尚处于不稳定状态的值,
因此不能做任何形式的假设。针对这些,提出最安全的做法:
1> 如果要对类中的某个方法进行同步控制,最好同步所有方法,如果忽略其中一个,通常很难确定这样会不会带来负面影响。
2>当去除方法的同步控制的时候,要非常小心,通常这么做是基于性能上的考虑。
3.4 线程状态和线程间的协作
3.4.1 线程状态
一个线程可以处于以下四种状态:
1> 新建(new) :线程对象已经建立,但是没有启动,所以不能运行。
2> 就绪(Runnable):在这种状态下,只要调度程序把时间片分配给线程,线程就可以运行,也就是说,在 任意时刻,线程可
以运行也可以不运行。只要调度程序吧时间片分配给线程,它就可以运行,这不同于死亡和阻塞状态。
3> 死亡(Dead):线程死亡的通常方式就是从run()方法返回。
4> 阻塞(Blocked):线程能够运行,但某个条件阻止它的运行。当线程处于阻塞状态时,调度机制将忽略线程,不会分配时间
片给该线程,直到线程重新进入就绪状态,他它才有可能执行操作。
3.4.2 线程之间的协作
调用sleep()的时候并没有释放同步锁,但是调用wait()方法时同步锁是被释放的,这就意味着在调用wait()期间,可以调用线程对象中的其
他同步方法。当线程在方法里遇到wait()的调用的时候,线程就会被挂起,对象锁被释放。
对于wait()而言:
1> 在wait()期间对象锁释放
2> 可以通过notify()、notifyAll()或时间到期,从wait()中恢复执行。
对于wait()、notify()、notifyAll(),查阅文档可以发现,它们并不是Thread的方法,而是Object的一部分,看似 有点奇怪,把它作为通用类
的一部分进行实现,不过这是有道理的,因为这些方法会用到同步锁也是所有对象的一部分。所以,这些方法可以被放到任何同步块
中,而不用考虑是不是继承自Thread或实现Runnable接口。事实上,只能在同步控制方法或者同步代码块中调用wait()、notify()、
notifyAll()。如果在非同步方法或同 步代码块中调用这些方法,程序会通过编译,但是在运行的时候会抛出IllegalMonitorStateException
异常,以及其他的错误。