Java笔记(二)
1 多线程
1.1 多线程、进程、线程
进程: 正在执行中的程序,其实是应用程序在内存中运行的那片空间。
线程: 进程中的一个执行单元,负责进程中的程序的运行,一个进程中至少要有一个线程。
多线程: 一个进程中是可以有多个线程的,这个应用程序也可以被称为多线程程序。
程序启动了多线程,可以实现多部分程序同时进行,即所谓的并发。
1.2 多线程的示例
多线程的使用可以合理使用CPU的资源,如果线程过多会导致性能降低。
在之前代码中,JVM启动后,必然有一个执行路径(线程)从main方法开始的,一直执行到main方法结束。这个线程在java中称为 主线程 。
当 主线程 在这个程序中执行时,如果遇到了循环而导致在指定位置停留时间过长。无法执行下面程序。 多线程主要实现 一个主程序负责执行其中一个循环,另外一个线程负责其他代码的执行。
创建线程有两种方式:
- 继承Thread类
- 定义一个类继承Thread;
- 重写run方法
- 创建子类对象,就是创建线程对象;
- 调用start方法,开启线程并让线程执行,同时还会告诉JVM去调用run方法。
为什么要去继承Thread类,而不是直接使用Thread类对象?
继承Thread类,因为Thread类描述线程事物,具备线程应该有功能。
而不直接使用Thread类对象即Thread t1 = new Thread(); t1.start();
是因为在Thread类中的run方法没有做什么实质事情,也没有定义我们需要让线程执行的代码
//1、继承Thread类 class Demo extends Thread { private String name; Demo(String name) { this.name = name; } //2、重写run方法 public void run() { for(int x=1; x<=20; x++) { System.out.println("name="+name+"....."+x); } } } class ThreadDemo { public static void main(String[] args) { //3、创建两个线程对象 Demo d1 = new Demo("小强"); Demo d2 = new Demo("旺财"); //4、调用start、run d2.start();//开启d2这个线程 d1.run();//由主线程负责 } }
线程对象调用run方法和调用start方法区别?
调用run方法不开启线程,仅是对象调用方法。调用start开启线程,并让jvm调用run方法在开启的线程中执行。
多线程内存:
多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间,进行方法压栈和弹栈。
当执行线程的任务结束了,线程自动在栈内存中释放。但是所有的执行线程都结束了。进程也就会结束。
【拓展】
1 Thread.currentThread()//获取当前线程对象 2 Thread.currentThread().getName();//获取名称
- 创建线程第二种方式,实现Runnable接口
- 定义类实现Runnable接口,避免了继承Thread类的单继承局限性。
- 覆盖接口中的run方法,将线程任务代码定义到run方法中
- 创建Thread类的对象,只有创建Thread类的对象才可以创建线程。
- 将Runnable接口的子类对象作为参数传递给Thread类的构造函数。因为线程已被封装到 Runnable接口的run方法中,而这个run方法所属Runnable接口的子类对象,所以将这个 子类对象作为参数传递给Thread的构造函数,这样,线程对象创建时就可以明确要运行的线程的任务。
- 调用Tread类的start方法开启线程
第二种方式实现Runnable接口避免了单继承的局限性,所以较为实用。
//1、定义类实现Runnable接口 class Demo implements Runnable { private String name; Demo(String name) { this.name = name; } //2、覆盖了接口Runnable中的run方法 public void run() { for(int x = 1; x<=20; x++) { System.out.println("name="+name+"..."+Thread.currentThread().getName()+"..."+x); } } } class ThreadDemo2 { public static void main(String[] args) { //3、创建Runnable子类的对象,它不是线程对象 Demo d = new Demo(); //4、创建Thread类的对象,将Runnable接口的子类对象作为参数传递给Thread类的构造函数 Thread t1 = new Thread(d); Thread t2 = new Thread(d); //5、启动线程 t1.start(); t2.start(); } }
创建线程的两种方式区别---即继承Thread类和实现Runnable接口区别:
继承Thread类: 线程对象和线程任务耦合在一起,一旦创建Thread类的子类对象,即是线程对象,也有线程任务。
实现runnable接口:
将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。实现Runnable接口的方式,更加的符合面向对象,线程分为两部分。
一部分线程对象,一部分线程任务。类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。
//通过源码的形式讲解runnable接口的子类对象作为参数传递给Thread构造函数的原因 class Thread{ private Runnable target; Thread(Runnable target) { this.target = target; } public void run() { if (target != null) { target.run(); } } public void start() { run(); } } Runnable d = new Demo(); //创建对象 Thread t = new Thread(d); //线程任务 t.start();
1.3 多线程示例
发生了线程安全问题:出现了错误的数据。0 -1 -2
问题产生的原因;
1,线程任务中在操作共享的数据。
2,线程任务操作共享数据的代码有多条(运算有多个)。
解决思路:
只要让一个线程在执行线程任务时将多条操作共享数据的代码执行完,
在执行过程中,不要让其他线程参与运算。就哦了。
代码体现呢?
Java中解决此问题通过代码块来完成的。也可以用同步函数来解决,下面1.5节会讲到。
这个代码块:同步代码块 synchronized
格式:
1 synchronized(对象) 2 { 3 //需要被同步的代码。 4 }
同步好处: 解决多线程安全问题。
同步弊端: 降低了程序的性能。
同步前提: 必须保证多个线程在同步中使用的是同一个锁。类似于火车卫生间
解决了什么问题?
当多线程安全问题发生时,加入了同步后,问题依旧,就要通过这个同步的前提来判断同步是否写正确。
class Ticket implements Runnable { //1,描述票的数量。 private int tickets = 100; //2,售票的动作,这个动作需要被多线程执行,那就是线程任务代码。需要定义run方法中。 //线程任务中通常都有循环结构。 private Object obj = new Object(); public void run() { while(true) { synchronized(obj) { if(tickets>0) { //要让线程在这里稍停,模拟问题的发生。sleep 看到了0 -1 -2 错误的数据,这就是传说中的多线程安全问题。 try{Thread.sleep(1);}catch(InterruptedException e){/*未写处理方式,后面讲*/} System.out.println(Thread.currentThread().getName()+"....."+tickets--);//打印线程名称。 } } } } } class ThreadDemo3 { public static void main(String[] args) { //1,创建Runnable接口的子类对象。 Ticket t = new Ticket(); //2,创建四个线程对象。并将Runnable接口的子类对象作为参数传递给Thread的构造函数。 Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); Thread t4 = new Thread(t); //3,开启四个线程。 t1.start(); t2.start(); t3.start(); t4.start(); } }
1.4 线程状态转换及转化
说明:
线程共包括以下5种状态。
1 新建状态(New) 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread();
2 就绪状态(Runnable)
也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,
从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3 运行状态(Running)
线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入 到运行状态。 具备了CPU的执行资格和具备了CPU的执行权
4 阻塞状态(Blocked)
阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。 直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
- 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
- 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
- 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。就是所谓的“冻结”--没有CPU执行资格和CPU执行权
5 死亡状态(Dead) 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
1.5 同步函数使用的锁&和同步代码块的区别
同步函数
public synchronized void sale()//同步函数,使用的锁对象 this。 { if(tickets>0) { try{Thread.sleep(10);}catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印线程名称。 } }
同步函数和同步代码块有什么区别吗?
同步函数使用的锁是固定的this。当线程任务只需要一个同步时完全可以使用同步函数。
同步代码块使用的锁可以是任意对象。当线程任务中需要多个同步时,必须通过锁来区分,这时必须使用同步代码块。
同步代码块较为常用。【注:锁是一个对象】
如何验证:
写一个同步代码块,写一个同步函数,如果同步代码块中的锁对象和同步函数中的锁对象是同一个,
就同步了,就没有错误的数据了。如果不是同一个锁对象,就不同步出现错误数据。
让两个线程,一个线程在同步代码块中执行,一个线程在同步函数中执行。总结:同步函数使用的锁时this。
class Ticket implements Runnable { private int tickets = 100; private Object obj = new Object(); boolean flag = true; public void run() { if(flag) { while(true) { synchronized(this) { if(tickets>0) { try { Thread.sleep(10); } catch(InterruptedException e) {} System.out.println(Thread.currentThread().getName()+"...obj..."+tickets--);//打印线程名称。 } } } } else { while(true) { this.sale(); } } } public synchronized void sale()//同步函数,使用的锁对象 this。 { if(tickets>0) { try{Thread.sleep(10);}catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印线程名称。 } } } class ThreadDemo4 { public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); t1.start(); try{Thread.sleep(10);}catch(InterruptedException e){} //切换标记,之前,让主线程停一会,这时就只有一个t1线程在,它就会执行同步代码块。 t.flag = false; t2.start(); } }
【备注】若同步函数设置为static,则其访问的成员变量也应该是static,运行程序之后,出现了错误的结果。
【原因】static 同步函数,因为静态存在的时候,此时this没有任何对象所指向。使用的锁不是this,而是字节码文件对象----即 类名.class
解决方法:
/* static 同步函数,使用的锁不是this,而是字节码文件对象, 类名.class */ class Ticket implements Runnable { private static int tickets = 100; //此处添加static private Object obj = new Object(); boolean flag = true; public void run() { if(flag){ while(true){ synchronized( Ticket.class ){ //此处将this更改为Ticket.class 字节码文件对象 if(tickets>0){ try{Thread.sleep(10);}catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()+"...obj..."+tickets--);//打印线程名称。 } } } } else{ while(true){ this.sale(); } } } public static synchronized void sale()// { if(tickets>0) { try{Thread.sleep(10);}catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印线程名称。 } } } class ThreadDemo5 { public static void main(String[] args) //此处添加static { Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); t1.start(); try{Thread.sleep(10);}catch(InterruptedException e){} //切换标记,之前,让主线程停一会,这时就只有一个t1线程在,它就会执行同步代码块。 t.flag = false; t2.start(); } }
1.6 单例兰懒汉式并发问题
//饿汉式。 多线程并发饿汉式没问题。 class Single { private static final Single s = new Single(); private Single(){} public static Single getInstance() { return s; } } //懒汉式。 class Single { private static Single s = null; private Single(){} /* 并发访问会有安全隐患,所以加入同步机制解决安全问题。 但是,同步的出现降低了效率。 可以通过双重判断的方式,解决效率问题,减少判断锁的次数。只要有一个线程将对象创建完,则s!=null,因而其他线程就不会读取到同步锁。 */ public static Single getInstance() { if(s==null) { synchronized(Single.class)//不能同步函数,因为就会变成单线程,只能同步共同部分的操作即可。 { if(s==null) // -->0 -->1 s = new Single(); } } return s; } } class Demo implements Runnable { public void run() { Single.getInstance(); } } class ThreadDemo6 { public static void main(String[] args) { System.out.println("Hello World!"); } }
1.7 多线程同步造成:死锁
同步的另一个弊端:
情况之一:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。
这时容易引发一种现象:死锁。
这种情况能避免就避免掉。
//Thread-0 synchronized(obj1) { -->thread-0 obj1 synchronized(obj2) { } } //Thread-1 synchronized(obj2) { Thread-1 obj2 synchronized(obj1) { } }
总结:此时线程一和线程2可能同时会获得obj2对象,就会引起冲突,形成死锁。
1.8 多线程等待唤醒
/* 多线程中最为常见的应用案例: 生产者消费者问题。 生产和消费同时执行,需要多线程。 但是执行的任务却不相同,处理的资源确实相同的:线程间的通信。 1,描述一下资源。 2,描述生产者,因为具备着自己的任务。 3,描述消费者,因为具备着自己的任务。 问题1:数据错误:已经被生产很早期的商品,才被消费到。 出现线程安全问题,加入了同步解决。使用同步函数。 问题已解决:不会在消费到之前很早期的商品。 问题2:发现了连续生产却没有消费,同时对同一个商品进行多次消费。 希望的结果应该是生产一个商品,就被消费掉。生产下一个商品。 搞清楚几个问题? 生产者什么时候生产呢?消费者什么时候应该消费呢? 当盘子中没有面包时,就生产,如果有了面包,就不要生产。 当盘子中已有面包时,就消费,如果没有面包,就不要消费。 生产者生产了商品后应该告诉消费者来消费。这时的生产者应该处于等待状态。 消费者消费了商品后,应该告诉生产者,这时消费者处于等待状态。 等待:wait(); 告诉:notify();//唤醒 问题解决:实现生产一个消费一个。 ===================== 等待/唤醒机制。 wait(): 会让线程处于等待状态,其实就是将线程临时存储到了线程池中。 notify():会唤醒线程池中任意一个等待的线程。 notifyAll():会唤醒线程池中所有的等待线程。 记住:这些方法必须使用在同步中,因为必须要标识wait,notify等方法所属的锁。 同一个锁上的notify,只能唤醒该锁上的被wait的线程。 为什么这些方法定义在Object类中呢? 因为这些方法必须标识所属的锁,而锁可以是任意对象,任意对象可以调用的方法必然时Object类中的方法。 举例:小朋友抓人游戏。 */ //1,描述资源。属性:商品名称和编号, 行为:对商品名称赋值,获取商品。 class Resource { private String name; private int count = 1; //定义标记。 private boolean flag = false; //1,提供设置的方法。 public synchronized void set(String name) { if(flag) try{this.wait();}catch(InterruptedException e){} //给成员变量赋值并加上编号。 this.name = name + count; //编号自增。 count++; //打印生产了哪个商品。 System.out.println(Thread.currentThread().getName()+"......生产者...."+this.name); //将标记改为true。 flag = true; //唤醒消费者。 this.notify(); } public synchronized void out() { if(!flag) try{this.wait();}catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name); //将标记该为false。 flag = false; //唤醒生产者。 this.notify(); } } //2,描述生产者。 class Producer implements Runnable { private Resource r ; // 生产者一初始化就要有资源,需要将资源传递到构造函数中。 Producer(Resource r) { this.r = r; } public void run() { while(true) { r.set("面包"); } } } //3,描述消费者。 class Consumer implements Runnable { private Resource r ; // 消费者一初始化就要有资源,需要将资源传递到构造函数中。 Consumer(Resource r) { this.r = r; } public void run() { while(true) { r.out(); } } } class ThreadDemo9 { public static void main(String[] args) { //1,创建资源对象。 Resource r = new Resource(); //2,创建线程任务。 Producer pro = new Producer(r); Consumer con = new Consumer(r); //3,创建线程。 Thread t1 = new Thread(pro); Thread t2 = new Thread(con); t1.start(); t2.start(); } }
1.9 多线程多消费模式问题
/* 多生产多消费。 问题1;生产了商品没有被消费,同一个商品被消费多次。 Thread-0......生产者....面包2499//没有被消费。 Thread-1......生产者....面包2500 Thread-3....消费者....面包2500 被唤醒的线程没有判断标记,造成问题1的产生。 解决:只要让被唤醒的线程必须判断标记就可以了。将if判断标记的方式改为while判断标记。记住:多生产多消费,必须时while判断条件。 问题2:发现while判断后,死锁了。 原因:生产方唤醒了线程池中生产方的线程。本方唤醒了本方。 解决:希望本方要唤醒对方,没有对应的方法,所以只能唤醒所有。 其实还有一些遗憾的,效率低了。 */ class Resource { private String name; private int count = 1; //定义标记。 private boolean flag = false; //1,提供设置的方法。 public synchronized void set(String name)// { while(flag) try{this.wait();}catch(InterruptedException e){}// t1等 t2等 //给成员变量赋值并加上编号。 this.name = name + count;//商品1 商品2 商品3 //编号自增。 count++;//2 3 4 //打印生产了哪个商品。 System.out.println(Thread.currentThread().getName()+"......生产者...."+this.name);//生产 商品1 生产商品2 生产商品3 //将标记改为true。 flag = true; //唤醒消费者。 this.notifyAll(); } public synchronized void out()// { while(!flag) try{this.wait();}catch(InterruptedException e){}//t3等 //t4等 System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);//消费 商品1 //将标记该为false。 flag = false; //唤醒生产者。 this.notifyAll(); } } //2,描述生产者。 class Producer implements Runnable { private Resource r ; // 生产者一初始化就要有资源,需要将资源传递到构造函数中。 Producer(Resource r) { this.r = r; } public void run() { while(true) { r.set("面包"); } } } //3,描述消费者。 class Consumer implements Runnable { private Resource r ; // 消费者一初始化就要有资源,需要将资源传递到构造函数中。 Consumer(Resource r) { this.r = r; } public void run() { while(true) { r.out(); } } } class ThreadDemo10 { public static void main(String[] args) { //1,创建资源对象。 Resource r = new Resource(); //2,创建线程任务。 Producer pro = new Producer(r); Consumer con = new Consumer(r); //3,创建线程。 Thread t1 = new Thread(pro); Thread t2 = new Thread(pro); Thread t3 = new Thread(con); Thread t4 = new Thread(con); t1.start(); t2.start(); t3.start(); t4.start(); } }
1.10 多线程练习问题一
/*
需求:
资源有姓名和性别。
两个线程,
一个负责给姓名和性别赋值,
一个负责获取姓名和性别的值。
参阅ThreadTest2.java文件
要求1,运行一下,解决程序的 "妖"的问题。
分析过程:
加入同步,必须保证同一个锁,解决妖的问题。
要求2,实现正确数据的间隔输出 如
张飞--男
rose--女女女
张飞--男
rose--女女女
使用等待唤醒机制。
wait(),notify(),notifyAll();
对于等待都需要判断,定义条件。
要求3,对代码进行重构。
将name,sex私有化,资源类提供对其访问的方法。
要求4,将程序改成JDK1.5的Lock Condition接口。
*/
//描述资源。
class Resource
{
String name;
String sex;
//定义标记,
boolean flag = false;
}
//赋值线程任务
class Input implements Runnable
{
private Resource r;
// private Object obj = new Object();
Input(Resource r)//任务一初始化就必须有要处理的资源。
{
this.r = r;
}
public void run()
{
int x = 0;
while(true)
{
synchronized(r) //解决问题一,加入同步
{
if(r.flag)
try{r.wait();}catch(InterruptedException e){} //解决问题二:间隔输出
if(x==0)
{
r.name = "张飞";
r.sex = "男";
}
else
{
r.name = "rose";
r.sex = "女女女女";
}
r.flag = true;
r.notify();
}
x = (x+1)%2;//实现切换。
}
}
}
//获取值线程任务
class Output implements Runnable
{
private Resource r ;
// private Object obj = new Object();
Output(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
synchronized(r)
{
if(!r.flag)
try{r.wait();}catch(InterruptedException e){}
System.out.println(r.name+"....."+r.sex);
r.flag = false;
r.notify();
}
}
}
}
class ThreadTest2_2
{
public static void main(String[] args)
{
Resource r = new Resource();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
1.11 多线程练习问题一的要求3
/* 需求: 资源有姓名和性别。 两个线程, 一个负责给姓名和性别赋值, 一个负责获取姓名和性别的值。 参阅ThreadTest2.java文件 要求1,运行一下,解决程序的 "妖"的问题。 分析过程: 加入同步,必须保证同一个锁,解决妖的问题。 要求2,实现正确数据的间隔输出 如 张飞--男 rose--女女女 张飞--男 rose--女女女 使用等待唤醒机制。 wait(),notify(),notifyAll(); 对于等待都需要判断,定义条件。 要求3,对代码进行重构。 将name,sex私有化,资源类提供对其访问的方法。 要求4,将程序改成JDK1.5的Lock Condition接口。 Lock替换了 同步函数或者同步代码块。 Condition替代了 监视器方法,将监视器方法从锁上分离出来,单独封装成Condition对象。 */ //描述资源。 class Resource { private String name; private String sex; //定义标记, private boolean flag = false; //赋值功能。 public synchronized void set(String name,String sex) { if(flag) try{this.wait();}catch(InterruptedException e){} this.name = name; this.sex = sex; flag = true; this.notify(); } //获取值。 public synchronized void out() { if(!flag) try{this.wait();}catch(InterruptedException e){} System.out.println(name+"------"+sex); flag = false; this.notify(); } } //赋值线程任务 class Input implements Runnable { private Resource r; Input(Resource r)//任务一初始化就必须有要处理的资源。 { this.r = r; } public void run() { int x = 0; while(true) { if(x==0) { r.set("张飞","男"); } else { r.set("rose","女女女女"); } x = (x+1)%2;//实现切换。 } } } //获取值线程任务 class Output implements Runnable { private Resource r ; Output(Resource r) { this.r = r; } public void run() { while(true) { r.out(); } } } class ThreadTest2_3 { public static void main(String[] args) { Resource r = new Resource(); Input in = new Input(r); Output out = new Output(r); Thread t1 = new Thread(in); Thread t2 = new Thread(out); t1.start(); t2.start(); } }
1.12 多线程问题一---要求四
/* 需求: 资源有姓名和性别。 两个线程, 一个负责给姓名和性别赋值, 一个负责获取姓名和性别的值。 参阅ThreadTest2.java文件 要求1,运行一下,解决程序的 "妖"的问题。 分析过程: 加入同步,必须保证同一个锁,解决妖的问题。 要求2,实现正确数据的间隔输出 如 张飞--男 rose--女女女 张飞--男 rose--女女女 使用等待唤醒机制。 wait(),notify(),notifyAll(); 对于等待都需要判断,定义条件。 要求3,对代码进行重构。 将name,sex私有化,资源类提供对其访问的方法。 要求4,将程序改成JDK1.5的Lock Condition接口。 Lock替换了 同步函数或者同步代码块。 Condition替代了 监视器方法,将监视器方法从锁上分离出来,单独封装成Condition对象。 */ import java.util.concurrent.locks.*; //描述资源。 class Resource { private String name; private String sex; //定义标记, private boolean flag = false; //1先创建锁对象。 private final Lock lock = new ReentrantLock(); //2通过锁对象获取监视器对象。 private Condition con = lock.newCondition(); //赋值功能。 public void set(String name,String sex) { //3锁--封装 lock.lock(); try{ if(flag) try{con.await();}catch(InterruptedException e){} this.name = name; this.sex = sex; flag = true; con.signal(); }finally{ lock.unlock(); } } //获取值。 public void out() { lock.lock(); try{ if(!flag) try{con.await();}catch(InterruptedException e){} System.out.println(name+"------"+sex); flag = false; con.signal(); }finally{ lock.unlock(); } } } //赋值线程任务 class Input implements Runnable { private Resource r; Input(Resource r)//任务一初始化就必须有要处理的资源。 { this.r = r; } public void run() { int x = 0; while(true) { if(x==0) { r.set("张飞","男"); } else { r.set("rose","女女女女"); } x = (x+1)%2;//实现切换。 } } } //获取值线程任务 class Output implements Runnable { private Resource r ; Output(Resource r) { this.r = r; } public void run() { while(true) { r.out(); } } } class ThreadTest2_4 { public static void main(String[] args) { Resource r = new Resource(); Input in = new Input(r); Output out = new Output(r); Thread t1 = new Thread(in); Thread t2 = new Thread(out); t1.start(); t2.start(); } }
1.12 多线程细节---面试问题
1,面试题: sleep方法和wait方法异同点是什么?
相同点 :可以让线程处于冻结状态。
不同点 :
1,
sleep必须指定时间。
wait可以指定时间,也可以不指定时间。
2,
sleep时间到,线程处于临时阻塞或者运行。
wait如果没有时间,必须要通过notify或者notifyAll唤醒。
3,
sleep不一定非要定义在同步中。
wait必须定义在同步中。
4,
都定义在同步中,
线程执行到sleep,不会释放锁。
线程执行到wait,会释放锁。
synchronized(obj) { //sleep(5000); wait();//0 1 2 code.... } synchronized(obj) { notifyAll();//3 code.... }
2,线程如何停止呢?★★★★
stop方法过时了,看起描述发现,有其他解决方案。
线程结束:就是让线程任务代码执行完,run方法结束。
run方法咋结束呢?
run方法中通常都定义循环,只要控制住循环就哦了。
注意:万一线程在任务中处于了冻结状态,那么它还能去判断标记吗?不能!
咋办?通过查阅stop方法的描述,发现提供了一个解决方法:
如果目标线程等待很长时间,则应使用 interrupt 方法来中断该等待
所谓的中断并不是停止线程。
interrupt的功能是 将线程的冻结状态清除,让线程恢复到的运行状态(让线程重新具备cpu的执行资格)。
因为时强制性的所以会有异常InterruptedException发生,可以在catch中捕获异常,
在异常处理中,改变标记让循环结束,让run方法结束。
//演示停止线程。 class Demo implements Runnable { private boolean flag = true; public synchronized void run() { while(flag) { try { wait();//t1 t2 } catch (InterruptedException e) { System.out.println(Thread.currentThread().toString()+"....."+e.toString()); changeFlag(); } System.out.println(Thread.currentThread().getName()+"----->"); } } //对标记的修改方法。 public void changeFlag() { flag = false; } } class StopThreadDemo { public static void main(String[] args) { Demo d = new Demo(); Thread t1 = new Thread(d,"旺财"); Thread t2 = new Thread(d,"小强"); t1.start(); //将t2标记为后台线程,守护线程。 // t2.setDaemon(true); t2.start(); int x = 0; while(true) { if(++x == 50)//条件满足。 { // d.changeFlag();//改变线程任务代码的标记,让其他线程也结束。 //对t1线程对象进行中断状态的清除,强制让其恢复到运行状态。 t1.interrupt(); //对t2线程对象进行中断状态的清除,强制让其恢复到运行状态。 t2.interrupt(); break;//跳出循环,主线程可以结束。 } System.out.println("main-------->"+x); } System.out.println("over"); } }
3,守护线程:也可以理解为后台线程,之前创建的都是前台线程。
只要线程调用了setDaemon(true);就可以把线程标记为守护线程。
前台后台线程运行时都是一样的,获取CPU的执行权执行。
只有结束的时候有些不同。
前台线程要通过run方法结束,线程结束。
后台线程也可以通过run方法结束,线程结束,还有另一种情况,
当进程中所有的前台线程都结束了,这时无论后台线程处于什么样的状态,都会结束,从而进程会结束。
进程结束依赖的都是前台线程。
4,线程的优先级:用数字标识的,1-10
其中默认的初始优先级时5 最明显的三个优先级 1,5,10。
setPriority(Thread.MAX_PRIORITY);
5,线程组:ThreadGroup :
可以通过Thread的构造函数明确新线程对象所属的线程组。
线程组的好处,可以对多个同组线程,进行统一的操作。
默认都属于main线程组。
6,join方法,yield方法。
7,开发中,线程的匿名内部类体现。
//面试题:
new Thread(new Runnable() { public void run() { System.out.println("runnable run"); } }){ public void run() { System.out.println("subthread run");//执行。 } }.start();
1.14 多线程---匿名类的使用
class ThreadTest { public static void main(String[] args) { /* new Thread(){ public void run(){ for(int x=0; x<40; x++) { System.out.println(Thread.currentThread().getName()+"...X...."+x); } } }.start(); Runnable r= new Runnable(){ public void run(){ for(int x=0; x<40; x++) { System.out.println(Thread.currentThread().getName()+"...Y...."+x); } } }; new Thread(r).start(); for(int x=0; x<40; x++) { System.out.println(Thread.currentThread().getName()+"...Z...."+x); } System.out.println("Hello World!"); */ //面试题: new Thread(new Runnable() { public void run() { System.out.println("runnable run"); } }){ public void run() { System.out.println("subthread run");//执行。 } }.start(); } } /* class Thread { private Runnable r; Thread(Runnable r) { this.r = r; } public void run() { if(r!=null) { r.run(); } } public void start() { run(); } } class SubThread extends Thread { public void run() { System.out.println("subthread run"); } } Runnable r = new Runnable() { public void run() { System.out.println("runnable run"); } } SubThread t = new SubThread(r); t.start(); */