黑马程序员 多线程

---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------

多线程是Java中一个非常重要的概念,它能使得一个进程中多个模块同时运行。比如常见的杀毒软件,在扫描木马的时候还能同时扫面插件、清理垃圾等等。如果一次只能做一件事情,那效率就低下许多了,但是多线程中可能存在安全隐患,应用的时候必须要注意。


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

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

JVM启动的时候会有一个进程java.exe。该进程中至少一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。

扩展:其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。


自定义一个线程有两种方式:

1. 定义一个类继承Thread,并复写其中的run()方法,并且new这个类后调用start方法启动。

2. 定义一个类实现Runnable接口,并复写其中的run()方法,再用new Thread(new 这个类).start方法启动。

下面用一个例子来说明这两种方法:

package test;

public class Test4 {
	//用两种方式启动两个线程,加上主线程,共3个线程,各自打印名字+i
	public static void main(String[] args) {
		new Demo1().start();
		new Thread(new Demo2()).start();
		for(int i=0;i<30;i++){
			System.out.println(Thread.currentThread().getName()+"_"+i);
		}
	}
}

//继承Thread类的实现方式
class Demo1 extends Thread{
	public void run(){
		for(int i=0;i<30;i++){
			System.out.println(Thread.currentThread().getName()+"_"+i);
		}
	}
}

//实现Runnable接口的实现方式
class Demo2 implements Runnable{
	public void run(){
		for(int i=0;i<30;i++){
			System.out.println(Thread.currentThread().getName()+"_"+i);
		}
	}
}
随便截取一小段打印结果:

Thread-0_6
Thread-0_7
main_0
Thread-0_8
main_1
Thread-1_0
Thread-0_9
Thread-1_1
main_2
Thread-1_2
Thread-0_10
Thread-1_3
main_3
Thread-1_4
可以看出3个线程是并发执行的,交替打印,并无先后顺序。这便是多线程的好处所在了,可以同时执行多个任务,对于程序的效率性大规模提高。


但是多线程在访问同一个变量的时候,会出现安全性的问题,因为程序执行时一个CUP只能同时执行一个线程,多个线程是随机交替执行,可能导致一个线程还没运行完,另一个线程操作了这个数据,可能出现错误。

导致安全问题的出现的原因:
1. 多个线程访问出现延迟。
2. 线程随机性。
注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

所以,为了解决这个问题,便出现了线程同步的操作。


线程的同步:

格式:synchronized(锁的对象) { 需要同步的代码;}

特点: 

同步的前提:

1. 同步需要两个或者两个以上的线程
2. 多个线程使用的是同一个锁
未满足这两个条件,不能称其为同步。
同步的弊端:
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

下面演示同步的安全问题:

package test;

public class Test5 {

	public static void main(String[] args) {
		Cus c = new Cus();
		Thread t1 = new Thread(c);   //创建2个客户存钱,检测程序的安全问题
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();	
	}

}

class Bank
{
	private int sum;
	public  void add(int n)
	{
//		synchronized(this)             //注释掉的部分是同步代码,打印出有同步代码,和无同步代码的结果
//		{
			sum = sum + n;
			try{Thread.sleep(10);}catch(Exception e){}  //为了看到现象,每次线程等待0.01秒
			System.out.println("银行总存款 sum="+sum);
//		}
	}
}

//这个一个客户类,每个客户每次存入银行100元,共存3次
class Cus implements Runnable
{
	private Bank b = new Bank();
	public void run()
	{		
		for(int x=0; x<3; x++)
		{
			b.add(100);    
		}
	}
}
不加同步代码,打印结果为:
银行总存款 sum=200
银行总存款 sum=200
银行总存款 sum=400
银行总存款 sum=400
银行总存款 sum=600
银行总存款 sum=600
可以看到,这里存在很严重的安全问题,可能因为多线程的随机执行问题,使得打印结果错误,即程序出现了安全问题,必须用同步解决。

去掉同步的注释,即加上同步代码,打印结果为:

银行总存款 sum=100
银行总存款 sum=200
银行总存款 sum=300
银行总存款 sum=400
银行总存款 sum=500
银行总存款 sum=600
这个是理想的结果一样,成功解决了安全问题。所以在使用多线程时必须注意安全性的问题,这个隐患发生的概率是很小,但是一旦发生,就是致命的问题。


同步中的synchronized可以直接加在方法上,当作修饰符用,这样就不能指定同步的锁了。那么此时用到的锁是哪个呢?在非静态的方法中,同步用到的锁就是对象本身this。而在静态方法中,同步用到的是该方法所在类的字节码文件对象,即 (类名.class) 文件。


线程间的通信:

多个线程间是计算机随机交易执行的,那么有没有方法使得它们按照一定顺序执行呢?如果有,那么各个线程之间必然有一个通信的方式,使得线程知道对方的状态,这便是线程间的通信了。

首先,我们需要知道线程到底存在哪几个状态:

可以看出线程有从创建到消亡一共有4个状态,多线程的通信便是通过使线程冻结,然后唤醒线程的方式来执行的。
wati():使得当前线程等待
notify():随机唤醒线程池里的一个线程
notifyAll():唤醒线程池里的所有线程
通过这三个函数,我们便能实现线程之间的交替执行。

在JDK1.5 中提供了多线程升级解决方案。将同步Synchronized替换成现实Lock操作。将Object中的wait,notify notifyAll,替换了Condition对象。该对象可以Lock锁 进行获取。
Lock:替代了Synchronized  
lock   
unlock  
newCondition()
Condition:替代了Object 
wait notify notifyAll  
await();  signal();  signalAll();

新的方法出现必然是有着其特有的优势,它能唤醒特定的线程,这样线程间的通信将更加方便。
下面用一个例子来说明:
package test;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test6 {
	public static void main(String[] args) throws InterruptedException {
		Resource res = new Resource();   //创建一个源类,生成、消费公用里面的数据
		
		Produce pro = new Produce(res);
		Consume con = new Consume(res);
		
		new Thread(pro).start();            //两个生产线程、两个消费线程
		new Thread(pro).start();
		new Thread(con).start();
		new Thread(con).start();
		
	}
}

//建立一个商品生产、消费的类,要求交替执行线程,生产一个商品,消费一个商品。
class Resource{
	private String name;   		//商品名字
	private int count = 1;		//商品编号
	private boolean flag = false;
	
	//创建用于同步线程的锁,和新特性Condition
	private Lock lock = new ReentrantLock();
	private Condition condition_pro = lock.newCondition();
	private Condition condition_con = lock.newCondition();
	
	//定义一个生产商品的方法
	public void produce(String name) throws InterruptedException{
		lock.lock();
		try {
			while (flag)     //标记为了按生产一个消费一个的顺序执行
				condition_pro.await();
			this.name = name + (count++);
			System.out.println(Thread.currentThread().getName() + "。。生产了。。"+ this.name);
			flag = true;
			condition_con.signal();
		}
		finally{
			lock.unlock();  //释放锁必须执行
		}
	}
	
	//定义一个消费商品的方法
	public void consume() 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 Produce implements Runnable{
	private Resource res;
	Produce(Resource res){
		this.res = res;
	}
	public void run(){
		while(true){
			try {
				res.produce("商品");
			} catch (Exception e) {
				System.out.println("线程出问题了");
			}
		}
	}
}

//定义消费商品的线程
class Consume implements Runnable{
	private Resource res;
	Consume(Resource res){
		this.res = res;
	}
	public void run(){
		while(true){
			try {
				res.consume();
			} catch (Exception e) {
				System.out.println("线程出问题了");
			}
		}
	}
}
这个程序运行结果截取一部分:
Thread-0。。生产了。。商品14639
Thread-2。。消费了。。。。商品14639
Thread-1。。生产了。。商品14640
Thread-3。。消费了。。。。商品14640
Thread-0。。生产了。。商品14641
Thread-2。。消费了。。。。商品14641
Thread-1。。生产了。。商品14642
Thread-3。。消费了。。。。商品14642
Thread-0。。生产了。。商品14643
Thread-2。。消费了。。。。商品14643
Thread-1。。生产了。。商品14644
Thread-3。。消费了。。。。商品14644
Thread-0。。生产了。。商品14645
Thread-2。。消费了。。。。商品14645
运行结果整齐的交替打印,并无安全问题发生,可见用新特性来实现同步有着效率更高的好处。







---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值