一般来说,把正在计算机中执行的程序叫做“进程”,所谓的“线程”是指“进程”中某个单一顺序的控制流。
要求读者掌握
1掌握java多线程机制
2.直到进程和线程的区别
3.掌握进程的几种状态
4.掌握使用两种方式创建进程
5.掌握线程的同步
6掌握线程不同状态之间的转化
Q 请说明进程好线程的区别
值得注意的是进程是属操作系统的
a一个程序至少有一个进程,一个进程至少有一个线程,线程的划分尺度小于进程,使用多线程程序的并发性高
b进程在执行过程中拥有独立的内存单元,而多个线程共享内容,从而极大地提高了程序的执行效率
c.线程在执行过程中与进程还是有区别的,每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中。
D.从逻辑角度上看,多线程的意义在于一个应用程序中可有有多个执行部分可以同时执行。操作系统并没有将多个线程看作是多个独立的应用,来实现程序的调度和管理,以及资源分配。这就是进程和线程的主要区别
E.线程是具有独立功能的程序关于某个数据集合上一次运动活动,进程是系统进行资源分配和调度的一个独立的单位
F.线程是进程的一个实体,是cpu调度和分派的基本单位,他是比进程更小的独立运行的基本单位。县城本身基本上不拥有资源,只拥有一点在运行中必不可少的资源(栈,程序计数器,一组寄存器),但是它可与同属于一个进程的其他线程共享进程所拥有的全部资源
G一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
最主要的区别是 它们是不同于操作系统资源管理方式
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的栈堆和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个程序死掉,所以多进程的程序要比多线程程序健壮,但是进程切换是,消耗资源较大,效率要差一些。
但是对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
Q一个具有生命的线程有哪些状态
创建、就绪、运行、阻塞、死亡五中状态
1创建 实例化Thread对象,但没有调用start((方法时的状态
ThreadTest a=new ThreadTest();或者Thread aa=new Thread(a);
虽然创建了Thread对象但是还是不能通过isAlive()
2就绪线程有资格运行,但调度进程还没有把它选为运行线程时所处的状态
线程创建后,调用了start方法,线程处于运行状态,但能通过isaliver方法
3运行 从就绪池中被选为但前执行的线程所处的状态
4等待、阻塞或者睡眠
线程依然活着,但是缺少条件,一旦具备条件就转化为就绪状态
Suspend方法和stop方法已经被废弃了,比较危险
5死亡一个线程run方法运行结束,那么该线程完成使命,他的栈结构将解散,也就是死完了。但他任然是一个Thread对象,仍然可以被调用,这一点与其他对象一样,而且别引用的对象也不会被垃圾回收器回收
一旦线程死去,永远不能从新启动了,也就是说不能再使用start方法让它运行
Q3那个方法是正确启动新线程。
B创建线程对象并且调用start方法
对于start方法的调用会立即返回,而线程会异步启动run方法
Q4如何创建启动线程
1继承java。Lang。Thread类
Calss ThreadTest extends Thread { Public static void main(String[] args){ Thread t=new Thread(); t.start(); t.run(“nihao”); } Public void run(String name){ System.out.println(“string in run is…”); } Public void run(){ System.out.println(“something run here!”); } } Output String int… Something run here
一旦调用start方法,必须给jvm留足时间,让它配置进程,而在jvm配置完成前,重载的run(string)方法被调用了,结果反而先输出 string in..然后 线程配置完成后 子啊输出 something
验证,如果在t.start加上for循环,给足充足时间,run方法就能执行
注意:这种输出结果顺序是没有保障的!不要依赖循环耗时!
Thread类中有许多管理线程的方法,包括创建、启动和暂停,所有的操作都是从run方法开始,并且在run方法内编写需要在独立线程内执行的代码。Run方法可以调用其它方法,但是执行的线程总是通过调用run方法
没有参数的run方法时自动被调用的,而带参数的run方法是被重载的,必须显示调用。
2实现java。Lang。Runnable接口
Class Thread implments Runnable{ Public void run(){ System.out.println(“something run here”); } ..Main() { ThreadTest tt =new ThreadTest(); Thread t1=new Thread(tt); Thread t2=new Thread (tt); T1.start(); T2.start(); } }
这种方式把线程相关的代码和线程要执行的代码分离开来。
另一种是参数式匿名内部类,
Class ThreadTest{ …Main(){ Thread t=new Thread(\new Runnable(){ Public void run(){ System.out.println(“anonymous thread”); } } ); t.start(); } }
线程的启动要调用start方法,只有这样才能创建新的调用栈。而直接调用run方法的话,就不会创建新的调用栈,也不会创建新的线程,run方法就与普通的方法没设么两样
Q5选择正确的线程说法
下面哪种说法正确?
A thread类是抽象类
B Thread类实现Runnable
C实现Runnable接口的类必须定义一个start方法
D 实现Runnable 的对象调用run方法将创建一个新的线程
E 当最后一个非守护线程结束时,程序将结束
Thread类实现了Runnable接口,不是抽象类
当最后一个非后台线程结束时,程序也就终止了
Runnable接口有一个run方法,不过该接口没有规定必须定义一个start方法
在一个runnable对象上调用run方法无法创建新的线程
Run是线程的执行方法
必须创建Thread类的实例,以生成大量的新的线程
Q6 选择正确的输出结果
package 练习; import java.util.Scanner; public class x extends Thread{ public void run(){ try{ for(int i=1;i<5;i++){ System.out.println(i+" "); if(i>2){ interrupt(); sleep(1000); if(interrupted()) break; } } }catch(InterruptedException e){ System.out.println(" caugth"); } } public static void main(String[] args) { x x=new X(); x.start(); } Output 1 2 3 Caught
注释: interrupt()只是改变中断状态而已. interrupt()不会中断一个正在运行的线程。这一方法实际上完成的是,给受阻塞的线程抛出一个中断信号,
这样受阻线程就得以退出阻塞的状态。更确切 的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞, 那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。
如果线程没有被阻塞,这时调用interrupt()将不起作用;否则,线程就将得到InterruptedException异常(该线程必须事先预备好处理此状况),接着逃离阻塞状态。
线程A在执行sleep,wait,join时,线程B调用线程A的interrupt方法,的确这一个时候A会有InterruptedException 异常抛出来.
但这其实是在sleep,wait,join这些方法内部会不断检查中断状态的值,而自己抛出的InterruptedException。
如果线程A正在执行一些指定的操作时如赋值,for,while,if,调用方法等,都不会去检查中断状态
,所以线程A不会抛出 InterruptedException,而会一直执行着自己的操作.
当线程A终于执行到wait(),sleep(),join()时,才马上会抛出 InterruptedException.
若没有调用sleep(),wait(),join()这些方法,即没有在线程里自己检查中断状态自己抛出InterruptedException的 话,
那InterruptedException是不会被抛出来的.
具体使用见实例1,实例2。
注意1:当线程A执行到wait(),sleep(),join()时,抛出InterruptedException后,中断状态已经被系统复位了,
线程A调用Thread.interrupted()返回的是false
Q7如何理解线程同步
package 练习; import java.util.Scanner; public class x { static Thread makeThread(final String id,boolean daemon){ Thread t=new Thread(id){ public void run(){ System.out.println(id); } }; t.setDaemon(daemon); t.start(); return t; } public static void main(String[] args) { Thread a=makeThread("A", false); Thread b=makeThread("b", true); System.out.println("END/"); } } Outout END/ A
线程共享了相同的资源
1共享变量
要是多个线程在一个程序中有用,必须有某种方法实现线程间互相通信或者共享结果,最简单的方法是使用共享变量。使用同步来确保值从一个线程正确传播到另一个线程,以及防止当一个线程正在更新一些相关的数据项时,另一个线程看到不一致的中间结果。
2.存在某一个内容空间中的所有线程
线程与进程有许多共同点,不同的是线程与同一进程中的其他线程共享相同的进程上下文,包括内存空间。只要访问工共享变量(静态变量或实例变量),线程就可以方便地互相交换数据,但必须确保线程一受控的方式访问共享变量,以免它们互相干扰对方的更改
3受控访问的同步
为了确保可以再线程之间手控方式共享数据,java语言提供了两个关键字,synchronized和volatile、
Synchronized有以下连个重要含义
一次只有一个线程可以执行代码的受保护部分
一个线程更改的数据对于其他线程是可见的。
若果没有同步,数据很容易就处于不一致状态。
4.确保共享数据更改的可见性
同步可以让用户确保县城看到一致的内存视图
处理器可以使用高速缓存加速对内存的访问(或者编译器可以将值存储到寄存器中,以便进行更快的访问)。这表示在这样的系统上,对于同一变量,在两个不同处理器上执行的连个线程可能会看到连个不同的值。
Volatile 比同步更简单,只适合于控制对基本变量(整数、布尔变量等)单个实例的访问。当一个变量被声明为volatile,任何对该变量的写操作都会绕过高速缓存,直接写入主存,而任何对该变量的读取也都绕过高速缓存,直接取自动主存。这表示是所有县城在任何时候看到的volatile变量值都相同。
5用锁保护的原子代码块
Volatile 对于确保每个线程看到最新的变量值非常有用,但实际上经常需要保护代码片段,同步使用监控器或锁的概念,已协调对特定代码块的访问。
每个java都有一个相关的锁,同一时间只能有一个线程持有java锁,当线程进入synchronized,线程会阻塞并等待直到锁可用。当线程处于就绪状态时,并且获得锁后,将执行代码块,当控制退出受保护的代码块,即达到了代码块末尾或者抛出没有在synchronized块中捕获的异常时,他就会释放锁
这样,每次只有一个线程可以执行受给定监控器保护的代码块。从其他线程的角度看,该代码块可以看做是原子的,他要么全部执行,要么更本不执行
6简单的同步示例
package 练习; import java.util.Scanner; public class x { private static Object lockObject=new Object(); private static class Thread1 extends Thread{ int x,y; public void run(){ synchronized(lockObject){ x=y=0; System.out.println(x); } } } private static class Thread2 extends Thread{ int x,y; public void run(){ synchronized(lockObject){ x=y=1; System.out.println(x); } } } public static void main(String[] args) { new Thread1().run(); new Thread2().run(); } }
将会打印 01或者10 如果没哟同步,他还会打印11 或00
7java锁定
Java锁定可以保护许多代码块或方法,每次只有一个线程就可以持有锁,如果两个线程正在等待相同的锁,则他们不会同时执行该代码
在以下示例中,连个线程可以同时不受限制地执行setLastAccess()方法中的synchronized块,因为每个线程有一个不同的thengie值。因此synchronized代码块受到两个正在执行的线程不同锁的保护。
package 练习;
import java.util.Date;
import java.util.Scanner;
public
classx {
public
staticclassThingie{
private Date
lastAccess;
public
synchronized void setLastAccess(Date
date){
this.lastAccess=date;
}
}
public
staticclassMyThread extendsThread{
private Thingie
thingie;
public MyThread(Thingie
thingie){
this.thingie=thingie;
}
public
void run(){
thingie.setLastAccess(new Date());
}
}
public
static void main(String[]
args) {
Thingiethingie=new Thingie();
Thingiethingie2=new Thingie();
new MyThread(thingie).start();
new MyThread(thingie2).start();
}
}
8同步方法
创建synchronized块的最简单方法是将方法声明成synchronized。这表示在进入方法主体前,调用者必须获得锁。
9同步的块
Synchronized块的语法比synchronized方法稍微复杂一些,因为还需要显示地指定锁要保护哪个块。
Public class point{
Public void setXY(int x,int y){
Synchronized(this){
This.x=x;
This .y=y;
}
}
使用this引用作为锁表示代码块将于这个类中的synchronized方法使用同一个锁
10大多说类并没有同步
因为同不会带来小小的性能损失
Q 线程同步选择题
运行下面例子会输出什么
package 练习;
import java.util.Date;
import java.util.Scanner;
public class X extends Thread {
staticObject lock1=new Object();
staticObject lock2=new Object();
staticvolatile int i1,i2,j1,j2,k1,k2;
publicvoid run(){
while(true){
doit();
check();
}
}
voiddoit(){
synchronized(lock1) {
i1++;
}
j1++;
synchronized(lock2) {
k1++;
k2++;
}
j2++;
synchronized(lock1) {
i2++;
}
}
voidcheck(){
if(i1!=i2){
System.out.println("i");
}
if(j1!=j2){
System.out.println("j");
}
if(k1!=k2){
System.out.println("k");
}
}
public static void main(String[] args) {
newX().start();
newX().start();
}
}
Outout
output
在执行过程中不能确定打印字母i,j,k的哪一个。
Q9下面哪些事件会导致线程死亡
A sleep()
B wait()
C Start()
D run
E线程构造执行结束
在java线程中,正在运行的线程可以转化为不可运行状态,八十线程不能够从不可运行状态直接转化为运行状态,而是首先转化为准备运行状态。
Interrupt():中断调度此方法线程。在等待通知、休眠或者阻塞以等待挂起完成的状态,线程会抛出InterruptedException异常
Yield():是线程暂时停止运行
1线程调度的优先级
Java将线程的优先级分为1个等级,分别是1-10之间的数字表示。数字越大表示等级越高。当一个线程对象被创建时,默认的线程优先级是5
为了控制现成的运行策略,java定义了线程调度器来监控系统中处于就绪状态的所有线程。线程调度器采用“抢占优先级”策略来调度线程执行。具有相同优先级的所有线程采用轮状的方式同分配cpu时间片。
使用setPriority()方法改变线程的优先级。
在Java中比较特殊的线程被称为守护线程deaemon线程的低级线程。这个线程具有最低的优先级。用于系统中的其他对象和线程提供服务。设置为一个守护线程方式是,在线程对象创建之前调用setdaemon方法。Jvm中的系统资源自动回收线程,他始终是在低级别的状态中运行。用于实施监控和管理系统中的可回收资源。
2.运行和挂起
线程通过start方法调用后,线程就进入了准备运行状态。进入准备运行阶段后,线程才能获得·运行的资格,即获得cpu的时间
系统进程调度器来决定哪个线程可以运行,并确定其运行时间,也就是说,线程具有不可预测性。
调用yield方法将会导致当前线程从运行状态转化为准备运行状态。使用yield方法的好处就是可以让出cpu的时间,供其他线程使用。
package 练习;
importjava.util.Date;
importjava.util.Scanner;
public class Xextends Thread {
public staticvoid main(String []args){
Mythread3mythread32=new Mythread3("t2");
Mythread3mythread3=new Mythread3("t1");
mythread3.start();
mythread32.start();
}
}
class Mythread3extends Thread{
publicMythread3(String s) {
// TODOAuto-generated constructor stub
super(s);
}
public voidrun(){
for(inti=0;i<100;i++){
System.out.println(getName()+":"+i);
if(i%10==0){yield();
}}}
}
t1: 0
t1: 1
t1: 2
t1: 3
t1: 4
t1: 5
t1: 6
t1: 7
t1: 8
t1: 9
t1: 10
t1: 11
t1: 12
t1: 13
t1: 14
t1: 15
t1: 16
t1: 17
t1: 18
t2: 0
t2: 1
t2: 2
t2: 3
sleep()方法
使当前线程(即调用该方法)暂停执行一段时间,让其他线程有机会继续执行,但他并不释放对象锁,也就说如果synchronized同步块,其他线程任然不嗯能够访问共享数据,注意该方法要捕获异常。
总之,sleep可以使低优先级的线程的待执行的机会,也可以让同优先级。高优先级的线程有执行的机会。
3 wait()和notify()
如果条件不满足,则等待。当条件满足时,等待该条件的线程将被唤醒,在java中,这个机制的实现依赖于wait和notify。等待机制和锁机制是密切关联的
Synchronize(obj){
While(!condition){
Obj.wait()
}
Obj.doSomething();
}
}
当线程A获得obj锁后,发现条件condition不满足,无法继续下一处理,于是线程a就等待(wait)
再赢一个线程b中,如果b更改了某些条件,使得线程a的condition条件满足了,就可唤醒线程A
Synchronized(obj){
Condition=true;
Obj.notify()
}
4线程调度规则
如果两个或是两个以上的线程都修改一个对象,那么把执行修改的方法定义为同步的(synchronized),如果对象更新影响到只读方法,那么只读方法也因该定义为同步的
如果一个线程必须等待一个对象状态变化,那么他应该在对象内部等待,而不是在外部等待,它可以调用一个被同步的方法,并让这个方法调用wait方法。
每当一个方法改变某个对象的状态时,它应该调用notify方法,这个等待队列的线程提供了机会,来看一看执行环境是否已经发生改变
记住wait notify notifyall 方法属于object,而不是thread类,仔细检查看看是否每次执行wait方法都有相应的notify或notifyall方法,且他们作用与相同的对象
注意:在java总每个类都有一个主线程,要执行一个程序,那么这个类当中一定要有main方法,main方法是java类中的主线程。自己创建线程有两种方法,一种是集成thread类,另一种是实现runnable接口。一般情况下,最好避免继承,因为java中是单根继承,如果继承thread类,则无法再继承其他类。
Q10 调用yield方法可以保证什么
A 所有优先级较低的线程获得cpu时间
B 当前线程休眠一段时间,其他线程运行
C 当前线程停止,直到其他线程终止
D 线程将等待,直到被通知
E以上都不正确
E 线程调度器的确切行为是没有定义的,不能保证yield方法的调用hi导致其他线程使用cpu
Q11notify方法意义在何处
A线程 b对象 c applet drunnable
b