(7)Java多线程

Java多线程

Java多线程

概述:

对于多线程,我们先要理解线程,而要理解线程,就必须要知道进程!

什么是进程:

是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径或者叫控制单元。

什么是线程:

线程就是进程中的一个独立的控制单元,线程在控制着进程的执行

什么是多线程?

javaJVM启动时,会有一个java.exe的执行程序,也就是一个进程。而在进程中至少会有一个线程在控制着java程序的执行,而这个线程运行的代码存在于main函数中,这个线程也称之为主线程。另外JVM除了执行主线程,还有一个控制着java垃圾回收机制的线程,这种在一个进程里面有多个线程执行就叫多线程。

多线程存在的意义

多线程的出现能让程序产生同时运行的效果,可以提高程序的运行效率。

例如:当java.exe控制的主线程,当执行的代码特别多的时候,就会在堆内存里存放很多对象,当对象调用完了以后就成为了垃圾,堆了很多酒可能造成堆内存内存不足的情况,甚至卡死,这样执行效率就很低,如果有另一个线程来控制垃圾回收机制,这样整个进程的效率就高很多。

计算机CPU工作原理

平时我们看到电脑或者智能手机很多程序在同时运行,貌似CPU在同时处理所有的这些软件,实际上在某一时刻单核CPU只能处理一个程序,我们看着在同时运行,实际上是CPU在做着快速的切换动作。

多线程执行的随机性

计算机CPU执行哪一个程序是没有规律的,谁抢到cpu执行权,或者cpu执行哪个程序,cpu就执行谁,而cpu执行某一程序只执行一会儿,就会再次随机执行另外一个程序,多线程也是一样具有随机性,运行时在做着快速的切换操作,谁抢到就执行谁,至于执行多长CPU说了算。

创建线程的方式:

创建线程有两种方式:继承Thread类方式和实现Runnable接口方式

第一种:继承方式:

在查阅JavaAPI帮助文档,发现java中提供了对线程这类事物描述的类—Thread类。我们只需要通过继承Thread类并复写其run方法就可以来创建线程。

创建步骤:

1, 定义类继承Thread

2, 复写其Thread类中的run方法。

目的:将自定义代码存储在run方法中,让线程来运行。

3, 创建定义类的实例对象,相当于创建了一个线程;

4, 用该对象调用线程的start方法;

两个作用:启动线程,和调用run方法;

面试考点:

创建一个实例对象(线程对象)调用start与调用run方法的区别?

首先,调用start方法是开启线程并执行该线程的run方法。调用run只是简单的对象调用对象方法,之前线程创建了可是没有被运行。

另外关于为什么要复写run方法?

因为Thread类是用于描述线程的,他定义了一个功能来存储要运行的代码,该功能就是run方法,如果没有复写,那开启多线程只是用了父类里面的没有自定义的空run方法。

获取线程对象以及名称

线程都有自己默认的名称:Thread-编号 该编号是从0开始的。

获取对象方法:static Thread   currentThread()

获取名称:getName()

设置线程名称:

setName()或者构造函数;

父类已经把事干完了,直接super(name)即可;

小代码示例:

*
小练习:创建两个线程,与主线程间交替运行
*/
class Demo extends Thread{
	//private String name;
	Demo(String name){
		//this.name = name;
		super(name);
	}
	public void run()//复写run方法
	{
		for (int x = 0;x<100 ;x++ ){
			System.out.println(Thread.currentThread()+"-------run");//获取线程对象
		}
	}
}
class  ThreadTest{
	public static void main(String[] args) {
		Demo d1 = new Demo("张三");//创建第一个线程
		Demo d2 = new Demo("李四");//创建第二个线程
		d1.start();//开启线程
		d2.start();
		for (int x=0;x<100 ;x++ )//主程序执行的代码
		{
			System.out.println("main");
		}
	}
}

线程运行状态

线程在实际中有几种状态,清楚了才能知道线程在怎么运作

1, 被创建状态:等待开启线程,调用start启动;

2, 运行状态:具有执行资格和执行权;

3, 临时阻塞状态:具有执行资格但是没有执行权;

4, 冻结状态(阻塞):当使用sleep(time)方法或者wait()方法,该线程就放弃了执行资格,而当时间一过或者使用notify()唤醒线程,就具有了执行资格没有执行权变为临时状态。

5, 消亡状态:当stop()或者run()方法结束

注意:当线程对象已经调用了start()方法开启了线程,在运行时就不能再调用start开启线程,否则会提示线程运行异常。

下面是运行状态图:



创建线程第二种方式:实现Runnable接口

在实际操作中我们发现继承Thread类有弊端:

一 若该类继承了其他类就无法在通过Thread类来创建多线程;

二 当多线程共同调用一个成员变量时,要么将成员变量静态(存在时间久占用内存)要么就定义一个Thread对象(可是线程被创建开启到运行状态就不能再start());

所以就有了第二种创建线程的方式实现Runnable接口并复写其中run方法。

创建步骤:

1, 定义类实现Runnable接口

2, 复写Runnable接口中的run方法,目的也是为了将运行代码存储在该run()方法中;

3, 创建该类对象

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

为什么呢?

因为自定义的run方法所属对象是Runnable接口的子类对象所以要让线程去指定对象的run方法,就必须明确该run方法所属对象

5, 调用Thread类的start方法开启线程并调用Runnbale接口子类的run方法。

面试题:实现方式和继承方式的区别?

实现费那个是好处:避免了单继承的局限性;

在实际开发中建议使用实现方式。

两种方式区别:

继承方式是:线程代码存放Thread子类run方法中;

实现方式是:线程代码存放Runnbale接口子类run方法中。

代码示例:

/*
需求:简单的卖票程序,多个窗口同时卖票
*/
class Ticket implements Runnable{
	private int num = 100;
	public void run(){
		while (true){
			if (num>0){
				System.out.println(Thread.currentThread().getName()+"票数:"+num--);//显示线程名称和剩余票数
			}			
		}		
	}
}
class  TicketDemo{
	public static void main(String[] args) {
		//创建Runnable接口子类的实例对象  
        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();
	}
}

多线程安全问题

问题原因:

当多条语句在操作同一个线程的共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程就参与进来,导致共享数据的错误。

这是多线程的随机性所导致的。

解决办法—同步

就是对多条操作共享数据的语句进行控制,只让其中一个线程执行完了再让另一个执行,在执行中其他线程不可以参与执行。

java中对于多线程的安全问题提供了专业的解决方式:synchronized(同步);

同步代码块:

synchronized(对象)

{

需要同步的代码

}

为什么同步可以解决安全问题的根本?

原因是同步那个参数对象,持有锁的线程可以在同步内执行,没有锁的线程及时有执行权,也没法进去,因为没有锁这个对象。

示例:卖票例子加上同步代码块

lass Ticket implements Runnable{
	private int num = 100;
	Object obj = new Object();
	public void run(){
		while (true){
			synchronized(obj){//给程序加锁
				if (num>0){
					try
					{
Thread.sleep(10);//让该线程睡个10毫秒,模拟线程的安全问题,因为sleep方法又异常声明,所以要对其进行异常处理,该异常不能抛出
  }
  catch(Exception e)
  {}
System.out.println(Thread.currentThread().getName()+"票数:"+num--);//显示线程名称和剩余票数
				}
			}			
		}		
	}
}

同步函数

实现方式:

在函数上加上synchronized修饰即可。

同步函数使用的是哪一个锁呢?

函数需要被对象调用,那么函数都有一个所属对象引用,就是this

所以同步函数所使用的锁是this

/*
需求:简单的卖票程序,多个窗口同时卖票,使用同步函数
*/
class Ticket implements Runnable
{
	private int num = 100;
	public void run()
	{
		while (true)
		{
			show();		
		}		
	}
	public synchronized void show()
	{
		if (num>0)
			{
			try
			{
				Thread.sleep(10);//让该线程睡个10毫秒,模拟线程的安全问题,
				//因为sleep方法又异常声明,所以要对其进行异常处理,该异常不能抛出
				
			}
			catch(Exception e)
			{}
			System.out.println(Thread.currentThread().getName()+"票数:"+num--);
				//显示线程名称和剩余票数
	}
}

同步前提:

1, 必须要有两个或者两个以上的线程。

2, 必须是多个线程使用同一个锁。

同步利弊:

好处,解决了多线程的安全问题;

坏处,多个线程需要判断锁,比较消耗资源

怎样判断多线程的安全问题?

1, 明确哪些代码是多线程运行代码;

2, 明确哪些是共享数据;

3, 明确多线程运行代码中哪些语句是操作共享数据的。

静态同步函数的锁是Class对象

如果同步函数被静态所修饰后,使用的锁是什么呢?

验证发现不是this,因为静态方法中不可以定义this

分析,静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。

类名.class 该对象的类型是Class

所以静态同步方法使用的锁是该方法所在类的字节码文件对象,即:类名.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;
	}
}

注意面试经常考点!

懒汉式和饿汉式有神马不同?

懒汉式的特点在于实例的延迟加载,在多线程时有访问问题,可以加同步来解决,加同步代码块或者同步函数都行但是稍微低效,可以使用双重判断可以解决效率问题,加同步时使用的锁是哪一个?是该类所属的字节码文件对象。

一般笔试题:请写一个延迟加载的单例模式示例。

多线程—死锁

死锁原因:

当同步嵌套同步时,锁却不同,就可能有出现死锁状态

*
写一个死锁程序
*/
class Ticket implements Runnable{
	private boolean flag;
	Ticket(boolean flag){
		this.flag = flag;
	}
	public void run(){
		if (flag){
			while (true){
				synchronized(PanDuan.o1){
					System.out.println(Thread.currentThread().getName()+"--if01");
					synchronized(PanDuan.o2){
						System.out.println(Thread.currentThread().getName()+"--if02");
					}
				}
			}
		}
		else{
			while (true){
				synchronized(PanDuan.o2){
					System.out.println(Thread.currentThread().getName()+"--if02");
					synchronized(PanDuan.o1){
						System.out.println(Thread.currentThread().getName()+"--if01");
					}
				}
			}
		}
	}
}
class PanDuan//定义两个锁
{
	static Object o1 = new Object();
	static Object o2 = new Object();
}
class  DeadLockDemo{
	public static void main(String[] args) {	//创建两个线程并启动
		new Thread(new Ticket(true)).start();
		new Thread(new Ticket(false)).start();
	}
}

线程间通信:

可以简单理解为多个线程在操作同一个共享数据,但是线程间的操作动作不同。

比如:一堆煤,有卡车往里面送,也有卡车往外面出,因为多线程的延迟性和随机性,可能会造成数据接收的错误,如:煤还没有送进去这个线程没有执行权了,送出去的卡车有执行权了,这样就会数据混乱了。

在线程间通讯时,要让其线程有序的执行代码,就要用到等待唤醒机制。

示例:

运用多线程原理,对姓名和性别进行存入和取出。(实际上就是使用同步操作同一资源)

分析:

因为要对资源进行存入和取出,为了避免出现姓名和性别的不一致需要进行synchronized

同步同时需要用到等待唤醒机制,先定义标记,然后当一个线程执行完后,唤醒另外一个线程,并改变标记等待;

另外一线程唤醒开启后运行结束前再唤醒之前线程,并改变标记等待,依次往复。

步骤:

1,定义Message类,对存储和获取姓名和性别定义方法,因为方法是拥有并操作数据所以加同步

2,定义两个类,分别实现Runnable接口复写run方法,调用Message类中的存储和获取姓名和性别的方法;

3,建立Message对象,并对实现了Runnable接口的类开启线程

class Message{
	private String name;
	private String sex;
	private boolean flag = false;
	public synchronized void setInput(String name,String sex){//存入姓名和性别
		if (flag)
			try{
				this.wait();//如果标记为真,本线程就等待,释放执行权,释放锁
			}
			catch (Exception e){
			}
		this.name = name;
		this.sex = sex;
		flag = true;//改变标记
		notify();//唤醒线程,一般唤醒第一个等待的线程。	
	}
	public synchronized void getOutput(){//获取姓名和性别
		if (!flag)
			try{
				this.wait();
			}
			catch (Exception e){
			}
		System.out.println(name+"。。。。。"+sex+"。");
		flag = false;
		notify();
	}
}
class Input implements Runnable{
	private Message m;
	Input(Message m){
		this.m = m;
	}
	public void run()//复写run方法{
		int x = 0;
		while(true){
			if (x==0)	{
				m.setInput("张三","男");
			}
			else	{
				m.setInput("xiaomei","nv");
			}
			x = (x+1)%2;//使之循环往复,亦可以定义布尔型标记。
			
		}
	}
}
class Output implements Runnable{
	private Message m;
	Output(Message m){
		this.m = m;
	}
	public void run(){
		while(true){
			m.getOutput();
		}
	}
}
class  InputOutputDemo
{
	public static void main(String[] args) {
		Message m = new Message();//创建对象
		new Thread(new Input(m)).start();//创建线程并开启。
		new Thread(new Output(m)).start();
	}
}部分打印结果如图:

线程间通信——生产者和消费者JDK5.0升级版

关于生产者和消费者的多线程示例

因为这里面涉及的是多个生产者和多个消费者,在具体的等待和唤醒时为了避免全部等待,需要对全部唤醒,但是这样也把自己的也唤醒了,所以在JDK5.0升级后,有了非常大的升级改变

1, 将同步synchronized替换成了Lock操作

2, Object中的waitnotifynotifyAll替换成了Condition对象

该对象可以对Lock锁进行获取,实现了本方唤醒对方的操作。

查询API文档中的java.util.concurrent.locks包,里面有LockCondition接口。

1,因为Lock接口不能new对象所以里面有一子类ReentrantLock,通过子类对象调用接口方法

Lock lock = new ReentrantLock();

lock.lock();//获取锁

lock.unlock();//释放锁

lock.newCondition();//返回绑定到此 Lock 实例的新 Condition 实例。

2,因为lock.newCondition(),返回的是一个Condition对象,所以就可以调用该接口方法。

Condition condition = lock.newCondition();

condition.await();//使当前线程等待;

condition.signal();//唤醒等待线程。

具体示例如下:

/*
需求:编写关于消费者和生产者的多线程示例
*/
import java.util.concurrent.locks.*;
class Resource//资源{
	private String name;
	private boolean flag = false;
	private int x = 1;
	Lock lock = new ReentrantLock();//获取接口子类对象
	Condition condition_pro = lock.newCondition();//获取两个Condition对象
	Condition condition_con = lock.newCondition();//来分别控制生产者和消费者的等待和唤醒
	public void setPro(String name)throws InterruptedException//抛出等待异常{
		lock.lock();//给生产者上锁
			try{
				while (flag)
					condition_pro.await();//生产者等待释并放执行权
				this.name = name+".."+(x++);
				System.out.println(Thread.currentThread().getName()+"生产:"+this.name);
				flag = true;
				condition_con.signal();//唤醒消费者	
			}
			finally{
				lock.unlock();//一定要执行的释放本线程锁资源
			}		
	}
	public void getCon()throws InterruptedException{
		lock.lock();//给消费者上锁
			try{
				while (!flag)
					condition_con.await();//消费者等待并释放执行权
				System.out.println(Thread.currentThread().getName()+"。。消费:"+this.name);
				flag = false;
				condition_pro.signal();//唤醒生产者
			}
			finally{
				lock.unlock();//一定要执行的释放本线程锁资源
			}
	}
}
class Producer implements Runnable{
	private Resource r;
	Producer(Resource r){
		this.r = r;
	}
	public void run()//复写run方法{
		int x = 0;
		while (true){
			try{
				r.setPro("商品");
			}
			catch (InterruptedException e)//把异常处理{
			}			
		}
	}
}
class Consumer implements Runnable{
	private Resource r;
	Consumer(Resource r){
		this.r = r;
	}
	public void run(){
		while (true){
			try{
				r.getCon();
			}
			catch (InterruptedException e){
			}
			
		}
	}
}
class  ProducerConsumerDemo{
	public static void main(String[] args) {
		Resource r = new Resource();
		new Thread(new Producer(r)).start();//创建并开启生产者线程;
		new Thread(new Consumer(r)).start();//创建并开启消费者线程;

		new Thread(new Producer(r)).start();//
		new Thread(new Consumer(r)).start();//
	}
}
打印结果如图:

停止线程

stop方法已经过时,只有一种,让run方法结束。

开启多线程运行,运行代码通常是循环结构。

只要控制住循环就可以让run方法结束,也就是线程结束。

一般通用方法

原理:在run方法中定义一个标记,当程序运行一段时间后,自动改变标记并判断标记,run方法结束线程结束。

另外还有一种特殊情况:

当线程处于冻结状态,就不会读取到标记,那么线程就不会结束。

当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除,强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。

Thread类中提供了该方法 interrupt方法,同时调用这个方法时会发生InterruptedException中断异常。

示例:

class Demo implements Runnable{
	private boolean flag = true;
	public synchronized void run(){
		while (flag){
			try	{
				this.wait();
			}
			catch (InterruptedException e)	{
				System.out.println(Thread.currentThread().getName()+"...");
				flag = false;
			}
			System.out.println(Thread.currentThread().getName()+"run");
		}
	}
	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.start();
		int x = 0;
		while (true){
			if (x++==50){
				t1.interrupt();
				t2.interrupt();
				d.changeFlag();
				break;
			}
			System.out.println(Thread.currentThread().getName()+x);
		}
	}
}

守护线程(后台线程)

Thread里面的常见方法:

setDaemon(boolean on):将该线程标记为守护线程或用户线程,当正在运行的线程都是守护线程时,java虚拟机退出,该方法必须在启动线程前使用。

特点:

开启后和前台线程共同抢劫cpu的执行资格开启运行没区别,但是结束有区别,当前台全部结束后,后台才自动结束。

主线程相当于前台线程,当主线程结束,后台也结束。

扩展知识:

Join方法

当A线程执行到了B线程的join()方法时,A就会等待,等B线程都执行完了,A线程才会执行。

作用:join可以用来临时加入线程执行。

setPriority(int num)线程执行优先级

优先级的大小是1到10,一般是1,5,10,在Thread构造函数里已经设置为常量,

Static void  yield()

暂停当前正在执行的线程对象,并执行其他线程。


什么时候使用多线程?

当某些代码需要被同时执行时就可以使用单独线程进行封装。

如:

new Thread(){
	public void run(){
		For(int x = 0;x<100;x++){
			System.out.println(x);
<span style="white-space:pre">	</span>}
  }
}.start();
---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------详细请查看: http://edu.csdn.net
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值