------Java培训、Android培训、iOS培训、.Net培训、期待与您交流!
一、线程
在计算机中,每一个程序的运行,都是通过线程来实现的。我们可以把一个正在执行的程序称为进程,每一个进程的执行都有一个执行顺序。该顺序是一个执行路径,或者叫控制单元。线程就是进程中的一个独立的控制单元,线程在控制着进程的执行。只要进程中有一个线程在执行,进程就不会结束。一个进程中至少有一个线程。
在多个程序运行时,CPU会随机地在多个进程中快速切换,哪个线程抢到了CPU的执行权,哪个线程就运行。运行Java程序时,虚拟机启动会有一个java.exe的进程,该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中,称之为主线程。虚拟机启动除了执行主线程外,还有负责垃圾回收机制的线程。这种在在一个进程中有多个线程执行的方式,就是多线程。
多线程的出现能让程序产生同时运行的效果,提高程序的运行效率。例如:在虚拟机启动后,进程中执行主线程时,一般会根据程序代码,在堆内存中产生很多对象,而对象调用完后,就成了垃圾,如果不及时清理,垃圾过多容易造成内存不足,影响程序的运行。所以如果只有主线程运行,程序的效率可能会很低;而如果有一个负责垃圾回收机制的线程运行时,就会对堆内存中的垃圾进行清理,保证了内存的稳定,就保证了程序的运行效率。
二、创建线程
有两种创建线程的方式:继承Thread类和实现Runnable接口。
(1)继承Thread类
Thread类是Java提供的对线程这类事物描述的类,通过继承Thread类,复写其run方法来创建线程。步骤如下:
1.定义一个类继承Thread。
2.覆盖Thread中的run方法。将自定义的代码放在run方法中,让线程运行时,执行这些代码。
3.创建这个类的对象。相当于创建一个线程。然后用该对象调用线程的start方法。该方法的作用是:启动线程,调用run方法。注意如果直接用对象调用run方法,相当于没有启动创建的线程,还是只有主线程在执行。
覆盖run方法的原因:Thread类用于描述线程。该类就定义了一个功能,用于存储线程要执行的代码。该存储功能就是run方法。也就是说,Thread类中的run方法,用于存储线程要运行的代码。
下面通过一段程序,演示如何用继承方法创建线程,如下:
/**
需求:创建两个线程,和主线程交替运行。
*/
class MyThread extends Thread
{
//覆盖父类的run方法,存入运行代码
public void run(){
for(int x=0;x<1000;x++)
System.out.println(Thread.currentThread().getName()+"在运行");
}
}
class ThreadDemo
{
public static void main(String[] args)
{
//创建线程
MyThread mt1=new MyThread();
MyThread mt2=new MyThread();
//启动线程
mt1.start();
mt2.start();
for(int x=0;x<1000;x++)
System.out.println("Hello World!");
}
}
主线程和创建的线程会交替执行,因为CPU是随机地选择要执行的线程。这种方法可以创建线程,但如果定义的类已经是其他类的子类了,就无法再继承Thread类了,所以Java提供了另一中创建线程的方式,通过实现Runnable接口的方式。实现Runnable接口,覆盖run方法。这种创建线程的方式避免了单继承的局限性,在定义创建线程时,一般都使用这种方式。具体步骤如下:
1.定义一个类实现Runnable的接口。
2.覆盖Runnable接口中的run方法。将线程要运行的代码存放在该run方法中。
3.通过Thread类创建线程对象,并将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法。这样做是因为自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定对象的run方法,就必须明确该run方法所属对象。
4.调用Thread类中start方法启动线程。start方法会自动调用Runnable接口子类的run方法。
下面是一个简单的售票程序,应用的就是实现这种创建线程的方式,如下:
/**
需求:简单的卖票程序。
多个窗口同时买票。
*/
class Ticket implements Runnable
{
private int ticket = 100;
//复写run方法
public void run()
{
while(true)
{
if(ticket>0)
{
System.out.println(Thread.currentThread().getName()+"....sale : "+ ticket--);
}
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
//创建4个线程,表示4个同时售票的窗口
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();
}
}
这种创建线程的方式,是把线程要运行的代码放在Runnable接口的子类的run方法中;而继承Thread类是把要运行的代码放在Thread的子类的run方法中。在以后的操作中一般用到的都是实现Runnable接口的方式。首先看一下表示线程运行状态的图例:
被创建:等待启动,调用start启动。如果线程已经启动,处在运行时,再次调用start方法,没有意义,会提示线程状态异常。
运行状态:具有执行资格和执行权。
临时状态(阻塞):有执行资格,但没有执行权。
冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
消亡状态:stop()方法,或者run方法结束。
四、线程安全
当多条语句在操作多个线程的共享数据时,当一个线程对多条语句只执行了一部分,还没有执行完时,另一个线程可能就会参与进来执行,这样会导致共享数据的错误。线程的安全问题一旦出现对程序的影响很大。所以Java中提供了解决线程安全问题的方法,叫做同步(synchronized),就是对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行,这样就解决了线程的安全问题。同步中分为两种解决方法,一种是同步代码块,另一种是同步函数
(1)同步代码块
格式:synchronized(对象){需要被同步的代码}
就以上面售票的程序为例,利用同步代码块,确保线程安全。如下:
class Ticket implements Runnable
{
private int ticket = 100;
//定义用于使用同步的对象。
Object obj = new Object();
public void run()
{
while(true)
{
//给程序实现同步
synchronized(obj)
{
if(ticket>0)
{
System.out.println(Thread.currentThread().getName()+"....sale : "+ ticket--);
}
}
}
}
}
class TicketDemo2
{
public static void main(String[] args)
{
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
在同步代码块中,对象就如同一把锁。持有锁的线程才可以在同步中执行。没有持有锁的线程即使获得CPU的执行权,也进不去,因为没有获取锁。
(2)同步函数
格式:就是在函数上加上synchronized即可。因为非静态函数需要被对象调用,所以非静态函数中都有一个所属对象引用,即this。同步函数使用的锁就this
下面还以售票的程序,来用同步函数的格式,实现同步。如下:
class Ticket implements Runnable
{
private int ticket = 100;
public void run()
{
while(true)
show();
}
//通过同步函数,实现同步。
public synchronized void show()
{
if(ticket>0)
System.out.println(Thread.currentThread().getName()+"....sale : "+ ticket--);
}
}
class TicketDemo2
{
public static void main(String[] args)
{
Ticket t = new Ticket();
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();
}
}
无论使用上面两种方法的哪一种,都必须保证存在两个或两个以上的线程,并且这些线程都是在使用同一个锁,否则实现不了不同,线程还是会有问题。在使用同步时要明确哪些是多线程的运行代码,哪些是多线程的共享数据以及运行代码中哪些是用来操作共享数据,明确了这些关键点之后,定义的同步会保证线程的安全
注意,因为静态函数中没有被对象调用,所以它内部没有对象引用,这时对一个静态函数同步,它的锁肯定不是this,而是它所属的类对应的字节码文件对象。格式为类名.class。因为静态函数进内存时,内存中一定有它所在的类的字节码文件对象。所以在静态函数同步时,就以字节码文件对象作为锁。
在我们之前学到的单例设计模式中,懒汉式需要方法调用时,才会创建对象。但如果是在多线程中调用此方法时,就容易出现安全问题,保证不了对象在内存中的唯一性,所以这时也要使用到同步。如下:
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;
}
}
五、线程间通信
就是多个线程在操作同一个资源,但是操作的动作不同。为了实现一个流程,不同的操作动作需要交替,这时需要用到wait、notify、notifyAll等方法。如下:
class Resource
{
private String name;
private String sex;
//定义判断标识
boolean flag;
//定义同步函数,表示输入
public synchronized void input(String name,String sex)
{ //如果已有资源,等待输入
if(!flag)
try{wait();}catch(Exception e){}
this.name=name;
this.sex=sex;
flag=false;
//唤醒等待线程
notify();
}
//表示输出
public synchronized void output()
{
//等待输出
if(flag)
try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+name+"........."+sex);
flag=true;
//唤醒等待线程
notify();
}
}
//定义输入线程
class InputDemo implements Runnable
{
private Resource res;
InputDemo(Resource res)
{
this.res=res;
}
public void run(){
int x=0;
while(true){
if(x==0)
res.input("小明","男");
else
res.input("haha","man");
x=(x+1)%2;
}
}
}
//定义输出线程
class OutputDemo implements Runnable
{
private Resource res;
OutputDemo(Resource res)
{
this.res=res;
}
public void run(){
while(true)
res.output();
}
}
class Test
{
public static void main(String[]args)
{
Resource res=new Resource();
//创建并启动线程
new Thread(new InputDemo(res)).start();
new Thread(new OutputDemo(res)).start();
}
}
执行结果为在控制台上交替打印"线程名"“小明”...........“男”和"线程名"“hehe”..........“man”。
1.这些方法存在与同步中。
2.使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。
3. 锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
(2)wait(),sleep()有什么区别?
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
在实际开发中,有时存在每个不同的操作动作都有多个线程在执行。如下:
/**
需求:多个生产者生产商品,每生产一件商品消费者就购买该商品。
*/
class ProducerConsumerDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
//创建线程
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();
}
}
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name)
{
//循环判断被唤醒线程的标识
while(flag)
try{wait();}catch(Exception e){}
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
flag = true;
//唤醒所有等待的线程
notifyAll();
}
public synchronized void out()
{
//循环判断被唤醒线程的标识
while(!flag)
try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);
flag = false;
//唤醒所有等待的线程
notifyAll();
}
}
//定义线程 表示生产
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
res.set("+商品+");
}
}
}
//定义线程 表示消费
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
res.out();
}
}
}
对于多个生产者和消费者,为什么要定义while判断标示:因为被唤醒的等待线程可能有多个,让被唤醒的线程再一次判断标识。JDK1.5中提供了多线程升级解决方案。将同步synchronized替换成显示的Lock操作,将Object中的wait、notify、notifyAll,替换成了Condition对象。该对象可以通过Lock锁获取,并支持多个相关的Condition对象。在这种方法中,实现了本方只唤醒对方的操作。如下:
/**
需求:多个生产者生产商品,每生产一件商品消费者就购买该商品。
*/
class ReflectTest1
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
//创建线程
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();
}
}
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
//定义Lock对象
Lock lock=new ReentrantLock();
//创建Conditon对象,用来唤醒对方线程。
Condition condition_con=lock.newCondition();
Condition condition_pro=lock.newCondition();
public void set(String name)
{ //实现锁
lock.lock();
try{
while(flag)
try{condition_pro.await();}catch(Exception e){}
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
flag = true;
//唤醒对方线程
condition_con.signal();
}
//释放锁的动作一定要执行
finally{
lock.unlock();
}
}
public synchronized void out()
{
//实现锁
lock.lock();
try{
while(!flag)
try{condition_con.await();}catch(Exception e){}
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;
}
public void run()
{
while(true)
{
res.set("+商品+");
}
}
}
//定义线程 表示消费
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
res.out();
}
}
}
注意,释放锁的动作一定要执行,所以放在finally语句中。
六、停止线程
停止线程只有一种方法,就是让run方法结束。
开启线程运行,运行代码通常是循环结构,只要控制住循环,就可以让run方法结束,线程就结束了,这时一般需要通过定义标识,通过标识的变化来实现。如下:
class StopThread implements Runnable
{
private boolean flag =true;
public void run()
{ //通过标识,控制循环
while(flag)
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);
int num = 0;
while(true)
{
if(num++ == 60)
{ //调用改变标识的办法,结束线程。
st.changeFlag();
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
}
}
class StopThread implements Runnable
{
private boolean flag =true;
public void run()
{
while(flag)
{ //线程冻结
try{Thread.sleep(30)}catch(Exception e){}
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.start();
t2.start();
int num = 0;
while(true)
{
if(num++ == 60)
{
//清除冻结状态
t1.interrupt();
t2.interrupt();
//改变标识
st.changeFlag();
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");
}
}
-------------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------