------- android培训、java培训、期待与您交流! ----------
进程:是一个正在执行的程序。每个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫做一个控制单元
线程:就是进程中的一个独立的控制单元。线程控制着进程的执行。
一个进程中 至少有一个线程
java VM启动的时候会有一个进程java.exe,该进程中至少有一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称为主线程。
其实更多细节说明jvm启动不止一个线程,还有负责垃圾回收机制的线程。
Thread类就是对线程这类事物的描述。
1.创建线程的第一种方法
继承Thread类;
复写run方法;
覆盖run方法的原因:Thread类定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法
目的:将自定义的代码存储在run方法中,让I线程运行
使用线程的start方法启动线程,调用run方法
class Demo extends Thread //继承Thread类,创建线程
{
public void run() //复写run方法
{
for(int x=0;x<10;x++)
System.out.println("demo run----"+x);
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Demo d=new Demo(); //建立一个对象,同时也就创建了一个线程
d.start(); //调用start()方法,启动线程,调用run方法
for(int x=0;x<10;x++)
{
System.out.println("Hello Word----------"+x);
}
}
}
运行结果:
发现运行结果每次都不一致,因为多个线程都在获取cpu的执行权,cpu执行到哪个线程,就运行哪个线程。在某一时刻,只能有一个程序在运行(多核除外)。
以上体现出了线程的一个特性,即随机性,谁抢到谁运行,运行时间由cpu决定
练习:创建两个现场,和主线程交替运行
class Demo extends Thread //继承Thread类
{
Demo(String name) //初始化构造函数,传递线程名称
{
super(name); //自定义线程名的方法,调用父类线程名
}
public void run()
{
for(int x=0;x<60;x++)
{
System.out.println(Thread.currentThread().getName()+"T run---"+x); //获取当前线程名
}
}
}
class ThreadTest
{
public static void main(String[] args)
{
Demo d=new Demo("zhangsan"); //创建线程,并传递线程名
Demo e=new Demo("lisi");
d.start();
e.start();
for(int x=0;x<60;x++)
{
System.out.println("main run-------------"+x);
}
}
}
线程都有自己的默认名;格式:Thread-数字,数字从0开始
发现Thread构造方法初始化的时候就有名称Thread(String name);线程Thread中有getName(),setName()方法来获取线程名,所以只需要调用super(name)就可以获取父类Thread来获取线程名static Thread currentThread():获取当前线程对象
设置线程名称:setName()或者构造函数
练习:简单的买票程序:多个窗口同时买票
class Ticket implements Runnable//extends Thread //继承Thread类
{
private <span style="color:#ff0000;">static</span> int tick=100; //初始化成员变量:票的总数,在此使用静态,使票的总数保持在100张
public void run() //复写run方法
{
while(true)
{
if(tick>0) //当还有票的时候
System.out.println(Thread.currentThread().getName()+"sale:"+tick--); //获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
//创建线程
Ticket t1=new Thread();
Ticket t2=new Thread();
Ticket t3=new Thread();
Ticket t4=new Thread();
//启动线程,调用run方法
t1.start();
t2.start();
t3.start();
t4.start();
}
}
但是使用静态让票数固定,会浪费资源,由此引入线程的第二种创建方法:
声明实现Runnable接口;
覆盖Runnable接口中的run方法;
将线程要运行的代码放在该方法中
通过Thread类建立线程对象;
将Runnable接口的子类对象作为实际参数传入Thread类;
实现此步骤的原因:自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定某个对象的run方法,就必须明确该run方法所属对象
调用Thread类的start方法开启线程,并调用Runnable接口的run方法
还是买票的例子:
class Ticket implements Runnable //实现Runnable接口
{
private int tick=100; //初始化成员变量:票的总数
public void run() //复写run方法
{
while(true)
{
if(tick>0) //当还有票的时候
System.out.println(Thread.currentThread().getName()+"sale:"+tick--); //获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
/*
//创建线程
Ticket t1=new Ticket();
Ticket t2=new Ticket();
Ticket t3=new Ticket();
Ticket t4=new Ticket();
//启动线程,调用run方法
t1.start();
t2.start();
t3.start();
t4.start();
*/
Ticket t=new Ticket(); //创建一个Ticket对象
//创建线程,并将线程与run()方法相关联,将实现Runnable接口的类对象作为实际参数传入线程对象中
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
//运行线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行结果:
线程也运行起来了
那么继承类与实现类有什么区别呢?
实现方式避免了单继承的局限性。类可以在继承的基础上实现接口,功能得以拓展。在定义线程时,最好使用实现方式
线程代码存在实现Runnable接口的子类的run方法中;而在继承中,线程代码存在于Thread子类的run方法中
在上面卖票的例子中通过传入对象作为参数,使各个线程运行的同时还可以保持票的总数不变
在售票的例子中,让线程停顿10毫秒,发现结果出现了负数。多线程的运行出现了安全问题
class Ticket implements Runnable //实现Runnable接口
{
private static int tick=100; //初始化成员变量:票的总数,在此使用静态,使票的总数保持在100张
public void run() //复写run方法
{
while(true)
{
if(tick>0) //当还有票的时候
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"sale:"+tick--); //获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
}
}
}
class TicketDemo1
{
public static void main(String[] args)
{
/*
//创建线程
Ticket t1=new Ticket();
Ticket t2=new Ticket();
Ticket t3=new Ticket();
Ticket t4=new Ticket();
//启动线程,调用run方法
t1.start();
t2.start();
t3.start();
t4.start();
*/
Ticket t=new Ticket(); //创建一个Ticket对象
//创建线程,并将线程与run()方法相关联
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
//运行线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没执行完,另一个线程参与进来执行,倒是共享数据的错误
解决办法:对于多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与
对于次安全问题,使用同步代码块处理:
synchronized(对象)
{
需要被同步的代码块
}
之后票数就正常了
对象如同锁,持有锁的线程可以在同步中执行;没有持有锁的线程即使获取cpu执行权,也进不去,因为没有持有锁
同步的前提:1.必须要有两个或两个以上的线程;2.必须是多个线程使用同一个锁
同步的好处:可以解决多线程的安全问题
弊端:多个线程需要判断锁,较为消耗资源
练习:银行金库,两个客户分别存入300元,分3次,每次存100
如何找问题:明确那些代码是多线程运行代码;明确共享数据;明确多线程运行代码中哪些语句是操作共享数据
引入同步函数:把同步作为修饰符传给函数
class Bank //对银行进行描述
{
Object obj=new Object();
private int sum; //引用成员变量,银行金库钱的总数
public synchronized void add(int n) //存入钱之后,金库内钱增加,计算总和,同步函数
{
//synchronized(obj)
//{
sum=sum+n;
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println("sum="+sum);
//}
}
}
class Cus implements Runnable //实现Runnable接口
{
private Bank b=new Bank(); //引用Bank对象作为成员变量
public void run() //复写run方法
{
for(int x=0;x<3;x++) //分为3次存钱
{
b.add(100); //银行金库总存款增加
}
}
}
class BankDemo
{
public static void main(String[] args)
{
Cus c=new Cus(); //新建实现Runnable接口的类对象
//创建线程对象,并将实现Runnable接口的类对象作为参数传入
Thread t1=new Thread(c);
Thread t2=new Thread(c);
//启动线程,调用Runnable接口子类的run方法
t1.start();
t2.start();
}
}
那么同步函数用的是哪个锁呢?函数需要被对象调用,那么函数都有一个所属对象调用,即this.所以同步函数使用的锁是this
接下来就买票的例子进行验证
class Ticket implements Runnable //实现Runnable接口
{
private static int tick=100; //初始化成员变量:票的总数,在此使用静态,使票的总数保持在100张
Object obj=new Object();
boolean flag=true;
public void run() //复写run方法
{
if(flag)
{
while(true)
{
synchronized(obj) //传入对象设为上帝类Object
{ //这里tick就是共享数据,所以在操作tick的代码上使用同步代码块
if(tick>0) //当还有票的时候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+" code-"+tick--); //获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
}
}
}
}
else
{
while(true)
{
show();
}
}
}
public synchronized void show()<span style="white-space:pre"> </span>//同步函数调用的是使用该方法的对象的锁,即this
{
if(tick>0) //当还有票的时候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"show===:"+tick--); //获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
}
}
}
class TicketDemo2
{
public static void main(String[] args)
{
Ticket t=new Ticket(); //创建一个Ticket对象
//创建线程,并将线程与run()方法相关联
Thread t1=new Thread(t);
Thread t2=new Thread(t);
//运行线程
t1.start();
//主函数瞬间先执行完,如此标志位是false,再执行另外两个线程,此时两个线程都会执行run方法中的else语句那么就只有同步函数在运行了,为了避免这种情况,在此处让主线程停10毫秒,从而能使其中一个线程运行同步代码块,而另一个则运行同步函数
try
{
Thread.sleep(10);
}
catch(Exception e)
{}
t.flag=false; //将标志更改,使得t2运行同步函数
t2.start();
}
}
发现结果中有出售0号票的
原因是共享数据用的不是同一个锁,第一个线程用的是obj的锁,第二个线程用的是this锁
将代码块的锁也改为this
class Ticket implements Runnable //实现Runnable接口
{
private static int tick=100; //初始化成员变量:票的总数,在此使用静态,使票的总数保持在100张
Object obj=new Object();
boolean flag=true;
public void run() //复写run方法
{
if(flag)
{
while(true)
{
synchronized(<span style="color:#ff0000;">this</span>)
{ //这里tick就是共享数据,所以在操作tick的代码上使用同步代码块
if(tick>0) //当还有票的时候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+" code-"+tick--); //获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
}
}
}
}
else
{
while(true)
{
show();
}
}
}
public synchronized void show()
{
if(tick>0) //当还有票的时候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"show===:"+tick--); //获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
}
}
}
class TicketDemo2
{
public static void main(String[] args)
{
Ticket t=new Ticket(); //创建一个Ticket对象
//创建线程,并将线程与run()方法相关联
Thread t1=new Thread(t);
Thread t2=new Thread(t);
//运行线程
t1.start();
//主函数瞬间先执行完,如此标志位是false,再执行另外两个线程,此时两个线程都会执行run方法中的else语句那么就只有同步函数在运行了,为了避免这种情况,在此处让主线程停10毫秒,从而能使其中一个线程运行同步代码块,而另一个则运行同步函数
try
{
Thread.sleep(10);
}
catch(Exception e)
{}
t.flag=false; //将标志更改,使得t2运行同步函数
t2.start();
}
}
同步函数和同步代码块都使用的是this锁,如此就没有0号票了
静态同步函数:如果同步函数被静态修饰后,使用的锁是什么呢?
class Ticket implements Runnable //实现Runnable接口
{
private static int tick=100; //初始化成员变量:票的总数,在此使用静态,使票的总数保持在100张
Object obj=new Object();
boolean flag=true;
public void run() //复写run方法
{
if(flag)
{
while(true)
{
synchronized(<span style="color:#ff0000;">this</span>)
{ //这里tick就是共享数据,所以在操作tick的代码上使用同步代码块
if(tick>0) //当还有票的时候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+" code-"+tick--); //获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
}
}
}
}
else
{
while(true)
{
show();
}
}
}
public static synchronized void show()
{
if(tick>0) //当还有票的时候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"show===:"+tick--); //获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
}
}
}
class TicketDemo3
{
public static void main(String[] args)
{
Ticket t=new Ticket(); //创建一个Ticket对象
//创建线程,并将线程与run()方法相关联
Thread t1=new Thread(t);
Thread t2=new Thread(t);
//运行线程
t1.start();
//主函数瞬间先执行完,如此标志位是false,再执行另外两个线程,此时两个线程都会执行run方法中的else语句那么就只有同步函数在运行了,为了避免这种情况,在此处让主线程停10毫秒,从而能使其中一个线程运行同步代码块,而另一个则运行同步函数
try
{
Thread.sleep(1);
}
catch(Exception e)
{}
t.flag=false; //将标志更改,使得t2运行同步函数
t2.start();
}
}
运行结果有0号票,说明用的不是同一个锁,同步函数的锁不在是this了,因为静态中不存在this
静态在内存是,内存中没有本类对象,但是一定有该类对象对应的字节码文件对象
类名.class,该对象的类型是Class
class Ticket implements Runnable //实现Runnable接口
{
private static int tick=100; //初始化成员变量:票的总数,在此使用静态,使票的总数保持在100张
Object obj=new Object();
boolean flag=true;
public void run() //复写run方法
{
if(flag)
{
while(true)
{
synchronized(<span style="color:#ff0000;">Ticket.class</span>)
{ //这里tick就是共享数据,所以在操作tick的代码上使用同步代码块
if(tick>0) //当还有票的时候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+" code-"+tick--); //获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
}
}
}
}
else
{
while(true)
{
show();
}
}
}
public static synchronized void show()
{
if(tick>0) //当还有票的时候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"show===:"+tick--); //获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
}
}
}
class TicketDemo3
{
public static void main(String[] args)
{
Ticket t=new Ticket(); //创建一个Ticket对象
//创建线程,并将线程与run()方法相关联
Thread t1=new Thread(t);
Thread t2=new Thread(t);
//运行线程
t1.start();
//主函数瞬间先执行完,如此标志位是false,再执行另外两个线程,此时两个线程都会执行run方法中的else语句那么就只有同步函数在运行了,为了避免这种情况,在此处让主线程停10毫秒,从而能使其中一个线程运行同步代码块,而另一个则运行同步函数
try
{
Thread.sleep(1);
}
catch(Exception e)
{}
t.flag=false; //将标志更改,使得t2运行同步函数
t2.start();
}
}
发现程序又安全了,所以静态的同步方法使用的锁是该方法所在类的字节码文件对象,类名.class
单例设计模式:懒汉式的同步
先复习下饿汉式
class Single
{
private static final Single s=new Single();<span style="white-space:pre"> </span>//一开始就初始化对象值
private Single()
{}
public static Single getInstance()
{
return s;
}
}
//懒汉式
class Single
{
private static Single s=null;
private Single()
{}
public static Single getInstance()
{
if(s==null)
s=new Single();
return s;
}
}
对于懒汉式,如果同时有多个线程在调用该类,就会出现安全问题,一个在判断,一个在赋值,不能保证对象的唯一性,所以引入同步,
class Single
{
private static Single s=null; //引入成员变量
private Single()
{}
public static Single getInstance() //创建获取实例对象的方法
{
if(s==null) //使用双重判断,提高效率
{
synchronized(Single.class)
if(s==null)
s=new Single();
return s;
}
}
}
使用双重判断,却比较麻烦,所以平常使用最好使用饿汉式,饿汉式只有一句话,不存在同步问题
死锁:通常情况下是同步中嵌套同步,而锁不同
class Ticket implements Runnable //实现Runnable接口
{
private static int tick=100; //初始化成员变量:票的总数,在此使用静态,使票的总数保持在100张
Object obj=new Object();
boolean flag=true;
public void run() //复写run方法
{
if(flag)
{
while(true)
{
synchronized(obj) //同步代码块中调用同步函数
{
show();
}
}
}
else
{
while(true)
{
show();
}
}
}
public synchronized void show() //同步函数中包含同步代码块,调用的是this锁
{
synchronized(obj)
{
if(tick>0) //当还有票的时候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"show===:"+tick--); //获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
}
}
}
}
class DeadLockDemo2
{
public static void main(String[] args)
{
Ticket t=new Ticket(); //创建一个Ticket对象
//创建线程,并将线程与run()方法相关联
Thread t1=new Thread(t);
Thread t2=new Thread(t);
//运行线程
t1.start();
//主函数瞬间先执行完,如此标志位是false,再执行另外两个线程,此时两个线程都会执行run方法中的else语句那么就只有同步函数在运行了,为了避免这种情况,在此处让主线程停10毫秒,从而能使其中一个线程运行同步代码块,而另一个则运行同步函数
try
{
Thread.sleep(1);
}
catch(Exception e)
{}
t.flag=false; //将标志更改,使得t2运行同步函数
t2.start();
}
}
发现没运行一会儿变没有了,发生了死锁
写出一个死锁程序
class Test implements Runnable //实现Runnable接口
{
private boolean flag;
Test(boolean flag)
{
this.flag=flag;
}
public void run() //复写run方法
{
if(flag)
{
while(true)
{
synchronized(MyLock.locka) //locka锁嵌套lockb锁
{
System.out.println("if locka");
synchronized(MyLock.lockb)
{
System.out.println("if lockb");
}
}
}
}
else
{
while(true)
{
synchronized(MyLock.lockb) //lockb中嵌套locka锁
{
System.out.println("else lockb");
synchronized(MyLock.locka)
{
System.out.println("else locka");
}
}
}
}
}
}
class MyLock //创建一个锁对象
{
static Object locka=new Object();
static Object lockb=new Object();
}
class DeadLockDemo
{
public static void main(String[] args)
{
//创建线程,传入实现Runnable接口的类作为参数
Thread t1=new Thread(new Test(true));
Thread t2=new Thread(new Test(false));
//启动线程
t1.start();
t2.start();
}
}
只循环了一次便终止了,发生了死锁
线程间通讯
建立一个共享资源,不断交替传入两个姓名和性别,然后打印出来
/*
练习:不断的存入两个人名,不断的输出打印
*/
class InputOutputDemo1
{
public static void main(String[] args)
{
Res r=new Res(); //建立资源对象
//创建线程第二步,创建Runnable接口的子类对象
Input in=new Input(r); //建立输入对象,并将资源对象作为参数传入
Output out=new Output(r); //建立输出对象,并将新建的资源对象传入
//创建线程第三步,新建线程,并将实现Runnable的子类对象传入
Thread t1=new Thread(in);
Thread t2=new Thread(out);
t1.start();
t2.start();
}
}
class Res //建立共有资源类
{
//包含姓名和性别
String name;
String sex;
}
class Input implements Runnable //存入线程,将姓名和性别存入资源中
{
private Res r; //创建资源类型的成员变量
Input(Res r) //构造函数初始化时就获取资源对象
{
this.r=r;
}
public void run() //复写run方法
{
int x=0;
while(true)
{
synchronized(r)<span style="white-space:pre"> </span>//加入同步,避免第一个性别还没打印,第二个性别就先打印了,此处寻找同一个锁,发现r就是共享对象
{
if(x==0)
{
r.name="mike";
r.sex="男";
}
else
{
r.name="lili";
r.sex="女女女女女女女女女";
}
x=(x+1)%2; //通过x的值的变换,来切换两个名字互相切换
}
}
}
}
class Output implements Runnable
{
private Res r; //与传入线程类似,创建资源型成员变量
Output(Res r) //初始化构造函数的时候获取资源对象
{
this.r=r;
}
public void run() //复写run方法
{
while(true)
{
synchronized(r)<span style="white-space:pre"> </span><span style="font-family: Arial, Helvetica, sans-serif;">//加入同步,避免第一个性别还没打印,第二个性别就先打印了,此处寻找同一个锁,发现r就是共享对象</span><span style="white-space:pre">
</span>
{
System.out.println(r.name+"=="+r.sex);
}
}
}
}
注意,在上面中,对姓名和性别赋值,打印都在不断操作共同数据,所以在输出语句中也要加入锁
此时虽然不会有性别和姓名错乱,但是希望的结果是两个姓名和年龄交替打印,而这里可能输入线程出了同步之后,没有被输出县城抢到执行权,继续被输入线程抢到执行权,然后继续覆盖之前的姓名,不断重复打印一个姓名和性别
此处引入等待唤醒机制
/*
练习:不断的存入两个人名,不断的输出打印
*/
class InputOutputDemo1
{
public static void main(String[] args)
{
Res r=new Res(); //建立资源对象
//创建线程第二步,创建Runnable接口的子类对象
Input in=new Input(r); //建立输入对象,并将资源对象作为参数传入
Output out=new Output(r); //建立输出对象,并将新建的资源对象传入
//创建线程第三步,新建线程,并将实现Runnable的子类对象传入
Thread t1=new Thread(in);
Thread t2=new Thread(out);
t1.start();
t2.start();
}
}
class Res //建立共有资源类
{
//包含姓名和性别
String name;
String sex;
boolean flag=false; //添加一个标志位,来维持两个线程分别进行
}
class Input implements Runnable //存入线程,将姓名和性别存入资源中
{
private Res r; //创建资源类型的成员变量
Input(Res r) //构造函数初始化时就获取资源对象
{
this.r=r;
}
public void run() //复写run方法
{
int x=0;
while(true)
{
synchronized(r)
{
if(r.flag)
{
try{
r.wait(); //如果标志位为真,就代表资源中姓名和年龄有值,让线程进入等待状态,让输出线程获得执行权
}
catch(Exception e){}
}
if(x==0)
{
r.name="mike";
r.sex="男";
}
else
{
r.name="lili";
r.sex="女女女女女女女女女";
}
x=(x+1)%2; //通过x的值的变换,来切换两个名字互相切换
r.flag=true; //赋值之后,将标志位修改为真
r.notify(); //唤醒等待的线程,两个线程都有执行权,如果此线程抢到执行权,会返回上面判断,然后就会等待
}
}
}
}
class Output implements Runnable
{
private Res r; //与传入线程类似,创建资源型成员变量
Output(Res r) //初始化构造函数的时候获取资源对象
{
this.r=r;
}
public void run() //复写run方法
{
while(true)
{
synchronized(r)
{
if(!r.flag) //如果标志位为假,则是姓名性别的值已经打印过了为空了
{
try{
r.wait(); //就让打印线程等待,输入线程运行
}
catch(Exception e){}
}
System.out.println(r.name+"=="+r.sex);
r.flag=false; //打印完之后,将标志位的值修改,
r.notify(); //将等待的线程唤醒,即唤醒输入线程;如果输出线程再抢到执行权,就会再回到上面判断,发现为假,就会等待,然后只有输入线程执行了
}
}
}
}
wait();notify();notifyAll() 都使用在同步中,因为要对持有监视器的线程操作,所以都使用在同步中,因为只有监视器中才有锁 在上面,因为线程存在嵌套,所以使用wait,notify方法的时候需要标明是调用对象锁r,即r.wart,r.notify;
上述方法都在Object中,因为等待和唤醒必须是同一个锁,而锁可以是任意一个对象,所以是定义在Object中的
下面可以对代码进行优化
/*
练习:不断的存入两个人名,不断的输出打印
*/
class InputOutputDemo1
{
public static void main(String[] args)
{
Res r=new Res(); //建立资源对象
//创建线程第二步,创建Runnable接口的子类对象
Input in=new Input(r); //建立输入对象,并将资源对象作为参数传入
Output out=new Output(r); //建立输出对象,并将新建的资源对象传入
//创建线程第三步,新建线程,并将实现Runnable的子类对象传入
Thread t1=new Thread(in);
Thread t2=new Thread(out);
t1.start();
t2.start();
}
}
class Res //建立共有资源类
{
//包含姓名和性别
private String name;
private String sex;
private boolean flag=false; //添加一个标志位,来维持两个线程分别进行
public synchronized void set(String name,String sex)
{
if(flag)
{
try{
this.wait(); //如果标志位为真,就代表资源中姓名和年龄有值,让线程进入等待状态,让输出线程获得执行权
}
catch(Exception e){}
}
this.name=name;
this.sex=sex;
flag=true;
this.notify();<span style="white-space:pre"> </span>//唤醒锁对象监视的等待线程,此处即输出线程
}
public synchronized void printout()
{
if(!flag)
{
try{
this.wait(); //如果标志位为真,就代表资源中姓名和年龄有值,让线程进入等待状态,让输出线程获得执行权
}
catch(Exception e){}
}
System.out.println(this.name+"=="+this.sex);
flag=false;
this.notify();<span style="white-space:pre"> </span>//唤醒锁对象监视的等待的线程,此处即唤醒输入线程
}
}
class Input implements Runnable //存入线程,将姓名和性别存入资源中
{
private Res r; //创建资源类型的成员变量
Input(Res r) //构造函数初始化时就获取资源对象
{
this.r=r;
}
public void run() //复写run方法
{
int x=0;
while(true)
{
if(x==0)
r.set("mike","男");
else
r.set("丽丽","女女女女女女女女女");
x=(x+1)%2; //通过x的值的变换,来切换两个名字互相切换
}
}
}
class Output implements Runnable
{
private Res r; //与传入线程类似,创建资源型成员变量
Output(Res r) //初始化构造函数的时候获取资源对象
{
this.r=r;
}
public void run() //复写run方法
{
while(true)
{
r.printout();
}
}
}
练习:多个生产者和消费者同时运行,但要保持生产一个产品就要消费一个产品
class BCTest
{
public static void main(String[] args)
{
Res r=new Res(); //创建资源对象
//创建生产消费者对象,并把资源对象作为参数传入
Pro p=new Pro(r);
Consumersumersumer c=new Consumersumersumer(r);
//创建线程
Thread t1=new Thread(p);
Thread t2=new Thread(p);
Thread t3=new Thread(c);
Thread t4=new Thread(c);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Res
{
private String name; //创建产品名称变量
private int count=1; //产品编号变量
private boolean flag; //标志位
public synchronized void set(String name) //构造生产者线程的调用方法
{
while(flag) //使用whil循环,使得被唤醒的线程每次都要先判断标志,避免生产一个还没消费就被覆盖掉了
{
try
{
this.wait(); //如果标志位为真,则让生产者等待,让消费者消费
}
catch(Exception e)
{
}
}
this.name=name+"=="+count++; //产品一有名称就自带编号
System.out.println(Thread.currentThread().getName()+"生产者=========="+this.name);
flag=true; //修改标志位
this.notifyAll(); //唤醒所有等待中的线程
}
public synchronized void out() //构造消费者线程的调用方法
{
while(!flag)
{
try
{
this.wait();
}
catch(Exception e)
{
}
}
System.out.println(Thread.currentThread().getName()+"消费者"+this.name);
flag=false; //打印完成后修改标志位
this.notifyAll(); //唤醒处于等待状态的所有线程
}
}
class Pro implements Runnable //创建生产者类
{
private Res r; //创建私有变量为资源类型的
Pro(Res r) //初始化构造函数
{
this.r=r;
}
public void run()
{
while(true)
r.set("商品"); //调用生产者方法
}
}
class Consumersumersumer implements Runnable //创建消费者类
{
private Res r; //创建私有资源类型的变量
Consumersumersumer(Res r) //初始化构造函数获取资源对象
{
this.r=r;
}
public void run()
{
while(true)
r.out(); //调用消费者方法
}
}
JDK5.0之后,synchronized 被Lock(接口)替代;调用其方法newCondition()可以新建一个Condition对象,包含notify,wait,notifyAll方法
并且可以建立多个Condition对象,对于上述例子,可以新建两个分别代表生产者和消费者对应的Condition对象
import java.util.concurrent.locks.*;
class CPTest
{
public static void main(String[] args)
{
Res r=new Res(); //创建资源对象
//创建生产消费者对象,并把资源对象作为参数传入
Pro p=new Pro(r);
Consumersumersumer c=new Consumersumersumer(r);
//创建线程
Thread t1=new Thread(p);
Thread t2=new Thread(p);
Thread t3=new Thread(c);
Thread t4=new Thread(c);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Res
{
private String name; //创建产品名称变量
private int count=1; //产品编号变量
private boolean flag; //标志位
private Lock lock=new ReentrantLock(); //建立锁的对象,取代了旧版中的同步
private Condition cond_con=lock.newCondition(); //condition中封装了wait,notify,notifyAll方法,所以建立此对象可以调用其中的方法
private Condition cond_pro=lock.newCondition(); //建立消费者对应的Condition对象
public void set(String name) throws InterruptedException //构造生产者线程的调用方法
{
lock.lock(); //加上锁
try
{
while(flag) //使用whil循环,使得被唤醒的线程每次都要先判断标志,避免生产一个还没消费就被覆盖掉了
{
cond_con.await(); //如果标志位为真,则让生产者等待,让消费者消费
}
this.name=name+"=="+count++; //产品一有名称就自带编号
System.out.println(Thread.currentThread().getName()+"生产者=========="+this.name);
flag=true; //修改标志位
cond_pro.signal(); //唤醒等待的线程
}
finally
{
lock.unlock(); //解锁
}
}
public void out() throws InterruptedException //构造消费者线程的调用方法
{
lock.lock();
try{
while(!flag)
{
cond_pro.await();
}
System.out.println(Thread.currentThread().getName()+"消费者"+this.name);
flag=false; //打印完成后修改标志位
cond_con.signal(); //唤醒等待线程
}
finally
{
lock.unlock(); //解除锁,释放资源
}
}
}
class Pro implements Runnable //创建生产者类
{
private Res r; //创建私有变量为资源类型的
Pro(Res r) //初始化构造函数
{
this.r=r;
}
public void run()
{
while(true)
try
{
r.set("商品"); //调用生产者方法
}
catch (InterruptedException e)
{
}
}
}
class Consumersumersumer implements Runnable //创建消费者类
{
private Res r; //创建私有资源类型的变量
Consumersumersumer(Res r) //初始化构造函数获取资源对象
{
this.r=r;
}
public void run()
{
while(true)
try
{
r.out(); //调用消费者方法
}
catch (InterruptedException e)
{
}
}
}
同时,新版本之后,要想停止线程只有一种方法,就是结束run方法
在开启多线程运行的时候,通常会在运行代码中使用循环结构,只要控制住循环结构就可以终止线程