java--线程
2015-07-09 14:15
573 查看
多线程概述
要理解多线程,就必须理解线程。而要理解线程,就必须知道进程。
1、 进程
是一个正在执行的程序。
每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
2、线程
就是进程中的一个独立的控制单元。线程在控制着进程的执行。只要进程中有一个线程在执行,进程就不会结束。
一个进程中至少有一个线程。
3、多线程
在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程
运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。像这种在一个进程中有多个线程执行的方式,就叫做多线程。
4、多线程存在的意义
多线程的出现能让程序产生同时运行效果。可以提高程序执行效率。
例如:在java.exe进程执行主线程时,如果程序代码特别多,在堆内存中产生了很多对象,而同时对象调用完后,就成了垃圾。如果垃圾过多就有可能是堆内存出现内存不足的现象,只是如果只有一个线程工作的话,程序的执行将会很低效。而如果有另一个线程帮助处理的话,如垃圾回收机制线程来帮助回收垃圾的话,程序的运行将变得更有效率。 下载就是多线程的。 系统在创建进程,线程也是系统创建的。
5、计算机CPU的运行原理
我们电脑上有很多的程序在同时进行,就好像cpu在同时处理这所以程序一样。但是,在一个时刻,单核的cpu只能运行一个程序。而我们看到的同时运行效果,只是cpu在多个进程间做着快速切换动作。
而cpu执行哪个程序,是毫无规律性的。这也是多线程的一个特性:随机性。哪个线程被cpu执行,或者说抢到了cpu的执行权,哪个线程就执行。而cpu不会只执行一个,当执行一个一会后,又会去执行另一个,或者说另一个抢走了cpu的执行权。至于究竟是怎么样执行的,只能由cpu决定
线程状态:
新建:start()
运行:具备执行资格,同时具备执行权;
冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;
临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;
消亡:stop()
创建线程的第一种方式:继承Thread,由子类复写run方法。
1.子类覆盖父类中的run方法,将线程运行的代码存放在run方法中。
2.建立子类对象的同时线程也被创建。
3.通过调用start方法开启线程。
class Demo extends Thread {
public void run() {
for(int i = 0;i<60;i++){
System.out.println(“demo run---+”i);
}
}
}
class ThreadDemo{
public static void main(String[] args){
Demo d = new Demo();//创建一个线程
d.start();//调用start方法执行该线程的run方法
}
}
创建线程的第二种方式:实现一个接口Runnable。
1.子类覆盖接口中的run方法
2.通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。
3.Thread类对象调用start方法开启线程。
class Test implement Runnable
{
public void run(){ //子类覆盖接口中的run方法
for(int x=0; x<60; x++){
System.out.println(“run..."+x);
}
}
}
class ThreadTest {
public static void main(String[] args) {
Test te = new Test();实现Runnable接口的对象
Thread t1 = new Thread(te);将那个对象作为参数传递到Thread构造函数
Thread t2 = new Thread(te);
t1.start();//调用Thread类的start方法开启线程
t2.start();
}
两种线程创建方式的区别:
继承Thread类创建对象:
1.Thread子类无法再从其它类继承(java语言单继承)。
2.编写简单,run()方法的当前对象就是线程对象,可直接操作。
使用Runnable接口创建线程:
1.可以将CPU,代码和数据分开,形成清晰的模型。
2.线程体run()方法所在的类可以从其它类中继承一些有用的属性和方法。
3.有利于保持程序的设计风格一致。
继承Thread类线程代码存放于Thread子类run方法中;
实现Runnable接口线程代码存在于接口的子类run方法中,而且这种方式避免了单继承的局限性,在实际应用中,几乎都采取第二种方式。
为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法,就必须明确该
run方法所属的对象。
sleep方法需要指定睡眠时间,单位是毫秒。
冻结与运行之间的状态:准备就绪。具备了执行资格,但是没有执行权。被创建:等待启动,调用start启动。
运行状态:具有执行资格和执行权。
临时状态(阻塞):有执行资格,但是没有执行权。
冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
消忙状态:stop()方法,或者run方法结束。
注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常
sleep方法需要指定睡眠时间,单位是毫秒。
冻结与运行之间的状态:准备就绪。具备了执行资格,但是没有执行权。被创建:等待启动,调用start启动。
运行状态:具有执行资格和执行权。
临时状态(阻塞):有执行资格,但是没有执行权。
冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
消忙状态:stop()方法,或者run方法结束。
注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常
t1.start();
t2.start();
for(int x=0; x<60; x++){
System.out.println("main....."+x);
}
}
}
线程的安全
导致安全问题的出现的原因:
1.多个线程访问出现延迟
2.线程随机性
线程安全问题在理想状态下,不容易出现,但是一旦出现对软件的影响是非常大的
解决方法:同步(synchronized)
格式:
synchronized(对象){
需要同步的代码;
}
同步可以解决安全问题的根本原因就是在那个对象上,该对象如同锁的功能。
4个窗口售票示例:
class Ticket implements Runnable {
private int tick = 1000;
Object obj = new Object();//建立一个obj类对象,供synchronized作参数
public void run(){
while(true){
synchronized(obj) { //同步锁
if(tick>0)
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
}
}
}
}
class TicketDemo{
public static void main(String[] args) {
Ticket t = new Ticket();//创建Ticket对象供Thread对象作构造函数的参数用
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
//开始4个线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
同步的前提:
|---->同步需要两个或者两个以上的线程
|---->多个线程使用的是同一个锁
未满足这两个条件,不能称其同步。
同步的弊端:
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行的效率。
同步函数
格式:在函数上加上synchronized修饰符即可。
同步函数使用的锁:函数需要被对象调用,那么函数都有一个所属对象引用,就是this。
所以同步函数使用的锁是this。
关于死锁
同步中嵌套同步,但是锁却不同,就会产生死锁
如:A线程持有A锁,B线程持有B锁,让A线程去抢夺B锁,B线程去抢夺A锁
class Dead implements Runnable {
private booleanb = false;
Dead(booleanb){
this.b = b;
}
public void run(){
while(true){
if(b){
synchronized(Locks.locka){
//0线程,持有了A锁
System.out.println(Thread.currentThread().getName()+"locka...+..if");
//等待B锁
synchronized(Locks.lockb){
System.out.println(Thread.currentThread().getName()+"lockb...+..if");
}
}
}
else{
synchronized(Locks.lockb) {
//1线程就进来了,持有了B锁
System.out.println(Thread.currentThread().getName()+"lockb...+..else");
synchronized(Locks.locka) { //等待获得A锁
System.out.println(Thread.currentThread().getName()+"locka...+..else");
}
}
}
}
}
}
class Locks { //创造锁
public static Object locka = new Object();
public static Object lockb = new Object();
}
class DeadLock {
public static void main(String[]args){
Dead d1 = new Dead(true);
Dead d2 = new Dead(false);
Thread t1 = new Thread(d1);
Thread t2 = new Thread(d2);
t1.start();
t2.start();
}
}
线程间通信
其实就是多个线程在操作同一个资源,但是操作的动作不同。
使用同步操作同一资源的示例:
/*
有一个资源
一个线程往里存东西,如果里边没有的话
一个线程往里取东西,如果里面有得话
*/
//资源
class Resource {
private String name;
private String sex;
private boolean flag=false;
public synchronized void setInput(String name,String sex){
if(flag){
try{wait();}catch(Exception e){}//如果有资源时,等待资源取出
}
this.name=name;
this.sex=sex;
flag=true;//表示有资源
notify();//唤醒等待
}
public synchronized void getOutput(){
if(!flag){
try{wait();}catch(Exception e){}//如果木有资源,等待存入资源
}
System.out.println("name="+name+"---sex="+sex);//这里用打印表示取出
flag=false;//资源已取出
notify();//唤醒等待
}
}
//存线程
class Input implements Runnable {
private Resource r;
Input(Resource r){
this.r=r;
}
public void run() { //复写run方法
int x=0;
while(true)
{
if(x==0)//交替打印
{
r.setInput("张三",".....man");
}
else
{
r.setInput("李四","..woman");
}
x=(x+1)%2;//控制交替打印
}
}
}
//取线程
class Output implements Runnable {
private Resource r;
Output(Resource r){
this.r=r;
}
public void run(){ //复写run方法
while(true) {
r.getOutput();
}
}
}
class ResourceDemo {
public static void main(String[] args) {
Resource r = new Resource();//表示操作的是同一个资源
new Thread(new Input(r)).start();//开启存线程
new Thread(new Output(r)).start();//开启取线程
}
}
问题:
1)wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
a,这些方法存在与同步中。
b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。
c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
2)wait(),sleep()有什么区别?
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
3)为甚么要定义notifyAll?
因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。
JDK1.5中提供了多线程升级解决方案。
将同步synchronized替换成显示的Lock操作。将Object中wait,notify,notifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。
升级解决方案的示例:
/*
生产者生产商品,供消费者使用
有两个或者多个生产者,生产一次就等待消费一次
有两个或者多个消费者,等待生产者生产一次就消费掉
*/
import java.util.concurrent.locks.*;
class Resource {
private String name;
private int count=1;
private boolean flag = false;
//多态
private Lock lock=new ReentrantLock();
//创建两Condition对象,分别来控制等待或唤醒本方和对方线程
Condition condition_pro=lock.newCondition();
Condition condition_con=lock.newCondition();
//p1、p2共享此方法
public void setProducer(String name)throws InterruptedException {
lock.lock();//锁
try{
while(flag)//重复判断标识,确认是否生产
condition_pro.await();//本方等待
this.name=name+"......"+count++;//生产
System.out.println(Thread.currentThread().getName()+"...生产..."+this.name);//打印生产
flag=true;//控制生产\消费标识
condition_con.signal();//唤醒对方
}
finally{
lock.unlock();//解锁,这个动作一定执行
}
}
//c1、c2共享此方法
public void getConsumer()throws InterruptedException {
lock.lock();
try {
while(!flag)//重复判断标识,确认是否可以消费
condition_con.await();
System.out.println(Thread.currentThread().getName()+".消费."+this.name);//打印消费
flag=false;//控制生产\消费标识
condition_pro.signal();
}
finally{
lock.unlock();
}
}
}
//生产者线程
class Producer implements Runnable {
private Resource res;
Producer(Resource res){
this.res=res;
}
//复写run方法
public void run(){
while(true){
try{
res.setProducer("商品");
}
catch (InterruptedException e)
{
}
}
}
}
//消费者线程
class Consumer implements Runnable{
private Resource res;
Consumer(Resource res) {
this.res=res;
}
//复写run
public void run(){
while(true){
try{
res.getConsumer();
}
catch (InterruptedException e)
{
}
}
}
}
class ProducerConsumer {
public static void main(String[] args) {
Resource res=new Resource();
new Thread(new Producer(res)).start();//第一个生产线程 p1
new Thread(new Consumer(res)).start();//第一个消费线程 c1
new Thread(new Producer(res)).start();//第二个生产线程 p2
new Thread(new Consumer(res)).start();//第二个消费线程 c2
}
}
停止线程
1.定义循环结束标记(run方法结束)
因为线程运行代码一般都是循环,只要控制了循环即可。
2.使用interrupt(中断)方法。
该方法是结束线程的冻结状态,使线程回到运行状态中来。
注:stop方法已经过时不再使用。
特殊情况:
当线程处于了冻结状态,就不会读取到标记。那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除,强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
Thread类提供该方法 interrupt();
class StopThread implements Runnable{
private boolean flag =true;
public synchornized void run(){
while(flag){
try{
wait();//调用线程等待
}
catch(InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"...Exception");
flag = false;//在异常中控制线程的运行
}
System.out.println(Thread.currentThread().getName()+"....run");
}
}
public void changeFlag() {
flag = false;
}
}
class StopThreadDemo{
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
int num = 0;
while(true) {
if(num++ == 60){
//st.changeFlag();
t1.interrupt();//调用interrupt方法来中断线程,抛出异常
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");
}
}线程类的其他方法
守护线程
setDaemon():将该线程标记为守护线程或用户线程,守护线程就相当于后台线程,当前台线程结束时,后台线程跟着也结束。
注意:该方法必须在启动线程前调用。
该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
join()
join方法 等待该线程终止。
当A线程执行到了B线程的join方法时,A就会等待,等B线程都执行完,A才会执行。join可以用来临时加入线程执行。 如果B线程wait了主线程就挂了,B线程还能继续运行。
setPriority()
更改线程的优先级。三个优先级分别为:
MAX_PRIORITY(最高优先级,10)
MIN_PRIORITY(最低优先级,1)
NORM_PRIORITY(默认优先级,5)
首先调用线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException。在其他情况下,线程优先级被设定为指定的newPriority 和该线程的线程组的最大允许优先级相比较小的一个。
yeild()
暂停当前正在执行的线程对象,并执行其他线程。(使线程交替执行) Thread中的toString方法返回该线程的字符串表现形式,包括线程名称、优先级和线程组。
A开启的该线程,该线程就属于A组。
wait和sleep区别:分析这两个方法:从执行权和锁上来分析:
wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。
sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。
wait:线程会释放执行权,而且线程会释放锁。
Sleep:线程会释放执行权,但是不释放锁。
要理解多线程,就必须理解线程。而要理解线程,就必须知道进程。
1、 进程
是一个正在执行的程序。
每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
2、线程
就是进程中的一个独立的控制单元。线程在控制着进程的执行。只要进程中有一个线程在执行,进程就不会结束。
一个进程中至少有一个线程。
3、多线程
在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程
运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。像这种在一个进程中有多个线程执行的方式,就叫做多线程。
4、多线程存在的意义
多线程的出现能让程序产生同时运行效果。可以提高程序执行效率。
例如:在java.exe进程执行主线程时,如果程序代码特别多,在堆内存中产生了很多对象,而同时对象调用完后,就成了垃圾。如果垃圾过多就有可能是堆内存出现内存不足的现象,只是如果只有一个线程工作的话,程序的执行将会很低效。而如果有另一个线程帮助处理的话,如垃圾回收机制线程来帮助回收垃圾的话,程序的运行将变得更有效率。 下载就是多线程的。 系统在创建进程,线程也是系统创建的。
5、计算机CPU的运行原理
我们电脑上有很多的程序在同时进行,就好像cpu在同时处理这所以程序一样。但是,在一个时刻,单核的cpu只能运行一个程序。而我们看到的同时运行效果,只是cpu在多个进程间做着快速切换动作。
而cpu执行哪个程序,是毫无规律性的。这也是多线程的一个特性:随机性。哪个线程被cpu执行,或者说抢到了cpu的执行权,哪个线程就执行。而cpu不会只执行一个,当执行一个一会后,又会去执行另一个,或者说另一个抢走了cpu的执行权。至于究竟是怎么样执行的,只能由cpu决定
线程状态:
新建:start()
运行:具备执行资格,同时具备执行权;
冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;
临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;
消亡:stop()
创建线程的第一种方式:继承Thread,由子类复写run方法。
1.子类覆盖父类中的run方法,将线程运行的代码存放在run方法中。
2.建立子类对象的同时线程也被创建。
3.通过调用start方法开启线程。
class Demo extends Thread {
public void run() {
for(int i = 0;i<60;i++){
System.out.println(“demo run---+”i);
}
}
}
class ThreadDemo{
public static void main(String[] args){
Demo d = new Demo();//创建一个线程
d.start();//调用start方法执行该线程的run方法
}
}
创建线程的第二种方式:实现一个接口Runnable。
1.子类覆盖接口中的run方法
2.通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。
3.Thread类对象调用start方法开启线程。
class Test implement Runnable
{
public void run(){ //子类覆盖接口中的run方法
for(int x=0; x<60; x++){
System.out.println(“run..."+x);
}
}
}
class ThreadTest {
public static void main(String[] args) {
Test te = new Test();实现Runnable接口的对象
Thread t1 = new Thread(te);将那个对象作为参数传递到Thread构造函数
Thread t2 = new Thread(te);
t1.start();//调用Thread类的start方法开启线程
t2.start();
}
两种线程创建方式的区别:
继承Thread类创建对象:
1.Thread子类无法再从其它类继承(java语言单继承)。
2.编写简单,run()方法的当前对象就是线程对象,可直接操作。
使用Runnable接口创建线程:
1.可以将CPU,代码和数据分开,形成清晰的模型。
2.线程体run()方法所在的类可以从其它类中继承一些有用的属性和方法。
3.有利于保持程序的设计风格一致。
继承Thread类线程代码存放于Thread子类run方法中;
实现Runnable接口线程代码存在于接口的子类run方法中,而且这种方式避免了单继承的局限性,在实际应用中,几乎都采取第二种方式。
为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法,就必须明确该
run方法所属的对象。
sleep方法需要指定睡眠时间,单位是毫秒。
冻结与运行之间的状态:准备就绪。具备了执行资格,但是没有执行权。被创建:等待启动,调用start启动。
运行状态:具有执行资格和执行权。
临时状态(阻塞):有执行资格,但是没有执行权。
冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
消忙状态:stop()方法,或者run方法结束。
注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常
sleep方法需要指定睡眠时间,单位是毫秒。
冻结与运行之间的状态:准备就绪。具备了执行资格,但是没有执行权。被创建:等待启动,调用start启动。
运行状态:具有执行资格和执行权。
临时状态(阻塞):有执行资格,但是没有执行权。
冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
消忙状态:stop()方法,或者run方法结束。
注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常
t1.start();
t2.start();
for(int x=0; x<60; x++){
System.out.println("main....."+x);
}
}
}
线程的安全
导致安全问题的出现的原因:
1.多个线程访问出现延迟
2.线程随机性
线程安全问题在理想状态下,不容易出现,但是一旦出现对软件的影响是非常大的
解决方法:同步(synchronized)
格式:
synchronized(对象){
需要同步的代码;
}
同步可以解决安全问题的根本原因就是在那个对象上,该对象如同锁的功能。
4个窗口售票示例:
class Ticket implements Runnable {
private int tick = 1000;
Object obj = new Object();//建立一个obj类对象,供synchronized作参数
public void run(){
while(true){
synchronized(obj) { //同步锁
if(tick>0)
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
}
}
}
}
class TicketDemo{
public static void main(String[] args) {
Ticket t = new Ticket();//创建Ticket对象供Thread对象作构造函数的参数用
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
//开始4个线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
同步的前提:
|---->同步需要两个或者两个以上的线程
|---->多个线程使用的是同一个锁
未满足这两个条件,不能称其同步。
同步的弊端:
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行的效率。
同步函数
格式:在函数上加上synchronized修饰符即可。
同步函数使用的锁:函数需要被对象调用,那么函数都有一个所属对象引用,就是this。
所以同步函数使用的锁是this。
关于死锁
同步中嵌套同步,但是锁却不同,就会产生死锁
如:A线程持有A锁,B线程持有B锁,让A线程去抢夺B锁,B线程去抢夺A锁
class Dead implements Runnable {
private booleanb = false;
Dead(booleanb){
this.b = b;
}
public void run(){
while(true){
if(b){
synchronized(Locks.locka){
//0线程,持有了A锁
System.out.println(Thread.currentThread().getName()+"locka...+..if");
//等待B锁
synchronized(Locks.lockb){
System.out.println(Thread.currentThread().getName()+"lockb...+..if");
}
}
}
else{
synchronized(Locks.lockb) {
//1线程就进来了,持有了B锁
System.out.println(Thread.currentThread().getName()+"lockb...+..else");
synchronized(Locks.locka) { //等待获得A锁
System.out.println(Thread.currentThread().getName()+"locka...+..else");
}
}
}
}
}
}
class Locks { //创造锁
public static Object locka = new Object();
public static Object lockb = new Object();
}
class DeadLock {
public static void main(String[]args){
Dead d1 = new Dead(true);
Dead d2 = new Dead(false);
Thread t1 = new Thread(d1);
Thread t2 = new Thread(d2);
t1.start();
t2.start();
}
}
线程间通信
其实就是多个线程在操作同一个资源,但是操作的动作不同。
使用同步操作同一资源的示例:
/*
有一个资源
一个线程往里存东西,如果里边没有的话
一个线程往里取东西,如果里面有得话
*/
//资源
class Resource {
private String name;
private String sex;
private boolean flag=false;
public synchronized void setInput(String name,String sex){
if(flag){
try{wait();}catch(Exception e){}//如果有资源时,等待资源取出
}
this.name=name;
this.sex=sex;
flag=true;//表示有资源
notify();//唤醒等待
}
public synchronized void getOutput(){
if(!flag){
try{wait();}catch(Exception e){}//如果木有资源,等待存入资源
}
System.out.println("name="+name+"---sex="+sex);//这里用打印表示取出
flag=false;//资源已取出
notify();//唤醒等待
}
}
//存线程
class Input implements Runnable {
private Resource r;
Input(Resource r){
this.r=r;
}
public void run() { //复写run方法
int x=0;
while(true)
{
if(x==0)//交替打印
{
r.setInput("张三",".....man");
}
else
{
r.setInput("李四","..woman");
}
x=(x+1)%2;//控制交替打印
}
}
}
//取线程
class Output implements Runnable {
private Resource r;
Output(Resource r){
this.r=r;
}
public void run(){ //复写run方法
while(true) {
r.getOutput();
}
}
}
class ResourceDemo {
public static void main(String[] args) {
Resource r = new Resource();//表示操作的是同一个资源
new Thread(new Input(r)).start();//开启存线程
new Thread(new Output(r)).start();//开启取线程
}
}
问题:
1)wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
a,这些方法存在与同步中。
b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。
c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
2)wait(),sleep()有什么区别?
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
3)为甚么要定义notifyAll?
因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。
JDK1.5中提供了多线程升级解决方案。
将同步synchronized替换成显示的Lock操作。将Object中wait,notify,notifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。
升级解决方案的示例:
/*
生产者生产商品,供消费者使用
有两个或者多个生产者,生产一次就等待消费一次
有两个或者多个消费者,等待生产者生产一次就消费掉
*/
import java.util.concurrent.locks.*;
class Resource {
private String name;
private int count=1;
private boolean flag = false;
//多态
private Lock lock=new ReentrantLock();
//创建两Condition对象,分别来控制等待或唤醒本方和对方线程
Condition condition_pro=lock.newCondition();
Condition condition_con=lock.newCondition();
//p1、p2共享此方法
public void setProducer(String name)throws InterruptedException {
lock.lock();//锁
try{
while(flag)//重复判断标识,确认是否生产
condition_pro.await();//本方等待
this.name=name+"......"+count++;//生产
System.out.println(Thread.currentThread().getName()+"...生产..."+this.name);//打印生产
flag=true;//控制生产\消费标识
condition_con.signal();//唤醒对方
}
finally{
lock.unlock();//解锁,这个动作一定执行
}
}
//c1、c2共享此方法
public void getConsumer()throws InterruptedException {
lock.lock();
try {
while(!flag)//重复判断标识,确认是否可以消费
condition_con.await();
System.out.println(Thread.currentThread().getName()+".消费."+this.name);//打印消费
flag=false;//控制生产\消费标识
condition_pro.signal();
}
finally{
lock.unlock();
}
}
}
//生产者线程
class Producer implements Runnable {
private Resource res;
Producer(Resource res){
this.res=res;
}
//复写run方法
public void run(){
while(true){
try{
res.setProducer("商品");
}
catch (InterruptedException e)
{
}
}
}
}
//消费者线程
class Consumer implements Runnable{
private Resource res;
Consumer(Resource res) {
this.res=res;
}
//复写run
public void run(){
while(true){
try{
res.getConsumer();
}
catch (InterruptedException e)
{
}
}
}
}
class ProducerConsumer {
public static void main(String[] args) {
Resource res=new Resource();
new Thread(new Producer(res)).start();//第一个生产线程 p1
new Thread(new Consumer(res)).start();//第一个消费线程 c1
new Thread(new Producer(res)).start();//第二个生产线程 p2
new Thread(new Consumer(res)).start();//第二个消费线程 c2
}
}
停止线程
1.定义循环结束标记(run方法结束)
因为线程运行代码一般都是循环,只要控制了循环即可。
2.使用interrupt(中断)方法。
该方法是结束线程的冻结状态,使线程回到运行状态中来。
注:stop方法已经过时不再使用。
特殊情况:
当线程处于了冻结状态,就不会读取到标记。那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除,强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
Thread类提供该方法 interrupt();
class StopThread implements Runnable{
private boolean flag =true;
public synchornized void run(){
while(flag){
try{
wait();//调用线程等待
}
catch(InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"...Exception");
flag = false;//在异常中控制线程的运行
}
System.out.println(Thread.currentThread().getName()+"....run");
}
}
public void changeFlag() {
flag = false;
}
}
class StopThreadDemo{
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
int num = 0;
while(true) {
if(num++ == 60){
//st.changeFlag();
t1.interrupt();//调用interrupt方法来中断线程,抛出异常
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");
}
}线程类的其他方法
守护线程
setDaemon():将该线程标记为守护线程或用户线程,守护线程就相当于后台线程,当前台线程结束时,后台线程跟着也结束。
注意:该方法必须在启动线程前调用。
该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
join()
join方法 等待该线程终止。
当A线程执行到了B线程的join方法时,A就会等待,等B线程都执行完,A才会执行。join可以用来临时加入线程执行。 如果B线程wait了主线程就挂了,B线程还能继续运行。
setPriority()
更改线程的优先级。三个优先级分别为:
MAX_PRIORITY(最高优先级,10)
MIN_PRIORITY(最低优先级,1)
NORM_PRIORITY(默认优先级,5)
首先调用线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException。在其他情况下,线程优先级被设定为指定的newPriority 和该线程的线程组的最大允许优先级相比较小的一个。
yeild()
暂停当前正在执行的线程对象,并执行其他线程。(使线程交替执行) Thread中的toString方法返回该线程的字符串表现形式,包括线程名称、优先级和线程组。
A开启的该线程,该线程就属于A组。
wait和sleep区别:分析这两个方法:从执行权和锁上来分析:
wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。
sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。
wait:线程会释放执行权,而且线程会释放锁。
Sleep:线程会释放执行权,但是不释放锁。
相关文章推荐
- Eclipse背景颜色设置 保护眼睛从这开始
- java--设计模式
- java--关键字
- java--string
- java--IO 使用带缓冲功能的字节流复制文件
- JAVA--集合
- java--内部类的总结
- java--异常及异常的处理机制
- JAVA--IO流(三)
- JAVA--IO流(二)
- java--线程的生命周期
- Java--继承、方法重载、方法覆盖总结
- java--JDK1.5新特性
- java--IO流
- java--用代码证明,在try中写了return,后面又写了finally,是先执行return还是先执行fianlly?
- Eclipse设置、调优、使用
- java--面向对象
- jdk安装错误error could not open jvm.cfg
- java--选择排序
- eclipse Kepler 安装 SVN Plugin + Groovy Plugin