14to16多线程的原理与使用
导语:
想到在写
- 通过代码了解只有主线程在执行时的弊端继承Thread类
- 创建线程的第二种方式实现Runnable接口
- 线程的安全问题分析原因
- 银行存款的练习
- 同步函数的体现,和同步代码块的区别
- 验证静态同步函数使用的锁时 类名.class
- 单例懒汉式被并发访问的解决方案
- 死锁在售票中的体现
- 简单的死锁程序
- 生产者消费者,发生的问题
- 生产者消费者,加入了等待唤醒机制,监视器方法说明
- 多生产多消费,发生的问题,解决办法
- 多生产多消费,通过JDK1.5解决问题。Lock Condition接口的使用
- Condition案例中的范例,带注释
- 妖的问题与解决。线程安全问题
- 数据间隔问题与解决。等待唤醒机制
- 代码重构问题与解决。同步函数
- JDK1.5升级问题与解决。Lock&Condition
- 停止线程示例。演示了循环结束标记和interrupt方法
- 演示了join方法的特点
- 线程的匿名内部类体现以及面试题
1. 通过代码了解只有主线程在执行时的弊端继承Thread类
/*
通过代码来演示之前和之后的区别。
在之前的代码中,jvm启动后,必然有一个执行路径(线程)从main方法开始的。一直执行到main方法结束。
这个线程在java中称之为主线程。
当主线程在这个程序中执行时,如果遇到了循环而导致在指定为停留时间过长,
无法执行下面的程序。
可不可以实现一个主线程负责执行其中一个循环,由另一个线程负责其他代码的执行。
实现多部分代码同时执行。
这就是多线程技术可以解决的问题。
该如何创建线程呢?
通过API中的英文Thread的搜索,查到Thread类。
通过阅读Thread类中的描述。
创建线程有两种方式:
1,继承Thread类。★★★★★
1.1 定义一个类继承Thread。
1.2 重写run方法。
1.3 创建子类对象,就是创建线程对象。
1.4 调用start方法,开启线程并让线程执行,同时还会告诉jvm去调用run方法。
为什么要这么做?
继承Thread类:因为Thread类描述线程事物,具备线程应该有功能。
那为什么不只讲创建Thread类的对象呢?
Thread t1 = new Thread();
t1.start();//这么做没有错,但是该start调用的时Thread类中的run方法,
而这个run方法没有做什么事情,更重要的是这个run方法中并没有定义我们需要让线程执行的代码。
创建线程的目的是什么?是为了建立单独的执行路径,让多部分代码实现同时执行。
也就是说线程创建并执行需要给定的代码(线程的任务)。
对于之前所讲的主线程,它的任务定义在main函数中。
自定义线程需要执行的任务都定义在run方法中。
Thread类中的run方法内部的任务并不是我们所需要,只要重写这个run方法,
既然Thread类已经定义了线程任务的位置,只要在位置中定义任务代码即可。
所以进行了重写run方法动作。
多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。
进行方法的压栈和弹栈。
当执行线程的任务结束了,线程自动在栈内存中释放了。
但是当所有的执行线程都结束了,那么进程就结束了。
//获取线程名称:
Thread:currentThread()获取当前线程对象。怎么名称呢?getName();
Thread.currentThread().getName();
主线程的名称: main
自定义的线程: Thread-1 线程多个时,数字顺延。Thread-2......
*/
class Demo extends Thread
{
private String name;
Demo(String name)
{
this.name = name;
}
public void run()
{
int[] arr = new int[3];
System.out.println(arr[4]);
for(int x=1; x<=20; x++)
{
System.out.println("name="+name+"..."+Thread.currentThread().getName()+"..."+x);
}
}
}
class ThreadDemo
{
public static void main(String[] args)
{
//创建了两个线程对象。
Demo d1 = new Demo("小强");
Demo d2 = new Demo("旺财");
d2.start();//将d2这个线程开启。
d1.run();//由主线程负责。
/*
面试题:线程对象调用 run方法和调用start方法区别?
调用run方法不开启线程。仅是对象调用方法。
调用start开启线程,并让jvm调用run方法在开启的线程中执行。
*/
}
}
2. 创建线程的第二种方式实现Runnable接口
/*
创建线程的第二种方式。实现Runnable接口。
1,定义类实现Runnable接口:避免了继承Thread类的单继承局限性。
2,覆盖接口中的run方法。将线程任务代码定义到run方法中。
3,创建Thread类的对象:只有创建Thread类的对象才可以创建线程。
4,将Runnable接口的子类对象作为参数传递给Thread类的构造函数。
因为线程已被封装到Runnable接口的run方法中,而这个run方法所属于Runnable接口的子类对象,
所以将这个子类对象作为参数传递给Thread的构造函数,这样,线程对象创建时就可以明确要运行的线程的任务。
5,调用Thread类的start方法开启线程。
第二种方式实现Runnable接口避免了单继承的局限性,所以较为常用。
实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。
继承Thread类:线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,有又有线程任务。
实现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();
*/
class Demo implements Runnable
{
private String name;
Demo(String name)
{
this.name = name;
}
//覆盖了接口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)
{
//创建Runnable子类的对象。注意它并不是线程对象。
Demo d = new Demo("Demo");
//创建Thread类的对象,将Runnable接口的子类对象作为参数传递给Thread类的构造函数。
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
//将线程启动。
t1.start();
t2.start();
System.out.println(Thread.currentThread().getName()+"----->");
}
}
3. 线程的安全问题分析原因
/*
案例:售票的例子。
售票的动作需要同时执行,所以使用多线程技术。
发生了线程安全问题:出现了错误的数据。0 -1 -2
问题产生的原因;
1,线程任务中在操作共享的数据。
2,线程任务操作共享数据的代码有多条(运算有多个)。
解决思路:
只要让一个线程在执行线程任务时将多条操作共享数据的代码执行完,
在执行过程中,不要让其他线程参与运算。就哦了。
代码体现呢?
Java中解决此问题通过代码块来完成的。
这个代码块:同步代码块 synchronized
格式:
synchronized(对象)
{
//需要被同步的代码。
}
同步好处:
解决多线程安全问题。
同步弊端:
降低了程序的性能。
同步前提:
必须保证多个线程在同步中使用的是同一个锁。
解决了什么问题?
当多线程安全问题发生时,加入了同步后,
问题依旧,就要通过这个同步的前提来判断同步是否写正确。
*/
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();
}
}
4. 银行存款的练习
//两个客户到一个银行去存钱,每个客户一次存100,存3次。
//问题:该程序是否有安全问题,如果有,写出分析过程,并定义解决方案。
/*
发现运行结果:
sum=200
sum=300
sum=400
sum=200
sum=500
sum=600
打印错乱,不关心,但是发现数值错误,没有100.
运行了几次发现有对的。
sum=100
sum=300
sum=400
sum=200
sum=500
sum=600
说明多线程的随机性造成了安全问题发生。
哪的问题啊?
1,既然是多线程的问题,必须问题发生在线程任务内。
2,任务代码中是否有共性数据呢?有的,b对象的中的sum。
3,是否有对sum进行多次运算呢?有!
加同步就搞定。
*/
//描述银行。
class Bank
{
private int sum;
private Object obj = new Object();
public void add(int num)
{
synchronized(obj)
{
sum = sum + num;
System.out.println("sum="+sum);//每存一次,看到银行金额变化。
}
}
}
class Consumer implements Runnable
{
private Bank b = new Bank();
public void run()
{
for(int x=0 ; x<3; x++)
{
b.add(100);//一次存100.循环3次,
}
}
}
class ThreadTest
{
public static void main(String[] args)
{
Consumer c = new Consumer();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
5. 同步函数的体现,和同步代码块的区别
/*
同步的另一种体现形式:同步函数。
同步函数使用的锁是哪个?
经过分析:大概猜的是this,因为函数必须被对象调用。
验证:
写一个同步代码块,写一个同步函数,如果同步代码块中的锁对象和同步函数中的锁对象是同一个,
就同步了,就没有错误的数据了。如果不是同一个锁对象,就不同步出现错误数据。
让两个线程,一个线程在同步代码块中执行,一个线程在同步函数中执行。
总结:同步函数使用的锁时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();
}
}
6. 验证静态同步函数使用的锁时 类名.class
/*
static 同步函数,使用的锁不是this,而是字节码文件对象, 类名.class
*/
class Ticket implements Runnable
{
private static int tickets = 100;
private Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag){
while(true){
synchronized(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)
{
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();
}
}
7. 单例懒汉式被并发访问的解决方案
//饿汉式。 多线程并发饿汉式没问题。
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(){}
/*
并发访问会有安全隐患,所以加入同步机制解决安全问题。
但是,同步的出现降低了效率。
可以通过双重判断的方式,解决效率问题,减少判断锁的次数。
*/
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!");
}
}
8. 死锁在售票中的体现
/*
同步的另一个弊端:
情况之一:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。
这时容易引发一种现象:死锁。
这种情况能避免就避免掉。
//Thread-0
synchronized(obj1)
{
-->thread-0 obj1
synchronized(obj2)
{
}
}
//Thread-1
synchronized(obj2)
{
Thread-1 obj2
synchronized(obj1)
{
}
}
*/
class Ticket implements Runnable
{
private int tickets = 200;
private Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag){
while(true){
synchronized(obj){
sale();//this lock;
}
}
}
else{
while(true){
this.sale();
}
}
}
public synchronized void sale()//this lock
{
synchronized(obj)//obj lock
{
if(tickets>0)
{
try{Thread.sleep(10);}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印线程名称。
}
}
}
}
class ThreadDemo7
{
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();
}
}
9. 简单的死锁程序
class Test implements Runnable
{
private boolean flag;
Test(boolean flag)
{
this.flag = flag;
}
public void run()
{
if(flag)
{
while(true)
{
synchronized(MyLock.LOCKA)
{
System.out.println(Thread.currentThread().getName()+"...if......locka");
synchronized(MyLock.LOCKB)
{
System.out.println(Thread.currentThread().getName()+"...if......lockb");
}
}
}
}
else
{
while(true)
{
synchronized(MyLock.LOCKB)
{
System.out.println(Thread.currentThread().getName()+"...else......lockb");
synchronized(MyLock.LOCKA)
{
System.out.println(Thread.currentThread().getName()+"...else......locka");
}
}
}
}
}
}
//定义一个用于存储锁对象类。
class MyLock
{
public static final Object LOCKA = new Object();
public static final Object LOCKB = new Object();
}
class DeadLockTest
{
public static void main(String[] args)
{
//创建两个线程任务。
Test t1 = new Test(true);
Test t2 = new Test(false);
Thread t11 = new Thread(t1);
Thread t22 = new Thread(t2);
t11.start();
t22.start();
}
}
10. 生产者消费者,发生的问题
/*
多线程中最为常见的应用案例:
生产者消费者问题。
生产和消费同时执行,需要多线程。
但是执行的任务却不相同,处理的资源确实相同的:线程间的通信。
1,描述一下资源。
2,描述生产者,因为具备着自己的任务。
3,描述消费者,因为具备着自己的任务。
问题1:数据错误:已经被生产很早期的商品,才被消费到。
出现线程安全问题,加入了同步解决。使用同步函数。
问题已解决:不会在消费到之前很早期的商品。
问题2:发现了连续生产却没有消费,同时对同一个商品进行多次消费。
希望的结果应该是生产一个商品,就被消费掉。生产下一个商品。
搞清楚几个问题?
生产者什么时候生产呢?消费者什么时候应该消费呢?
当盘子中没有面包时,就生产,如果有了面包,就不要生产。
当盘子中已有面包时,就消费,如果没有面包,就不要消费。
*/
//1,描述资源。属性:商品名称和编号, 行为:对商品名称赋值,获取商品。
class Resource
{
private String name;
private int count = 1;
//1,提供设置的方法。
public synchronized void set(String name)
{
//给成员变量赋值并加上编号。
this.name = name + count;
//编号自增。
count++;
//打印生产了哪个商品。
System.out.println(Thread.currentThread().getName()+"......生产者...."+this.name);
}
public synchronized void out()
{
System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);
}
}
//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 ThreadDemo8
{
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();
}
}
11. 生产者消费者,加入了等待唤醒机制,监视器方法说明
/*
多线程中最为常见的应用案例:
生产者消费者问题。
生产和消费同时执行,需要多线程。
但是执行的任务却不相同,处理的资源确实相同的:线程间的通信。
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();
}
}
12. 多生产多消费,发生的问题,解决办法
/*
多生产多消费。
问题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();
}
}
13. 多生产多消费,通过JDK1.5解决问题。Lock Condition接口的使用
/*
jdk1.5以后提供多生产多消费的解决方案。
在java.util.concurrent.locks 软件包中提供相应的解决方案
Lock接口:比同步更厉害,有更多操作。lock():获取锁 unlock():释放锁;
提供了一个更加面对对象的锁,在该锁中提供了更多的显示的锁操作。
替代同步。
升级到JDK1.5,先把同步改成 Lock。
已经将旧锁替换成新锁,那么锁上的监视器方法(wait,notify,notifyAll)也应该替换成新锁的监视器方法。
而jdk1.5中将这些原有的监视器方法封装到了一个Condition对象中。
想要获取监视器方法,需要先获取Condition对象。
Condition对象的出现其实就是替代了Object中的监视器方法。
await();
signal();
signalAll();
将所有的监视器方法替换成了Condition。
功能和ThreadDemo10.java老程序的功能一样,仅仅是用新的对象。改了写法而已。
但是问题依旧;效率还是低。
希望本方可以唤醒对方中的一个。
老程序中可以通过两个锁嵌套完成,但是容易引发死锁。
新程序中,就可以解决这个问题,只用一个锁,
可以在一个锁上加上多个监视器对象。
*/
import java.util.concurrent.locks.*;
class Resource
{
private String name;
private int count = 1;
//定义一个锁对象。
private final Lock lock = new ReentrantLock();
//获取锁上的Condition对象。为了解决本方唤醒对方的问题。可以一个锁创建两个监视器对象。
private Condition produce = lock.newCondition();//负责生产。
private Condition consume = lock.newCondition();//负责消费。
//定义标记。
private boolean flag = false;
//1,提供设置的方法。
public void set(String name)//
{
//获取锁。
lock.lock();
try{
while(flag)
try{produce.await();}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;
//执行的消费者的唤醒。唤醒一个消费者就哦了。
consume.signal();
}finally{
lock.unlock();//一定要执行。
}
}
public void out()//
{
lock.lock();
try{
while(!flag)
try{consume.await();}catch(InterruptedException e){}//t3等 //t4等
System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);//消费 商品1
//将标记该为false。
flag = false;
//
produce.signal();
}
finally{
lock.unlock();
}
}
}
//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 ThreadDemo11
{
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();
}
}
14. Condition案例中的范例,带注释
class BoundedBuffer {
final Lock lock = new ReentrantLock();//锁
final Condition notFull = lock.newCondition(); //生产
final Condition notEmpty = lock.newCondition(); //消费
final Object[] items = new Object[100];//存储商品的容器。
int putptr/*生产者使用的角标*/, takeptr/*消费者使用的角标*/, count/*计数器*/;
/*生产者使用的方法,往数组中存储商品*/
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length) //判断计数器是否已到数组长度。满了。
notFull.await();//生产就等待。
items[putptr] = x; //按照角标将商品存储到数组中
if (++putptr == items.length) //如果存储的角标到了数组的长度,就将角标归零。
putptr = 0;
++count;//计数器自增。
notEmpty.signal();//唤醒一个消费者
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0) //如果计数器为0,说明没有商品,消费者等待。
notEmpty.await();
Object x = items[takeptr]; //从数组中通过消费者角标获取商品。
if (++takeptr == items.length) //如果消费的角标等于了数组的长度,将角标归零。
takeptr = 0;
--count;//计数器自减。
notFull.signal();//唤醒生产者。
return x;
} finally {
lock.unlock();
}
}
}
15. 妖的问题与解决。线程安全问题
/*
需求:
资源有姓名和性别。
两个线程,
一个负责给姓名和性别赋值,
一个负责获取姓名和性别的值。
参阅ThreadTest2.java文件
要求1,运行一下,解决程序的 "妖"的问题。
分析过程:
加入同步,必须保证同一个锁,解决妖的问题。
要求2,实现正确数据的间隔输出 如
张飞--男
rose--女女女
张飞--男
rose--女女女
要求3,对代码进行重构。
将name,sex私有化,资源类提供对其访问的方法。
要求4,将程序改成JDK1.5的Lock Condition接口。
*/
//描述资源。
class Resource
{
String name;
String sex;
}
//赋值线程任务
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(x==0)
{
r.name = "张飞";
r.sex = "男";
}
else
{
r.name = "rose";
r.sex = "女女女女";
}
}
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)
{
System.out.println(r.name+"....."+r.sex);
}
}
}
}
class ThreadTest2
{
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();
}
}
16. 数据间隔问题与解决。等待唤醒机制
/*
需求:
资源有姓名和性别。
两个线程,
一个负责给姓名和性别赋值,
一个负责获取姓名和性别的值。
参阅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();
}
}
17. 代码重构问题与解决。同步函数
/*
需求:
资源有姓名和性别。
两个线程,
一个负责给姓名和性别赋值,
一个负责获取姓名和性别的值。
参阅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();
}
}
18. JDK1.5升级问题与解决。Lock&Condition
/*
需求:
资源有姓名和性别。
两个线程,
一个负责给姓名和性别赋值,
一个负责获取姓名和性别的值。
参阅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;
//先创建锁对象。
private final Lock lock = new ReentrantLock();
//通过锁对象获取监视器对象。
private Condition con = lock.newCondition();
//赋值功能。
public void set(String name,String sex)
{
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();
}
}
19. 停止线程示例。演示了循环结束标记和interrupt方法
//演示停止线程。
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");
}
}
20. 演示了join方法的特点
class Demo implements Runnable
{
public void run()
{
for(int x=1; x<=40; x++)
{
System.out.println(Thread.currentThread().getName()+"------>"+x);
Thread.yield();//线程临时暂停。将执行权释放,让其他线程有机会获取执行权。
}
}
}
class JoinThreadDemo
{
public static void main(String[] args)
{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
//主线程执行到这里,知道t1要加入执行,主线程释放了执行权,
//执行资格并处于冻结状态,什么时候恢复呢?等t1线程执行完。
// try{t1.join();}catch(InterruptedException e){}//用于临时加入一个运算的线程。让该线程运算完,程序才会继续执行。
for(int x=1; x<=50; x++)
{
System.out.println("main---------->"+x);
}
System.out.println("over");
}
}
21. 线程的匿名内部类体现以及面试题
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();
*/