多线程技术


一:多线程的一些概念!

一般来说,当运行一个应用程序的时候,就启动了一个进程,当然有些会启动多个进程。启动进程的时候,操作系统会为进程分配资源,其中最主要的资源是内存空间,因为程序是在内存中运行的。在进程中,有些程序流程块是可以乱序执行的,并且这个代码块可以同时被多次执行。实际上,这样的代码块就是线程体。线程是进程中乱序执行的代码流程。当多个线程同时运行的时候,这样的执行模式成为并发执行。多线程的目的是为了最大限度的利用CPU资源。

二:Java中实现多线程程序的两种方式及其区别!
方法一:继承Thread类
class hello extends Thread {
      public hello() {
 
    }
    public hello(String name) {
        this.name = name;
    }
 
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + "运行     " + i);
        }
    }
    public static void main(String[] args) {
        hello h1=new hello("A");
        hello h2=new hello("B");
        h1.start();//这里千万不要直接调用run()方法,否则就是一个普通的run方法了
        h2.start();
    } 
    private String name;

为何不能直接调用run()方法?
我的理解是:多线程的运行需要“本地操作系统”的支持:
查看线程的start方法的源码可以发现:
public synchronized void start() {
        /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added 
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0 || this != me)
            throw new IllegalThreadStateException();
        group.add(this);
        start0();
        if (stopBeforeStart) {
        stop0(throwableFromStop);
    }
}
private native void start0();
说明此处调用的是start0()。并且这个这个方法用了native关键字,次关键字表示调用本地操作系统的函数。因为多线程的实现需要本地操作系统的支持。

方法二:实现Runnable接口
/**
 * @author chen 实现Runnable接口
 * */
class hello implements Runnable {
 
    public hello() {
 
    }
 
    public hello(String name) {
        this.name = name;
    }
 
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + "运行     " + i);
        }
    }
 
    public static void main(String[] args) {
        hello h1=new hello("线程A");
        Thread demo= new Thread(h1);
        hello h2=new hello("线程B");
        Thread demo1=new Thread(h2);
        demo.start();
        demo1.start();
    }
 
    private String name;
}

这两种方式的区别,我们到底该用那种方式去玩“多线程”?
Thread和Runnable的区别:
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
比如:
class hello extends Thread {
    public void run() {
        for (int i = 0; i < 7; i++) {
            if (count > 0) {
                System.out.println("count= " + count--);
            }
        }
    }
 
    public static void main(String[] args) {
        hello h1 = new hello();
        hello h2 = new hello();
        hello h3 = new hello();
        h1.start();
        h2.start();
        h3.start();
    }
 
    private int count = 5;
}
打印结果:

count= 5

count= 4

count= 3

count= 2

count= 1

count= 5

count= 4

count= 3

count= 2

count= 1

count= 5

count= 4

count= 3

count= 2

count= 1

每个线程的资源“count”,都是独立的。

但是如果使用“实现Runnable接口”的方式就很容易实现资源的共享:
class MyThread implements Runnable{
 
    private int ticket = 5;  //5张票
 
    public void run() {
        for (int i=0; i<=20; i++) {
            if (this.ticket > 0) {
                System.out.println(Thread.currentThread().getName()+ "正在卖票"+this.ticket--);
            }
        }
    }
}
public class lzwCode {
     
    public static void main(String [] args) {
        MyThread my = new MyThread();
        new Thread(my, "1号窗口").start();
        new Thread(my, "2号窗口").start();
        new Thread(my, "3号窗口").start();
    }
}
打印结果:

count= 5

count= 4

count= 3

count= 2

count= 1

总结一下:

实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。


三:“线程安全”问题!

例子:一个用于演示线程安全的例子!

package com.ThreadDemo.bean;

/**
 * 用于演示线程安全的例子
 * <p/>
 * <p/>
 * 以下的代码,在多线程的情况下,是可能出现线程安全的情况的。
 * <p/>
 * 现象:order可能会打印出“负数”的情况
 * <p/>
 * 我们已经在程序中加了条件if(order > 0),为何还是会出现负数的情况呢?
 * 原因:由于我们在“测试代码”中,我们写成“Quan quan = new Quan”,然后新建多个Thread,传入这个对象,然后开启多线程程序的方式!
 * 由于quan这个对象是同一个对象,所以下面的多个线程共享的是同一份order变量,
 * <p/>
 * 当多条语句在操作同一个线程共享数据的时候,一个线程对多条语句只执行了一部分,还没结束的时候,另一个线程进入了此代码区域,进行下一次执行,导致共享数据发生错误!
 * <p/>
 * <p/>
 * 解释:比如这个例子中,为何会出现负数?
 * 比如Thread0,在执行run方法中的语句的时候,当此时order为1了,执行到Thread.sleep()方法的时候,这个线程的“执行权”,被Thread1抢过去了,Thread1执行run方法中的代码的时候,因为这个时候Thread0
 * 还没有执行代码order--,所有Thread1在执行run方法中的代码的时候if(order > 0)是正确的,顺利进入了代码块!并且顺利执行了order--,那么此时order变成了0.
 * 过了一会儿,Thread0线程被唤醒了,恢复了执行权,执行了刚刚还没结束的代码,order--,那么此时order就变成了-1,那么就出现了所谓的“线程安全”的问题。
 * <p/>
 * <p/>
 * 什么情况下会出现线程安全的问题?
 * 1 多线程的情况下
 * 2 多线程共享同一份数据的时候
 * 3 多线程执行的run方法中的代码有“多条语句”,可能出现某个线程没有完全执行完所有语句的情况下,被另外的线程抢走执行权的情况!
 * <p/>
 * <p/>
 * 解决“线程安全”隐患的方法!
 * 对多条语句执行共享数据的代码,在某一个时间段,执行某一个线程执行,这多条语句,并且只有都执行完了这些语句,下一个线程才被允许进行这多条语句的执行。在前一个线程
 * 执行这多条语句的过程中,另一个线程即使获得了“执行权”,也只能暂时等待在哪里,当前面的线程全部执行完“多条语句”后,才能执行!也就是给这“多条语句”加上synchronized关键字!
 *
 * 使用synchronized关键字,同步时的条件:
 *  1必须是多线程,也就是两个或者两个以上的线程去执行sybchronized关键字中的代码
 *  2这多个线程必须使用的是同一把“锁”,如果是不同的锁,它们之间是不会达到“互斥”的效果的
 *
 *  使用synchronized的不足之处:多线程,在每次执行synchronized中的代码的时候,每次都需要判断参数中的“锁”对象,所以一定程度上会消耗一定的系统资源!
 * <p/>
 * <p/>
 * 注意点:下面的多线程的程序使用的实现Runnable的方式实现多线程的,如果我们直接继承Thread,然后开启多线程的话,
 * 比如Quan extend Thread,Quan quan = new Quan(),Quan quan1 = new Quan(),Quan quan2 = new Quan()
 * 然后开启线程的话,由于这里我们new了多个Quan对象,每个Quan对象都拥有各自的独立的一份“order”变量,那么就不会出现上述的“线程安全”的问题!
 * <p/>
 * <p/>
 * User: OF895
 * Date: 14-11-12
 * Time: 下午11:52
 */
public class Quan implements Runnable {

    private int order = 100;

    @Override
    public void run() {
        while (true) {
            //这里的quan.class可以看做一把“锁”,持有该“锁”的线程,可以进行代码块中进行执行,当前不持有该“锁”的线程
            //即使获得了“执行权”,也无法进入“同步代码块”中
            synchronized (Quan.class) {
                if (order > 0) {
                    try {
                        //让当前线程睡10ms
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "........." + order--);
                }
            }

        }

    }

    public static void main(String[] args) {
        Quan quan = new Quan();

        Thread t1 = new Thread(quan);
        Thread t2 = new Thread(quan);
        Thread t3 = new Thread(quan);
        Thread t4 = new Thread(quan);

        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
}

打印结果:

Thread-0.........100
Thread-0.........99
Thread-2.........98
Thread-1.........97
Thread-1.........96
Thread-1.........95
Thread-3.........94
Thread-3.........93
Thread-3.........92
Thread-1.........91
Thread-2.........90
Thread-0.........89
Thread-2.........88
Thread-2.........87
Thread-2.........86
Thread-1.........85
Thread-1.........84
Thread-3.........83
Thread-1.........82
Thread-1.........81
Thread-2.........80
Thread-0.........79
Thread-2.........78
Thread-1.........77
Thread-3.........76
Thread-3.........75
Thread-3.........74
Thread-1.........73
Thread-1.........72
Thread-2.........71
Thread-0.........70
Thread-0.........69
Thread-0.........68
Thread-2.........67
Thread-1.........66
Thread-1.........65
Thread-3.........64
Thread-1.........63
Thread-2.........62
Thread-0.........61
Thread-2.........60
Thread-1.........59
Thread-3.........58
Thread-1.........57
Thread-2.........56
Thread-0.........55
Thread-2.........54
Thread-1.........53
Thread-3.........52
Thread-1.........51
Thread-1.........50
Thread-2.........49
Thread-2.........48
Thread-0.........47
Thread-0.........46
Thread-2.........45
Thread-2.........44
Thread-2.........43
Thread-1.........42
Thread-3.........41
Thread-1.........40
Thread-1.........39
Thread-2.........38
Thread-0.........37
Thread-0.........36
Thread-0.........35
Thread-0.........34
Thread-2.........33
Thread-2.........32
Thread-2.........31
Thread-1.........30
Thread-3.........29
Thread-1.........28
Thread-2.........27
Thread-0.........26
Thread-0.........25
Thread-2.........24
Thread-1.........23
Thread-3.........22
Thread-3.........21
Thread-1.........20
Thread-2.........19
Thread-2.........18
Thread-0.........17
Thread-2.........16
Thread-1.........15
Thread-3.........14
Thread-1.........13
Thread-2.........12
Thread-0.........11
Thread-2.........10
Thread-1.........9
Thread-3.........8
Thread-1.........7
Thread-2.........6
Thread-0.........5
Thread-2.........4
Thread-1.........3
Thread-3.........2
Thread-1.........1

这样就实现了多个线程共享这100张券的情况,而不会出现小quan的数据小于0的情况!


四:我们如何去判断多线程的程序是否有线程安全的问题呢?

package com.ThreadDemo;

/**
 * 目的:该程序是否有安全问题,如果有,如何解决?
 * <p/>
 * <p/>
 * 如何判断一个程序是否有“线程安全”的问题?
 * <p/>
 * 1明确哪些代码是多线程运行的代码
 * 2明确哪些是多线程的“共享数据”
 * 3明确多线程运行代码中,哪些是“操作”共享数据的。
 *
 *
 *
 * 同步有两种表现形式:
 * 1同步代码块
 * 2同步函数,同步函数使用的”锁“是this
 * <p/>
 * <p/>
 * User: OF895
 * Date: 14-11-14
 * Time: 下午11:53
 */
public class BankDemo {
    public static void main(String[] args) {
        Cus c = new Cus();
        Thread t1 = new Thread(c);
        Thread t2 = new Thread(c);
        t1.start();
        t2.start();
    }
}


class Cus implements Runnable {
    private Bank b = new Bank();

    @Override
    public void run() {
        for (int x = 0; x < 3; x++) {   //这里的“x”,就不是“共享变量”了,因为每个线程都会有独自的”一份X变量“,这个是”局部变量“
            b.add(100); //这里是线程执行的地方,但是真正的“多线程”执行的代码在Bank类的add方法中。
        }
    }
}

class Bank {
    private int sum;   //这个“成员变量”,那么肯定是共享数据
    Object obj = new Object();

    public void add(int n) {
        synchronized (obj) {
            sum = sum + n; //这个是“多线程代码”中,明确操作“共享数据的” ,这里有多条语句操作共享数据,那么就可能发生“线程安全”的问题
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("sum = " + sum);//这个是“多线程代码”中,明确操作“共享数据的” ,这里有多条语句操作共享数据,那么就可能发生“线程安全”的问题
        }

    }
}

//class Bank {
//    private int sum;   //这个“成员变量”,那么肯定是共享数据
//    Object obj = new Object();
//
//    public synchronized void add(int n) {    //这里的写法是”同步函数“的写法,和上面的”同步代码块“给区别开来,同步函数使用的锁是”this“
//            sum = sum + n; //这个是“多线程代码”中,明确操作“共享数据的” ,这里有多条语句操作共享数据,那么就可能发生“线程安全”的问题
//            try {
//                Thread.sleep(10);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println("sum = " + sum);//这个是“多线程代码”中,明确操作“共享数据的” ,这里有多条语句操作共享数据,那么就可能发生“线程安全”的问题
//    }
//}

五:拓展知识,单例模式有两种,饱汉式和懒汉式(这个在多线程的程序下可能会存在“线程安全的”问题)

饱汉式(这中没有线程安全的问题):

package com.ThreadDemo;

/**
 * 饱汉式单列模式
 * <p/>
 * User: OF895
 * Date: 14-11-16
 * Time: 下午11:23
 */
public class SingleDemo1 {
    public static void main(String[] args) {

        Single s = Single.getInstance();

    }
}

class Single {
    //构造方法设置为private的,这样在类外就无法通过new创建类的对象了
    private Single() {

    }
    private static final Single s = new Single();

    //提供一个方法供外部调用,获得该类的唯一的对象
    public static Single getInstance() {
        return s;
    }
}

懒汉式(这种在多线程的情况下有线程安全的问题):

package com.ThreadDemo;

/**
 * "懒汉式"单列模式
 * User: OF895
 * Date: 14-11-16
 * Time: 下午11:28
 */
public class SingleDemo2 {
}

class Singleton {
    private Singleton() {

    }
    //这里的Singleton s是一个“共享数据”
    private static Singleton s = null;

    public static Singleton getInstance() {
        //只有s==null的情况下,才返回s,这就是所谓的“懒汉式”,但是这个在多线程的情况下,可能就不能保证是得到的singleton对象是“同一个对象”了

        //这里有多条语句在操作这个“共享数据s”,一条语句是判断s是否为null,第二条语句生成一个Singleton对象赋值给s,那么在多线程的情况下就会有线程安全的问题,需要加synchronized代码块。
        synchronized (Singleton.class){ //因为这个方法是个静态方法,所以我们的锁不能用this,需要使用Singleton.class
            if (s == null) {
                s = new Singleton();
            }
        }
        return s;
    }
}

六:“同步函数”的例子:

package com.ThreadDemo;

/**
 * 演示一下“同步函数”的例子
 * <p/>
 * 同步函数用的是哪一个“锁”呢?
 * 函数需要被对象调用,那么函数都有一个所属对象引用,就是this
 * <p/>
 * 静态同步函数使用的锁是该方法所在的类的“class”,字节码文件,因为这个每个类只有一份,所以在静态方法上面加上这个synchronized关键字,那么也是线程不安全的
 * 因为多个线程之间,他们使用的是同一个“锁”对象,那么就“锁”不了了,达不到安全的目的了!
 * User: OF895
 * Date: 14-11-16
 * Time: 下午10:06
 */
public class SynchronizedMethodDemo {


    public static void main(String[] args) {
        Thread t1 = new Thread(new Book());
        Thread t2 = new Thread(new Book());
        Thread t3 = new Thread(new Book());
        t1.start();
        t2.start();
        t3.start();
    }

}

class Book implements Runnable {
    private int num = 1000;

    @Override
    //千万不要把“synchronized”关键字加到这个run方法上,否则的话,会导致“只有一个”线程在启动执行代码,下一个线程无法进行到run方法中,也就无法执行run方法中的代码了!
    public void run() {
        show();
    }


    //同步函数,等同于上面的例子中的“同步代码块”的使用,也是为了解决“线程安全”的问题的,锁是“调用的对象”,也即是this
    public synchronized void show() {
        if (num > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }

            System.out.println(Thread.currentThread().getName() + "...........sale: " + num--);
        }
    }
}


七:如何避免“线程间通信时候”可能造成的线程通信的问题。

package com.ThreadDemo;

/**
 * 如何避免“线程间通信”时,可能造成的“线程安全”问题
 * <p/>
 * <p/>
 * 解释:这是一个“线程间”通信的小例子
 * Input线程如果以奇数和偶数为标识,不停地向“Resource”中的name和sex“赋不同的值”,而Output不停的从中“取值”
 * 这里有两个地方需要注意:
 * <p/>
 * 1 在Output类中的run方法中,虽然只有“一句”打印的语句,但是它是一个独立的线程,如果不添加synchronized关键字的话,如果Input线程set好了name之后,暂时休眠了,这时候Output线程获得了“执行权”,那么就会打印出 “mike 女”或者“丽丽 man”这样的“线程不安全”(数据被混淆了)的数据
 * <p/>
 * 2 因为我们分析了上面出现线程不安全的原因,就是因为两个线程Input和Output他们虽然是两个线程,但是他们对同一个共享数据Resource r是有不同的操作动作的,所以我们需要
 * 都给他们加上“同步”,并且他们的“锁”对象必须是“一样的”(同一把锁),这样我们才能够保证打印出的永远是“mike man”,"丽丽 女",这样的“安全”的数据。
 * <p/>
 * <p/>
 * <p/>
 * User: OF895
 * Date: 14-11-17
 * Time: 下午10:21
 */


public class NotifyThread {
    public static void main(String[] args) {
        Resource r = new Resource();
        Thread t1 = new Thread(new Input(r));
        Thread t2 = new Thread(new Output(r));

        t1.start();
        t2.start();
    }

}

class Resource {
    String name;
    String sex;
}

class Input implements Runnable {
    private Resource r;

    Input(Resource r) {
        this.r = r;
    }

    @Override
    public void run() {
        int x = 0;
        while (true) {
            synchronized (r) {
                if (x == 0) {
                    r.name = "mike";
                    r.sex = "man";
                } else {
                    r.name = "丽丽";
                    r.sex = "女";
                }
                x = (x + 1) % 2;
            }
        }

    }
}

class Output implements Runnable {
    private Resource r;

    Output(Resource r) {
        this.r = r;
    }

    @Override
    public void run() {
        while (true) {
            //这里因为和上面的线程操作的是同一个“共享数据”,所以我们也需要加上synchronized关键字
            //并且使用的“锁”对象,必须和Input中的“锁”对象保持一致
            synchronized (r) {
                System.out.println(r.name + ".........." + r.sex);
            }
        }

    }
}











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值