---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IOS开发</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------
一、进程与线程
1,进程:就是正在执行的程序,所占有的内存的空间
2,线程:其实就是进程中的一个子程序,就是一个独立的执行路径,独立的执行控制单元
二、创建线程的两种方式
java中可以进行多线程的程序编程,其实线程是由操作系统开启,依靠java虚拟机来实现,线程也是对象,有了线程对象的描述类 java.lang.Thread,就可以进行多线程的编程。
1,创建线程的第一种方式——继承Thread类
继承Thread类的方式,数据是线程独享的,通过以下三步进行线程的创建
1.1,定义类继承Thread
1.2,复写Thread类的run方法
1.3,创建线程子类对象,调用线程的start方法开启线程
class Demo extends Thread
{
public void run(){
for(int x = 0 ; x < 20 ; x++){
System.out.println("run..."+x);
}
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Demo d = new Demo();
d.start();
for(int x = 0 ; x < 20 ; x++){
System.out.println("main..."+x);
}
}
}
程序在执行到Demo d = new Demo()时创建线程d,执行d.start()时开启这个线程,此时存在两个线程主线程与线程d,run方法与main方法中的for语句交替执行。
2,创建线程的第二种方式——实现Runnable接口
继承有局限性,java中只能单继承,接口可以多实现,Runnable接口方式,数据是程共享的,通过实现Runnable接口创建线程通过以下步骤完成
2.1,定义类实现Runnable接口
2.2,复写Runnable接口的run方法
2.3,通过Thread类建立线程对象
2.4,将Runnable接口子类对象作为实际参数传递给Thread类的构造函数
2.5,调用Thread类的start方法开启线程并调用Runnable接口子类对象的run方法
class Ticket implements Runnable
{
private int tickets = 100;
public void run(){
while(true){
if(tickets > 0){
System.out.println(Thread.currentThread().getName()+" 出售第"+tickets--);
}
}
}
}
class TicketDemo
{
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.setName("一号窗口");
t2.setName("二号窗口");
t3.setName("三号窗口");
t4.setName("四号窗口");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
三、多线程的安全问题
1,原因
多线程操作同一个共享数据的时候,会发生数据安全问题,以售票系统为例,if(tickets > 0)与System.out.println(Thread.currentThread().getName()+" 出售第"+tickets--)这两句话操作共享数据tickets,这样就会出现当一个线程池判断tickets=1时,进入if语句,还未执行到tickets--),时第二个线程进入判断tickets > 0也进入if语句,这样就会导致出现负票的现象。下面程序中使用wait方法让线程还没有执行完所有操作共享数据的语句就停下来,然后让其它线程进来执行来造成打印负票的现象
class Ticket implements Runnable
{
private int tickets = 100;
public void run(){
while(true){
if(tickets > 0){
try{
Thread.sleep(10);
}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+" 出售第"+tickets--);
}
}
}
}
2,解决办法
2.1,思路:让一个线程执行操作共享数据的多条语句时,其他线程执行不了,当这个线程执行完后其它线程才能执行
2.2,办法:同步机制synchronized(对象){线程操作的共享数据 },同步中的对象,看成一把锁,线程进到同步代码块后,拥有了锁,没有锁的线程,就不会执行,当有锁的线程执行完毕后,会释放锁其他线程就开始抢锁。
class Ticket implements Runnable
{
private int tickets = 100;
public void run(){
while(true){
synchronized(this){//同步机制
if(tickets > 0){
try{
Thread.sleep(10);
}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+" 出售第"+tickets--);
}
}
}
}
}
class TicketDemo
{
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.setName("一号窗口");
t2.setName("二号窗口");
t3.setName("三号窗口");
t4.setName("四号窗口");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
四、同步代码块与同步函数
1,同步代码块是将多条操作共享数据的语句放到synchronized(锁对象){}的大括号中,实现同步
2,同步函数是将同步放在函数上也叫方法,来达到同步,使用方式如下,Bank类为银行具有存钱的功能其内部sum为共享数据且有多条语句操作它,而且语句都需要同步因此把同步放在函数上
class Bank
{
private int sum;
public synchronized void add(int n )
{ sum = sum+n;
try{Thread.sleep(10);}catch(Exception e){}
System.out.println("sum="+sum);
}
}
class Cash implements Runnable
{
private Bank b = new Bank();
public void run()
{
for(int x=0;x<3;x++)
{
b.add(100);
}
}
}
class CashDemo
{
public static void main(String[] args)
{
Cash c = new Cash();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
五、同步锁
1,同步函数的锁是this,同步函数使用的锁对象是this
程序通过判断flag标记来决定执行同步函数show或同步代码块,当同步代码块为的锁对象为obj即synchronized(obj)时程序出现负票现象,当同步代码块为的锁对象为this即synchronized(this)时程序运行正常。因此说明同步函数的锁是this。
class ThisTicket implements Runnable
{
private int ticket = 100;
boolean flag = true;
Object obj = new Object();
public void run()
{
if(flag)
{
while(true)
{
//synchronized(obj) //obj锁
synchronized(this) //this锁
{
if(ticket>0)
{
try
{
Thread.sleep(10);
}
catch (Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"code sale:"+ticket--);
}
}
}
}else
{
while(true)
show();
}
}
public synchronized void show()//this锁
{
if(ticket>0)
{
try
{
Thread.sleep(10);
}
catch (Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"method sale:"+ticket--);
}
}
}
class ThisLockThread
{
public static void main(String[] args)
{
ThisTicket tt = new ThisTicket();
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
t1.start();
try
{
Thread.sleep(10);
}
catch (Exception e)
{
}
tt.flag=false;
t2.start();
}
}
2,静态同步函数的锁是所在类字节码文件
上例程序中将show声明为static即public static synchronized void show()时,当同步代码块为的锁对象为obj即synchronized(obj)时程序出现负票现象,当同步代码块为的锁对象为this即synchronized(this)时程序出现负票现象。当同步代码块为的锁对象为StaticTicket.class即synchronized(StaticTicket.class)时程序运行正常,因此说明同步函数的锁是StaticTicket.class。因此静态同步函数的锁是所在类字节码文件。
六、总结同步的前提
1,必须要有两个或以上个线程
2,必须是多个线程使用同一个锁
七、多线程中生产者与消费者问题
多线程不同方向操作数据,解决办法 1.,线程只要被唤醒,就必须判断标记。2,唤醒全部的线程
//定义产品
class Rescourc
{
private String name ;//产品的名字
private int count = 0 ;//产品的计数器
private boolean flag = false;
//提供一个赋值的方法,生产产品
public synchronized void set(String name){
while(flag==true){//生产完了,没消费呢,不能生产了
try{ this.wait();}catch(Exception e){}
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+
"生产---"+this.name);
//标记改成true
flag = true;
this.notifyAll();
}
//提供一个获取值的方法,消费产品
public synchronized void get(){
while(flag==false){//消费完了,没生产了,不能再消费了
try{ this.wait();}catch(Exception e){}
}
System.out.println(Thread.currentThread().getName()+
"消费======="+this.name);
flag = false;
this.notifyAll();
}
}
//定义生产者类,实现接口,覆盖run方法
class Pro implements Runnable
{
private Rescourc r ;
Pro(Rescourc r){ this.r = r;}
public void run(){
while(true){
r.set("鼠标");
}
}
}
//定义消费者类,实现接口,覆盖run方法
class Cus implements Runnable
{
private Rescourc r ;
Cus(Rescourc r){ this.r = r;}
public void run(){
while(true){
r.get();
}
}
}
class ProCusDemo
{
public static void main(String[] args)
{
Rescourc r = new Rescourc();//资源产品
Pro p = new Pro(r);//生产者
Cus c = new Cus(r);//消费者
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
Thread t3 = new Thread(p);
Thread t4 = new Thread(c);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
程序Rescourc类的run方法使用的是while(flag==true)语句而不是if语句,因为while语句要反复判断标记,而if不是。this.notifyAll()在唤醒线程时使用的是notifyAll()包括唤醒所有线程包括不同操作方的线程,而notify()只唤醒最先wait的线程容易造成所有线程进入wait。
八、停止线程
1,由于stop方法已经过时,因此停止线程的方法只有一种就是让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);
t1.start();
t2.start();
int n=0;
while(true)
{
if(n++==60)
{
st.changeFlag();
break;
}
System.out.println(Thread.currentThread().getName()+" run--------"+n);
}
System.out.println("over");
}
}
主线程执行if语句当n=60时st对象调用changeFlag方法将标记变为false从而使run方法中while语句的标记变为false,使while语句结束循环,结束线程。
2,当线程进入冻结状态将读不到标记,线程就不会结束,使程序挂起,因此当没有指定方式让线程恢复到运行状态时,就需要对冻结进行清除,强转让线程恢复到运行状态,在对标记进行判断,从而使循环结束,线程结束。
2.1,Thread类的interrupt()方法解决
class StopThread implements Runnable
{
private boolean flag=true;
public synchronized void run()
{
while(flag)
try
{
wait();
}
catch (Exception e)
{
System.out.println(Thread.currentThread().getName()+" exception");
}
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 n=0;
while(true)
{
if(n++==60)
{
st.changeFlag();
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+" run--------"+n);
}
System.out.println("over");
}
}
线程t1和t2执行wait()后进入冻结状态,因而读不到flag,所以程序结束不了,t1和t2在调用interrupt方法后回复到运行状态,执行while语句判断标记为false,结束循环,结束线程。
---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IOS开发</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------
详细请查看:<a href="http://edu.csdn.net" target="blank">http://edu.csdn.net</a>