Java基础-多线程

来源:互联网 发布:淘宝网休闲鞋女 编辑:程序博客网 时间:2024/06/05 00:34

进程:正在运行中的程序。

线程:就是进程中一个执行单元或执行情景或执行路径。负责进程中代码执行的控制单元。
多线程:一个进程中至少要有一个线程,当一个进程中有多个线程时,就是多线程。

多线程的好处:可以让多部分代码同时执行。

   什么是同时执行呢?         在单核时代其实是cpu在瞬间做着快速的切换完成的;      现在的多核时代,才算是真正的多线程。

其实java运行就是多线程的。 main函数+gc垃圾回收器

   在执行main函数中内容的同时,垃圾回收器也在回收堆内存的垃圾,所以执行main方法的线程,和执行垃圾回收器的线程同时在执行,这就是多线程。

创建多线程的目的:

   当有多部分代码需要同时执行时。而且每一个线程都有自己要执行的内容,这个内容称之为:线程任务。   简单说:启动多线程就是为了执行任务,当任务有多个,需要同时执行时,就需要多个线程。

线程的状态
这里写图片描述

1、被创建。2、运行:这种状态的线程,具备着cpu的执行资格,具备着执行资格。3、冻结:这种状态的线程,释放了cpu执行资格,并释放了cpu执行权。相当于睡着了,但还会醒来。    有两种方法实现:        a、sleep(time).唤醒sleep(time时间到)。        b、wait()睡着,唤醒notify()。   wait()方法也可以指定时间,如果不指定时间需要用notify()来唤                                       醒。(是Object中的方法。)4、临时阻塞状态:这种状态的线程,具备着cpu执行权,不具备执行资格。5、消亡:线程结束了。通过stop()方法来完成。

如何创建线程?

Java要调用底层才能完成进程的建立和线程的创建,所以java对外提供了描述线程的对象,方便程序员对线程的操作。创建线程的方式:    A:自定义一个类继承Thread类。    B:自定义一个类实现Runnable接口方式1:继承Thread类。    A:定义一个类继承Thread类。    B:子类要重写Thread类的run()方法。    C:让线程启动并执行。注意:启动线程并执行,是不能使用run()方法的。这个时候,必须使用另外的一个方法。     这个方法名是start()。***这个方法其实做了两件事情,第一,让线程启动。第二,自动调用run()方法。***

代码演示:
自定义类继承Thread类

/* * 为了看到每次确实在变化,我们要是能够知道线程对象的名字有多好呢? * 在Thread类中提供了一个方法: *      public final String getName():获取线程对象的名称。默认情况下,名字的组成 Thread-编号(编号从0开始) *      public final void setName(String name):设置线程名称。 */public class MyThread extends Thread {    @Override    public void run() {        for (int x = 0; x < 10; x++) {            System.out.println(getName()+"---hello" + x);        }    }}

线程测试类:

public class ThreadDemo {    public static void main(String[] args) {        MyThread my1 = new MyThread();        MyThread my2 = new MyThread();        my1.setName("用友");        my2.setName("金蝶");        // my.run();        // my.run();        // 同一个线程对象连续两次start,报错:IllegalThreadStateException        // 表示该线程的状态有问题。        // my.start();        // my.start();        my1.start();        my2.start();    }}

结果:
这里写图片描述
分析:
我们可以看到程序开启了两个线程,在输出结果的时候,线程1和线程2在交替着运行。因为他们都在等待着cpu的执行。并且每个线程执行的时间都是不确定,由cpu分配。

方式2:    A:创建一个类实现Runnable接口    B:重写run()方法    C:创建类的实例    D:把类的实现作为Thread的构造参数传递,创建Thread对象

自定义类实现Runnable接口
代码演示:

/* * public static Thread currentThread():返回当前正在执行的线程对象引用 */public class MyRunnable implements Runnable {    @Override    public void run() {        for (int x = 0; x < 100; x++) {            // getName()方法是Thread类的,而MyRunnable只实现了Runnable接口,本身没有getName(),所以不能使用。            System.out.println(Thread.currentThread().getName() + "---hello"                    + x);        }    }}

测试类:

public class MyRunnableDemo {    public static void main(String[] args) {        MyRunnable my = new MyRunnable();        // my.start();        // 实现了Runnable接口的类没有start()方法,而我们启动线程必须调用start()方法。        // 又由于,start()方法只有Thread类有。所以,我们就考虑这个把该类转换成Thread类。        Thread t1 = new Thread(my);        Thread t2 = new Thread(my);        t1.setName("乔峰");        t2.setName("慕容复");        t1.start();        t2.start();    }}

结果:
这里写图片描述
分析:
这里同样实现了多线程的需求。

那么问题来了:既然有了继承Thread类的方式,为什么还要有实现Runnable接口的方式呢?
A:避免的单继承的局限性
因为我们都知道,在Java的类中,只支持单继承,如果一个类继承了Thread类,就不能继承其他类了,这样会让自
义类的功能有很大的局限性。
B:实现接口的方式,只创建了一个资源对象,更好的实现了数据和操作的分离。
一般我们选择第二种方式。

**注意:**    1、多个线程的运行是不规律的。    2、必须得所有线程程序运行结束,进程才结束。    3、调用run方法和调用start方法的区别:          调用run方法,仅仅是一般对象调用对象中的方法,并没有开启线程。          调用start方法,开启一个线程,让这个线程去执行run方法中的内容。   

需求:通过四个窗口卖票,一共有100张票。用第二种创建线程方式实现。

卖票程序如果处理不当,会有同步问题。

/* * 目前这个代码是符合真实的卖票程序。 * 但是,有问题,居然出现了负数票的情况。 * 那么,产生的原因是什么呢? *      线程的随机性和延迟性,导致了线程访问共享数据出现了问题。 * 怎么解决呢? */public class TicketRunnable implements Runnable {    private int tickets = 100;    @Override    public void run() {        while (true) {            //t1,t2,t3,t4过来了            //tickets = 1            if (tickets > 0) {                //t1首先抢到了CPU的执行权,接着,进行了判断,发现是满足条件的,就进来了                //t2就抢到了,也进行了判断,发现还是满足,也就进来了                //t3抢到了,也进行了判断,发现还是满足,也就进来了                //t4抢到了,也进行了判断,发现还是满足,也就进来了                // public static void sleep(long millis)                try {                    //t1睡着了                    //t2睡着了                    //t3睡着了                    //t4睡着了                    Thread.sleep(100);                } catch (InterruptedException e) {                    e.printStackTrace();                }                //t1醒了 -- 窗口1正在出售第1张票 tickets=0                //t2醒了 -- 窗口2正在出售第0张票 tickets=-1                //t3醒了 -- 窗口3正在出售第-1张票 tickets=-2                //t4醒了 -- 窗口4正在出售第-2张票 tickets=-3                System.out.println(Thread.currentThread().getName() + "正在出售第"                        + (tickets--) + "张票");                //注意:如何卖出相同的票的呢?                //关键点:tickets--                //A:读取tickets的操作    100                //B:修改tickets的操作    99                //C:把最后的值赋值给tickets = 99            }        }    }}

Test类

public class RunnableTest {    public static void main(String[] args) {        TicketRunnable tr = new TicketRunnable();        Thread t1 = new Thread(tr);        Thread t2 = new Thread(tr);        Thread t3 = new Thread(tr);        Thread t4 = new Thread(tr);        t1.setName("窗口1");        t2.setName("窗口2");        t3.setName("窗口3");        t4.setName("窗口4");        t1.start();        t2.start();        t3.start();        t4.start();    }}

那么,产生的原因是什么呢?
线程的随机性和延迟性,导致了线程访问共享数据出现了问题。
怎么解决呢?
在多线程程序中,一般来说,不会是所有的代码都有问题,所以,我们只需要找到那些可能出问题的代码,
我把可能出问题的代码, 跟包起来,看做是一个整体,只有这个整体完毕,别人才能继续访问。
为了安全,把这个整体包起来的代码加个锁。给锁起来。

怎么找?(多线程出问题的判断条件)
A:看有没有共享数据
B:看对共享数据的操作是不是多条语句
C:看是不是在多线程程序中

找到后,就把同时满足这三个条件的代码给锁起来。

怎么锁?
java提供了一种锁机制方式:
synchronized(锁对象)
{
需要被锁的代码;
}
锁对象:怎么做呢?反正不知道,所以,我们就用Object类的实例。

注意:多个线程必须使用同一把锁。

举例:
排队上厕所。

厕所      --      资源多个人,就可以看成是多个线程。他们发现厕所哪里的灯是绿色,张三去了,李四去了,王五去了...由于张三最近,张三抢到了,就进去了,灯就变成红色了。张三...张三出来,灯比绿色,但是,张三拉肚子,所以,他一回身又进去了。    ...        ...

代码演示:

public class TicketRunnable implements Runnable {    private int tickets = 100;    private Object obj = new Object();    @Override    public void run() {        while (true) {            //tickets=1            //t1,t2,t3,t4都来了            //假设t1抢到,看到是开的状态            synchronized (obj) {                //锁对象的状态:开,关                //t1进来了,给外界了一个关的状态。                if (tickets > 0) {                    try {                        //t1睡了                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println(Thread.currentThread().getName() + "正在出售第"                            + (tickets--) + "张票");                }            } //t1把状态修改为开        }    }}

Test类

public class RunnableTest {    public static void main(String[] args) {        TicketRunnable tr = new TicketRunnable();        Thread t1 = new Thread(tr);        Thread t2 = new Thread(tr);        Thread t3 = new Thread(tr);        Thread t4 = new Thread(tr);        t1.setName("窗口1");        t2.setName("窗口2");        t3.setName("窗口3");        t4.setName("窗口4");        t2.start();        t3.start();        t4.start();        t1.start();    }}

结果:
这里写图片描述
分析:
由于加上了锁,所以共享的数据在同一时间内,只被一个线程所操作,直到该线程不再持有这个锁,其他线程才有机会得到这个锁,只有得到这个锁的线程,才有访问共享数据的资格。

同步:

*同步原理*:其实就是将需要同步的代码进行封装,并在该代码上加一个锁。*同步好处*:解决了多线程安全问题。*同步弊端*:降低性能。(注意):加了同步,安全问题还在,如何解决?利用同步的两个前提来解决。*同步的前提:*    必须要保证在同步中有多个线程,因为同步中只有一个线程,该同步是没有意义的。    必须要保证多个线程在同步中使用的是同一个锁。**注意:当锁定义在局部中时,相当于每个线程都具备一个锁。这时就不是同一个锁了。**

多线程安全问题:

多线程安全的原因:1、多个线程在操作共享数据。2、操作共享数据的代码有多条。    一个线程在执行多条操作共享数据的过程中,其他线程参与了运算,这时就会发生安全问题。

分析多线程是否安全的依据:

线程中任务中有没有共享数据,该数据是否被多条语句操作。

安全问题解决方案:

只要保证一个线程在执行多条操作共享数据的语句时,其他线程不能参与运算即可。当该线程都执行完后,其他线程才可以执行这些语句。在多线程操作的代码上加上同步(synchronzied)。

Synchornzied同步:

同步的原理:其实就是将需要同步的代码进行封装,并在该代码上加上一个锁。同步的好处:解决多线程安全问题。同步弊端:降低程序的运行效率,同时有可能会出现死锁情况。一种现象:出现了多线程安全问题,为了解决,加上同步,发现问题依旧,怎么办?

同步的前提:

   必须要保证在同步中有多个线程,以为同步中只有一个线程的同步是没有意义的。   必须要保证多个线程同步中使用的是同一锁。(将锁对象定义在成员位置)

多线程的程序,如何加同步?

1、  从线程任务代码中分析,也就是run方法中的代码中分析。2、  分析啥呢?是否有共享数据,有没有多条语句在操作共享数据?

同步函数:将同步加在函数上。

如何将同步代码块以同步函数的形式表现?

如果run方法中的所有数据都要被同步,那么直接在run方法中加同步,如果只有部分代码需要同步,那么将需要被同步的代码用一个函数封装,然后在run方法中调用这个同步函数就行了。

同步函数和同步代码块有什么区别?

1、 同步函数使用的锁是this,同步代码块使用的锁是任意指定的对象。

建议开始时,使用同步代码块,因为锁可以是任意的。尤其是需要用到多个不同锁时。

同步函数使用的锁是什么?

   同步函数使用的锁是this。

静态同步函数使用的锁是什么?

   静态随着类的加载而加载,这时内存中只存储的对象至少一个,就是该类字节码文件对象(类名.class)。所以静态同步函数使用的锁是字节码对象。

关于单例模式中懒汉式的知识点补充:
我们都知道,单例模式中的懒汉式中,有线程同步安全问题。也就是说如果不添加同步锁的情况下,可能保证不了在内存中只有一个对象的特点。。
改进代码:

package cn.itcast.single;//懒汉式的多线程处理:双重判断。public class Single {    //创建私有化并且静态修饰的对象引用    private static Single s = null;    //私有化无参构造方法,目的是不让外部创建实例化对象。    private Single() {    }    //在本类中提供一个公共方法,返回本类的一个对象。    public static Single getInstanse() {        if (s == null) {            synchronized (Single.class) {                if (s == null)                    s = new Single();            }        }        return s;    }}

死锁:

最常见的死锁情况:同步嵌套。同步中还有同步,两个同步用的不是同一个锁。死锁代码演示:同步嵌套。

死锁问题:
5个哲学家的故事。
5个哲学家去吃饭,菜饭都上齐了,筷子也上了,但是,一人只有一只筷子,每个人,先思考一会,把筷子借给别人,
然后,别人吃完了,自己在吃。假如这5个人都饿了,他们就会拿起自己的筷子,但是只有一只,都等待这个别人
放下那一只筷子,然后好拿过来吃饭,而没有任何一个人愿意先放下筷子,所以,就出现了死锁。

代码演示:
DieLock类:

public class DieLock extends Thread {    private boolean flag;    public DieLock(boolean flag) {        this.flag = flag;    }    @Override    public void run() {        if (flag) {            synchronized (MyLock.objA) {                 System.out.println("true -- objA");                synchronized (MyLock.objB) {                System.out.println("true -- objB");                }            }        } else {            synchronized (MyLock.objB) {                System.out.println("false -- objB");                synchronized (MyLock.objA) {                    System.out.println("false -- objA");                }            }        }    }}
锁类:
public class MyLock {    public static final Object objA = new Object();    public static final Object objB = new Object();}
测试类:
public class DieLockDemo {    public static void main(String[] args) {        DieLock d1 = new DieLock(true);        DieLock d2 = new DieLock(false);        d1.start();        d2.start();    }}

结果:
这里写图片描述

0 0
原创粉丝点击