黑马程序员-java基础(五)-多线程

------- android培训java培训、期待与您交流! ----------

多线程

1  创建线程

java中Thread类用于描述线程

【】创建线程方法一

1、定义类继承Thread

Thread中run()方法用于存储线程要运行的代码

2、复写Thread中的run方法

将自定义的代码存储在run方法中

3、调用线程的start方法(启用线程,调用run方法)【线程都有自己的名称,Thread-编号,编号从0开始】

class MyThread extends Thread //定义类继承Thread
	{
	public void run()//覆写Thread中的run方法 
	{
		for (int i = 0; i < 450; i++)
		{
			System.out.println("Thread线程走起。。。"+i);
		}
		
	}
	}
 
 class Test{
        public static void main(String[] args){
        MyThread a=new MyThread();//创建线程
        a.start();//启动线程,并执行run方法
 for (int x = 0; x < 350; x++) {
			System.out.println("hallo java"+x);
		}       
        }
 }

P.S.

线程的几种运行状态:


P.S.

currentThread():获取当前线程对象,可以用this代替

get name():获取线程名称

setName 或 构造函数:设置线程名称:
class MyThread extends Thread //定义类继承Thread
	{
	MyThread(String s){
		super(s);
	}
	public void run()//覆写Thread中的run方法 
	{
		for (int i = 0; i < 3; i++)
		{
			System.out.println((Thread.currentThread()==this)+"..."+this.getName()+"Thread线程走起。。。"+i);//getName()获取线程名称
		}
		
	}
	}
 
 class Test{
        public static void main(String[] args){
        MyThread a=new MyThread("a---");//创建线程
        MyThread b=new MyThread("b---");
        //a.setName("MY---");//设置线程名称
        a.start();//启动线程,并执行run方法
        b.start();
        //a.run();//一般对象调用,没有启动线程
        for (int x = 0; x < 3; x++) {
			System.out.println("hallo java"+x);
		}       
        }
 }

运行结果:


【】创建线程方法二

1、定义类实现Runnable接口

2、覆盖Runnable中的run方法

3、通过Thread类建立线程对象

4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数

Thread(Runnable target),将子类Runnable对象传递给Thread类的构造函数,自定义的run方法所属的是Runnable接口的子类对象,

5、调用Thread类的start方法开启线程并调用Runnable接口类的run方法

class Ticket implements Runnable //定义类继承Thread
	{
	int num=10;
	public void run()//覆写Thread中的run方法 
	{
		while(true){
		if (num>0)
		System.out.println(Thread.currentThread().getName()+"售票"+num--);
		}
	}
	}
 class Test{
        public static void main(String[] args){
        Ticket t=new Ticket();
        Thread a1=new Thread(t);
        Thread a2=new Thread(t);
        Thread a3=new Thread(t);
        a1.start();//启动线程,并执行run方法\
        a2.start();
        a3.start();
	     
        }
 }


P.S.

第二种创建多线程的方式避免因java单继承的局限性

2  线程的安全问题


多线程的安全隐患:多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,另一个线程参与进来

class Ticket implements Runnable //定义类继承Thread
	{
	int num=100;
	public void run()//覆写Thread中的run方法 
	{
		while(true){
			if (num>0){
				try {
					Thread.sleep(10);//线程休眠10ms,会抛出异常,祥见api thread类
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"售票"+num--);
			
			}}
	}
	}
 class Test{
        public static void main(String[] args){
        Ticket t=new Ticket();
        Thread a1=new Thread(t);
        Thread a2=new Thread(t);
        Thread a3=new Thread(t);
        a1.start();//启动线程,并执行run方法\
        a2.start();
        a3.start();
        }
 }

运行结果


p.s.

public static void sleep(long millis,int nanos)
                  throws InterruptedException  在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行)

多线程安全问题原因:

1. 多个线程在操作共享的数据。
2. 操作共享数据的线程代码有多条。当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。

多线程安全解决法:

对多条操作共享数据语句,只能让一个线程都执行完,过程中,其他线程无法参与

解决方法一:【同步代码块】格式:synchronized{需要同步的代码块}-类似火车卫生间上锁

【】同步的前提:

1、必须有两个以上的线程

2、必须多个线程拥有同一个锁【静态同步函数的锁是class对象即类名.Class-字解码对象文件】

class Ticket implements Runnable //定义类继承Thread
	{
	int num=500;
	Object obj=new Object();
	public void run()//覆写Thread中的run方法 
	{
		while(true){
			synchronized(obj) {//同步代码块,锁是obj
			if (num>0){
				try {
					Thread.sleep(10);//线程休眠10ms,会抛出异常,祥见api thread类
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"售票"+num--);
			
			}}}
	}
	}
 class Test{
        public static void main(String[] args){
        Ticket t=new Ticket();
        Thread a1=new Thread(t);
        Thread a2=new Thread(t);
        Thread a3=new Thread(t);
        a1.start();//启动线程,并执行run方法\
        a2.start();
        a3.start();
        }
 }

运行结果:


P.S.

原因在于Object对象相当于是一把锁,只有抢到锁的线程,才能进入同步代码块向下执行。因此,当num=1时,CPU切换到某个线程后,如上图的Thread-3线程,其他线程将无法通过同步代码块继而进行if判断语句,只有等到Thread-3线程执行完“num--”操作(此后num的值为0),并且跳出同步代码块后,才能抢到锁。其他线程即使抢到锁,然而,此时num值已为0,也就无法通过if语句判断,从而无法再执行“num--”的操作了,也就不会出现0、-1、-2等情况了。

解决方法二:同步函数】:把同步作为修饰符放函数上,格式: public synchronized void add(int  n)【同步函数的锁是this】

【例】

class Bank{
    private int sum ;
    public synchronized void  add(int num)//同步函数,锁为this
    {
    	sum = sum + num;
        System. out.println("sum = " + sum);
    }
}

class Cus implements Runnable{
    private Bank b = new Bank();
    public void run(){
          for(int x = 0; x < 8; x++){
                b.add(100);//存100
         }
   }
}

class Test{
    public static void main(String[] args){
         Cus c = new Cus();
         Thread t1 = new Thread(c);
         Thread t2 = new Thread(c);
         t1.start();
         t2.start();
   }
}

运行结果:


同步函数和同步代码块的区别:
1. 同步函数的锁是固定的this。
2. 同步代码块的锁是任意的对象。
建议使用同步代码块。

由于同步函数的锁是固定的this,同步代码块的锁是任意的对象,那么如果同步函数和同步代码块都使用this作为锁,就可以实现同步。
class Ticket implements Runnable{
    private int num = 100;
    boolean flag = true;

    public void run(){
          if(flag ){
                while(true ){
                      synchronized(this ){//同步代码块,将同步代码块的锁使用this,可以实现同步代码快和同步函数的同步
                            if(num > 0){
                                  try{
                                       Thread. sleep(10);
                                  } catch(InterruptedException e){
                                       e.printStackTrace();
                                  }
                                  System. out.println(Thread.currentThread().getName() + "...obj..." + num--);
                           }
                     }
               }
         } else
                while(true )
                     show();
     }

    public synchronized void show(){//同步函数
          if(num > 0){
                try{
                     Thread. sleep(10);
                } catch(InterruptedException e){
                     e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "...function..." + num--);
         }
   }
}

class Test{
    public static void main(String[] args){
         Ticket t = new Ticket();
         Thread t1 = new Thread(t);
         Thread t2 = new Thread(t);
         
         t1.start();
          try{//如果不加,线程t1尚未真正启动,flag已经设置为false,那么当t1执行的时候,就会按照flag为false的情况执行,线程t2也按照flag为false的情况执行,实验就达不到目的了。
               Thread. sleep(10);
         } catch(InterruptedException e){
               e.printStackTrace();
         }
         t. flag = false ;
         t2.start();
   }
}
p.s.
静态的同步函数使用的锁不是this(静态方法中不可能存在this)是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。
class Ticket implements Runnable{
    private static int num = 100;
    boolean flag = true;

    public void run(){
          if(flag ){
                while(true ){
                      synchronized(Ticket.class){//锁为Ticket的字节码文件对象,和下面的同步函数锁一样
                            if(num > 0){
                                  try{
                                       Thread. sleep(10);
                                  } catch(InterruptedException e){
                                       e.printStackTrace();
                                  }
                                  System. out.println(Thread.currentThread().getName() + "...obj..." + num--);
                           }
                     }
               }
         } else
                while(true )
                     show();
     }

    public static  synchronized void show(){//静态函数的锁是所属的字节码文件对象
          if(num > 0){
                try{
                     Thread. sleep(10);
                } catch(InterruptedException e){
                     e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "...function..." + num--);
         }
   }
}

class Test{
    public static void main(String[] args){
         Ticket t = new Ticket();
         Thread t1 = new Thread(t);
         Thread t2 = new Thread(t);
         
         t1.start();
          try{//下面这条语句一定要执行。因为可能线程t1尚未真正启动,flag已经设置为false,那么当t1执行的时候,就会按照flag为false的情况执行,线程t2也按照flag为false的情况执行,实验就达不到目的了。
               Thread. sleep(10);
         } catch(InterruptedException e){
               e.printStackTrace();
         }
         t. flag = false ;
         t2.start();
   }
}

【死锁】:锁的同步嵌套
class Die implements Runnable
{
	private Boolean flag;
	Die(Boolean flag)
	{
		this.flag=flag;
	}
	public void run()
	{
		if(flag)
		{
			synchronized (Lock.locka)
			{
				System.out.println("Ture...LOCK...A");
				synchronized (Lock.lockb)
				{
					System.out.println("Ture...LOCK...B");//线程一拿到A锁,阻塞在此,需要B锁
				}
			}
		}
		else
		{
			synchronized (Lock.lockb)
			{
				System.out.println("FALSE...LOCK...B");
				synchronized (Lock.locka)
				{
					System.out.println("FALSE...LOCK...A");//线程二拿到B锁,阻塞在此,需要A锁
				}
			}
		}
	}
	
}

class Lock
{
	static Object locka=new Object();
	static Object lockb=new Object();
}
class Test{
    public static void main(String[] args){
       
        Thread a1=new Thread(new Die(true));
        Thread a2=new Thread(new Die(false));
        a1.start();
        a2.start();
   }
}
运行结果:

原因:线程1拿了A锁需要B锁,线程二拿了B锁需要A锁
线程间的通信:
【生产消费问题-单生产、单消费】
多个线程在处理统一资源,但是操作动作却不同(例如:一个存一个取),这时候就需要线程间通信。
class Person{
	String name,sex;
	}

class Input implements Runnable{
	int i=0;
	Person pi=new Person();
	Input(Person p){
		pi=p;
	}
	public void run(){
		while(true)
		{
			if(i==1){//间隔存放标志
			pi.name="麻子";
			pi.sex="男...男...男...";}
			if(i==0)
			{
				pi.name="Ada";
				pi.sex="women...women...women...";
			}
			i=(i+1)%2;//将标志更改
		}
		}
}

class Output implements Runnable{
	Person po=new Person();
	Output(Person p){
		po=p;
	};
	public void run(){
		while (true)
		{
			System.out.println(po.name+"---"+po.sex);
		}
	}
}


public class Test {

	public static void main(String[] args) {
		Person p=new Person();
		Input i=new Input(p);
		Output o=new Output(p);
		Thread a1=new Thread(i);
		Thread a2=new Thread(o);
		a1.start();
		a2.start();					
	}

}
运行结果:


错误原因:输入输出线程不同步

class Person{
	String name,sex;
	}

class Input implements Runnable{
	int i=0;
	Person pi=new Person();
	Input(Person p){
		pi=p;
	}
	public void run(){
		while(true)
		{
			synchronized (Input.class) {//将输入输出线程同步,并定义同一个锁		
			if(i==1){
			pi.name="麻子";
			pi.sex="男...男...男...";}
			if(i==0)
			{
				pi.name="Ada";
				pi.sex="women...women...women...";
			}
			i=(i+1)%2;
			}
			}
		}
}

class Output implements Runnable{
	Person po=new Person();
	Output(Person p){
		po=p;
	};
	public void run(){
		while (true)
		{
			synchronized (Input.class) {//将输入输出线程同步,并定义同一个锁
				System.out.println(po.name+"---"+po.sex);	
			}
			
		}
	}
}


public class Test {

	public static void main(String[] args) {
		Person p=new Person();
		Input i=new Input(p);
		Output o=new Output(p);
		Thread a1=new Thread(i);
		Thread a2=new Thread(o);
		a1.start();
		a2.start();					
	}

}



如何实现,存一个取一个?

应用【等待唤醒机制】

wait()等待

notify()唤醒

存完后等待,直到唤醒,取完后唤醒

P.S.

wait、notify、notifyAll都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,只有同步才有锁

这些方法在操作同步线程时,都必须标识他们所操作的线程

notify只能唤醒同一个监视器上的等待线程

又监视器是任意对象,因此这些方法要被定义在object中

class Person{
	String name,sex;
	Boolean flag=false;//资源中的标记
	}

class Input implements Runnable{
	int i=0;
	private Person p;
	Input(Person p){
		this.p=p;
	}
	public void run(){
		while(true)
		{
			synchronized (p) {		
				if(p.flag)
				try {
					p.wait();//wait会抛出异常,需要标识所操作的线程,用锁标识,锁.wait()
				} catch (Exception e) {					
				}
				if(i==1){
			p.name="麻子";
			p.sex="男...男...男...";}
			if(i==0)
			{
				p.name="Ada";
				p.sex="women...women...women...";
			}
			i=(i+1)%2;
			p.flag=true;
			p.notify();//需要标识所操作的线程
			}
			}
		}
}

class Output implements Runnable{
	private Person p;
	Output(Person p){
		this.p=p;
	};
	public void run(){
		while (true)
		{
			synchronized (p) {
				
				if(!p.flag)
				try {
					p.wait();//需要标识所操作的线程
				} catch (Exception e) {
					
				}
				else
				{
				System.out.println(p.name+"---"+p.sex);
				p.flag=false;
				p.notify();//需要标识所操作的线程
				}
				}
			
		}
	}
}


public class Test {

	public static void main(String[] args) {
		Person p=new Person();
		Input i=new Input(p);
		Output o=new Output(p);
		Thread a1=new Thread(i);
		Thread a2=new Thread(o);
		a1.start();
		a2.start();					
	}

}
运行结果


【生产消费问题-多生产、多消费】
class Resource{
	private String name;
	private int num=1;
	private Boolean flag=false;
	public synchronized void set(String name){
		
		if(flag){
			try {
				wait();
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		this.name=name+"....."+num++;
		System.out.println(Thread.currentThread().getName()+"生产。。。"+this.name);
		flag=true;
		notify();
		}
	public synchronized void out(){
		if(!flag){
			try {
				wait();
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		System.out.println(Thread.currentThread().getName()+"消费。。。。。。"+this.name);
		flag=false;
		notify();
	}
}

class Input implements Runnable{
	private Resource r;
	Input(Resource r){
		this.r=r;
	}
	public void run(){
		while(true){
			r.set("apple");
		}
	}
}

class Output implements Runnable{
	private Resource r;
	Output(Resource r){
		this.r=r;
	}
	public void run(){
		while(true){
			r.out();;
		}
		}
	}



public class Test {

	public static void main(String[] args) {
		Resource r=new Resource();
		Input in=new Input(r);
		Input in1=new Input(r);
		Output ot=new Output(r);
		Output ot1=new Output(r);
		Thread a1=new Thread(in);
		Thread a2=new Thread(in1);
		Thread a3=new Thread(ot);
		Thread a4=new Thread(ot1);
		a1.start();
		a2.start();
		a3.start();
		a4.start();
	}
}
运行结果:

按照单消费的思路,会出现安全隐患,生产一次,被消费了两次,解决方法:判断资源标记改while()循环,用notifyAll()唤醒所有等待线程

class Resource{
	private String name;
	private int num=1;
	private Boolean flag=false;
	public synchronized void set(String name){
		
		while(flag){//改if判断循环为while循环,让每次唤醒后都判断资源标记
			try {
				wait();//线程等待
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		this.name=name+"....."+num++;
		System.out.println(Thread.currentThread().getName()+"生产。。。"+this.name);
		flag=true;
		notifyAll();//改notify为notifyAll(),防止程序卡死
		}
	public synchronized void out(){
		if(!flag){
			try {
				wait();
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		System.out.println(Thread.currentThread().getName()+"消费。。。。。。"+this.name);
		flag=false;
		notify();
	}
}

class Input implements Runnable{
	private Resource r;
	Input(Resource r){
		this.r=r;
	}
	public void run(){
		while(true){
			r.set("apple");
		}
	}
}

class Output implements Runnable{
	private Resource r;
	Output(Resource r){
		this.r=r;
	}
	public void run(){
		while(true){
			r.out();;
		}
		}
	}



public class Test {

	public static void main(String[] args) {
		Resource r=new Resource();
		Input in=new Input(r);
		Input in1=new Input(r);
		Output ot=new Output(r);
		Output ot1=new Output(r);
		Thread a1=new Thread(in);
		Thread a2=new Thread(in1);
		Thread a3=new Thread(ot);
		Thread a4=new Thread(ot1);
		a1.start();
		a2.start();
		a3.start();
		a4.start();
	}
}
运行结果:


JDK1.5 新特性:

将同步和锁封装成对象

1、将隐式同步synchronized替换成显式的lock操作


Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵活,可以一个锁上加上多组监视器

1、lock():获取锁。
2、unlock():释放锁---为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。

2、将object中的wait、notify、notifyAll替换为Condition对象,将这些监视器方法单独进行了封装,变成Condition监视器对象,可以与任意锁进行组合

import java.util.concurrent.locks.*;

import java.util.concurrent.locks.*;

class Resource{
	private String name;
	private int num=1;
	private Boolean flag=true;
	Lock lock=new ReentrantLock();//创建一个锁对象
	Condition con1=lock.newCondition();//通过已有的锁获取该锁对应的监视器对象
	Condition con2=lock.newCondition();
	// synchronized void set(String name)
	public  void set(String name)throws Exception
	{	
		lock.lock();//锁
		try{
		while(flag)
		{
			con1.await();//con1监视器等待
		}
			this.name=name+"....."+num++;
			System.out.println(Thread.currentThread().getName()+"生产。。。"+this.name);
			flag=true;
			con2.signal();//唤醒con2
		}
		finally{
			lock.unlock();//解锁
		}
	}
	public  void out()throws Exception
	{
		lock.lock();//锁
		try {
			while(!flag){
				con2.await();//con2等待
			}
		System.out.println(Thread.currentThread().getName()+"消费。。。。。。"+this.name);
		flag=false;
		con1.signal();//唤醒con1
		}
		finally{
			lock.unlock();//解锁
		}
	}
}

class Input implements Runnable{
	private Resource r;
	Input(Resource r){
		this.r=r;
	}
	public void run(){
		while(true){
			try {
				r.set("apple");
			} catch (Exception e) {
				// TODO: handle exception
			}
			
		}
	}
}

class Output implements Runnable{
	private Resource r;
	Output(Resource r){
		this.r=r;
	}
	public void run(){
		while(true){
			try {
				r.out();
			} catch (Exception e) {
				// TODO: handle exception
			}
			
		}
		}
	}



public class Test {

	public static void main(String[] args) {
		Resource r=new Resource();
		Input in=new Input(r);
		Input in1=new Input(r);
		Output ot=new Output(r);
		Output ot1=new Output(r);
		Thread a1=new Thread(in);
		Thread a2=new Thread(in1);
		Thread a3=new Thread(ot);
		Thread a4=new Thread(ot1);
		a1.start();
		a2.start();
		a3.start();
		a4.start();
	}

}

运行结果


怎么控制线程的任务结束呢?    任务中都会有循环结构,只要控制住循环就可以结束任务。


总结

1、多线成可以解决不同任务同时进行的问题,解决很多现实生活中例如多窗口售票的问题,但是当多线程操作共有数据时容易产生安全隐患,这时候就需要应用同步的知识来解决安全隐患,这个是需要特别注意的点。

2、创建线程的两种方法:extends Thread、implents Runnable,两者原理上想通,最终都要创建Thread的子类或者类(实现接口的类作为参数传递)开启线程,第二种方法避免了单继承的局限性,更为常用



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值