标签:print rac 试题 join 合并 new t 完全 数组元素 auto
《Java线程》
线程,是一个程序里面,不同的执行路径。
进程:一个静态的概念。eg:一个.exe文件,一个.class文件。
程序的执行过程:OS先把该程序的代码放到内存代码区里面。这时一个进程产生,但还没执行。当该进程中的主线程执行时,该进程称为执行。所以进程相当于线程的“壳”,进程不会执行,只有进程里的线程能够执行。计算机上,实际运行的都是线程。
多线程:线程的并发不是同时执行的,CPU把时间片分给各个线程, 切换时间很短,看起来是同时执行的。某个时间点,一个CPU只能执行一个线程(进程)。
开启线程两种方式:继承thread类,重写run方法,实现runable接口,实现run方法,然后创建Thread对象。通常采用第二种方法
Strart()方法:
当执行到t.start()的时候,开辟了一个新的线程(通知CPU),此时处于就绪状态也有可能处于运行状态,取决于操作系统。一旦运行,它和主线程开始并行执行(实际是交替执行的),会在在调度过程中在运行状态和就绪状态转换。通常使用抢占式调度,但一个线程时间片用完,操作系统考虑优先级选择下个线程。
Start()与Run()方法的区别:
直接调用一个方法执行原来Runner1中run()的内容,此时仍然在一个线程内,必须run执行完才能执行下面的代码。
isAlive()方法:“活着”是指“就绪”、“运行”、“阻塞”3个状态,“终止”就死了。
setPriority(),getPriority()方法 : 优先级:优先级越高,获得CPU时间片越多。
上例,执行结果为,子线程循环10次(每次1秒);10秒时,主线程将子线程打断(thread.interrupt());子线程捕获InterruptedException,结束;主线程结束。
注意1,Thread.sleep(***)静态方法,在哪个线程中使用,就睡眠哪个线程;即由所处的线程决定。
注意2,上例中,子线程类MyThread的定义中,sleep(1000)方法的异常InterruptedException不能交由run()抛出(throws出去);这是因为run()方法是一个重写父类的方法,只能抛出与父类完全相同的异常。
注意3,“捕获InterruptedException然后return空”不是打断睡眠结束线程的最好方式。eg:打开的资源未必来的及close()。 还有个stop()方法来结束线程,但这个方法基本废弃(一棍子打死不给catch异常的机会),一般只有在结束锁死的线程时使用。 比较温和的方法,下图:
2、Join:(另外一个线程合并进来,首先执行子线程,执行完了才能执行主线程)
上例,t1.join()方法,使得并行执行的两个线程合并为单一顺序执行的一个线程。结果中,main线程由于t1.join(),必须等待t1执行完后才能继续执行。
3、yeild
上例,观察结果能观察到,i被10整除时必让出cpu一次。
线程优先级:
三、线程同步
3.1同步:
几个线程对同一个资源进行访问
上例,线程同步一个重要例子。
代码分析:首先,Timer类(简单Pojo),有静态变量num和方法add(String name);add(String name)的作用,Timer.add(String name)每被调用一次,静态变量num加1,然后打印“**是第num个被调用的”;
其次,Test类(实现Runnable接口),包含必须的run()方法和main()方法,还有一个属性Timer对象;
再次,main()方法中,用Test类的对象test初始化了两个线程t1和t2,然后t1和t2分别setName();
最后,t1线程启动后,进入run()方法,调用timer.add();在timer.add()中,num由0变1,然后sleep(1),注意此时还没有打印t1的结果;sleep(1)的时候,t2启动,进入run()和add()方法,num再加1变为2;所以,打印的结果是t1和t2都是“第2个”。
此例出错的核心是,t1和t2线程都调用了资源timer(Timer的add()方法),但是add()方法分若干步完成,在没有将这些步骤同步的情况下,会产生问题(sleep(1)加大了错误发生概率)。
此例看起来别扭的原因是,main()方法所在的Test类继承Runnable接口,来初始化main()方法中的两个子线程t1和t2,即“自身使用自身”。
3.2synchronized:
解决同步出现的问题的两种方法:
注意,sychronized锁(称为对象互斥锁,但syschronized表示“同步的”,即资源是同步的,使用资源的对象是互斥的),有两种方式。其内部机制是:执行该方法的过程中锁定当前对象。只有当synchronized块的代码执行完,下个使用它的对象才能执行它,否则等待。但这个说法可能有歧义,看后面的笔试题例子。 一般synchronized理解为:保证被它包围的代码段是个整体。
3.3死锁:
死锁机制:对象a需要使用同步资源x和y(先x后y),对象b使用y和x(先y后x)。这样a锁定x等待y,b锁定y等待x。
上例,死锁。
解决死锁,一个办法是把锁的粒度加粗。即尽量不锁定两个对象而是一个对象。把锁的粒度加粗,效率低但是安全。
死锁一般在中间件中被解决了,小的项目不容易碰到,系统级的程序会遇到。数据库软件开发经常使用到各种锁。eg:只读锁、只写锁,行级锁、表级锁。
3.3synchronized面试题:
上例,为一常见笔试题。执行过程:从main()方法看,先启动了子线程t,子线程t会调用run()方法中的synchronized方法m1();m1()会把属性b由默认100改为1000,然后睡眠5秒;与此同时,主线程main()睡眠了1秒(为了m1()中的b = 1000执行完),然后调用对象tt的普通方法m2()。
syschronized只是锁定了对象的一个方法,其他线程完全可以自由调用对象的其他非同步方法,并且可能会对同步方法中产生影响。
注意,syschronized的“执行该方法的过程中锁定当前对象”,不是指若对象中有一个方法含synchronized,则使用时锁定该synchronized方法(或代码块)就是锁定该对象的所有方法。 synchronized很简单,只能保证被它包围的代码段是个整体。 上例中,只能保证“b = 1000”、“sleep(50000)”和“system.out.println()”三行代码是个整体。
若将m2()方法改为如下,则结果如下:
上例,先m1()方法的b = 1000,睡眠过程中,m2()方法改为b = 2000,m1()睡了5秒后,打印输出b ,值为2000。表明,synchronized并没有锁住整个对象,只是锁定了它包围的代码段。 所以,这样的锁定方法(锁定对象中修改属性的一个方法,但不锁定其他修改属性的方法)是危险的,并没有达到真正锁定属性b的效果。 应该,若想在方法中锁定属性的值,则需把所有关于属性操作的方法都锁定。如下:
上例,锁定m2()之后,m2()则必须遵循锁的规则,只有在另一个synchronized方法m1()执行完后,才能执行对b的操作。
即,通过锁定关于属性b更改操作的所有方法,来完成对属性b的锁定。
所以,若想同步一段代码或一个方法,有时是很困难的,需要考虑所在对象中其他方法是否也要同步,还要考虑过多同步带来的开销问题。
m1与m2同时 synchronized:
public class TT implements Runnable { int b = 100;
public synchronized void m1() throws Exception{ b = 1000; 3 Thread.sleep(5000); 6 System.out.println("b = " + b); 7 }
public synchronized void m2() throws Exception { Thread.sleep(2500); 1 b = 2000; 2 }
public void run() { try { m1(); } catch(Exception e) { e.printStackTrace(); } }
public static void main(String[] args) throws Exception { TT tt = new TT(); Thread t = new Thread(tt); t.start(); tt.m2(); System.out.println(tt.b); 4 } }
结果: 1000 b = 1000 |
只有m1 synchronized :
public class TT implements Runnable { int b = 100;
public synchronized void m1() throws Exception{ b = 1000; 2 Thread.sleep(5000); System.out.println("b = " + b); 5 }
public void m2() throws Exception { 1 Thread.sleep(2500); b = 2000; 3 }
public void run() { try { m1(); } catch(Exception e) { e.printStackTrace(); } }
public static void main(String[] args) throws Exception { TT tt = new TT(); Thread t = new Thread(tt); t.start(); tt.m2(); System.out.println(tt.b); 4 } } 结果: 2000 b = 2000 |
生产者与消费者问题:
wait()方法:从object类继承的方法,表示调用这个方法的当前的线程进入wait状态,wait()必须在synchronized用先锁定才有资格用。
Notify()方法:从object类继承的方法,唤醒这个对象的其他线程。
Wait和sleep方法的区别:
wait时别的线程可以访问锁定对象。
Sleep时别的线程不能访问锁定对象。
上例,描述:Producer(生产者)不停地往SyncStack(筐)中扔Wotou(窝头),Consumer(消费者)不断地从筐中取窝头;而筐最多放6个窝头,当满6时,生产者必须wait(),直到消费者取走馒头,生产者才被notify(),并继续仍馒头;当筐为0时,消费者wait(),同理。
此例核心是SyncStack类:
a) Wotou[] arrwWT= new Wotou[6],用数组表示“筐”。存的时候从0增到6,取的时候从6减到0,实现了栈stack的方式,才符合“筐”的后进先出特点。
b) push()和pop()方法是synchronized的。一是实现了属性index的同步,即index作为共享资源,在多个线程对其加减操作时,必须对其加锁;二是方法内部语句的加锁,arrWT[index] = wt和index++这两句需要时一个整体,否则会发生数组元素被“吃掉”的现象。
c) 在谈到wait()和notify()时,必须注意到:push()和pop()方法公用一个SyncStack对象ss来初始化所有Producer和Consumer的线程。所以当this.notify()执行时,唤醒的只是当前线程(可能是Producer也可能是Consumer)。
d) 右图,是push()方法的开头部分,先判断是否满6个,满了就让SyncStack.push()所在的线程(一定是个Producer线程)wait();然后不管怎样this.notify()一次。 注意,这个notify()并不是用来唤醒刚才wait()的那个线程的。因为唤醒后回去判断index还是6,会继续wait()。这个wait()(SyncStack.push()所在的线程),一定是被SyncStack.pop()所在的线程(一定是个Consumer线程)notify()的;因为一个消费者线程pop()了一个窝头后,index才会减1,this.notify()后,一直wait()的线程被唤醒,回去判断发现index不足6了,它才继续执行push()后面的语句。
e) 使用while循环而不是if语句,是因为InterruptException,当wait()被打断捕获异常并处理后,while循环会返回继续循环,而if语句则不会。
f) 此例为方便测试只new了1个生产者和1个消费者,当有多个生产者和消费者时,SyncStack中应是notifAll(),以在消费者取走第6个窝头后,一次唤醒所有因为筐满而等待的生产者。
wait()方法:首先,wait()必须在synchronized用,否则立即报错,即当一个线程锁定中才能wait();其次,wait()与sleep()不同,wait()是Object类的方法,使用时this.wait();其次,wait()后需要被唤醒,否则也会产生死锁;notify()含义是,叫醒一个在当前对象上等待的线程,wait()含义是,将在当前对象上的线程睡眠过去。
什么时候用wait():当某方法(例如发生阻塞事件)需要“暂停”(如判断数组满了),然后等待,直到该对象的某一事件(如判断数组不是满的了)发生并notify()。 notifyAll()是指若该对象的多个线程都wait()了,notifyAll()可全都唤醒。
标签:print rac 试题 join 合并 new t 完全 数组元素 auto
原文地址:http://www.cnblogs.com/ChuPengcheng/p/6189569.html