黑马程序员 多线程

来源:互联网 发布:num在c语言中什么意思 编辑:程序博客网 时间:2024/05/17 03:05
---------------------- 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个线程,各自打印名字+ipublic 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_6Thread-0_7main_0Thread-0_8main_1Thread-1_0Thread-0_9Thread-1_1main_2Thread-1_2Thread-0_10Thread-1_3main_3Thread-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;//创建用于同步线程的锁,和新特性Conditionprivate 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。。生产了。。商品14639Thread-2。。消费了。。。。商品14639Thread-1。。生产了。。商品14640Thread-3。。消费了。。。。商品14640Thread-0。。生产了。。商品14641Thread-2。。消费了。。。。商品14641Thread-1。。生产了。。商品14642Thread-3。。消费了。。。。商品14642Thread-0。。生产了。。商品14643Thread-2。。消费了。。。。商品14643Thread-1。。生产了。。商品14644Thread-3。。消费了。。。。商品14644Thread-0。。生产了。。商品14645Thread-2。。消费了。。。。商品14645
运行结果整齐的交替打印,并无安全问题发生,可见用新特性来实现同步有着效率更高的好处。







---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------
0 0
原创粉丝点击