JAVA线程(简单)

多线程:就是应用程序有多条执行路径
    线程:是进程的执行单元,执行路径。
        如果一个应用程序只有一条只有执行路径,那么,该程序就是单线程程序
        如果一个应用程序有多条执行路径,那么,该程序就是多线程
    进程:正在运行的应用程序,
        每个进程具有独立的空间。
        我们目前的操作系统都是支持多进程的。
    举例:360,迅雷
        一个人吃一桌饭:单进程单线程
        多个人吃一桌饭:单进程多线程
    多线程的好处:效率高一点
    多线程的问题:
        多个线程针对同一个资源,导致数据会有安全问题,资源冲突
我们怎么实现多线程程序?
    假设JAVA没有提供,怎么办?
    我们要想实现多线程,首先要去创建一个进程,然后将进程拆分成多个线程
    问题来了:进程是由操作系统根据应用程序的运行产生的,也就是,我想要创建进程,我们得去操作操作系统的资源,但是,java程序是不能直接操作操作系统的资源的,那么,我们直接通过java语言做这件事情是实现不了的。
    所以,java就提供了线程的类供我们使用,
    但是,底层肯定是C或者C++去完成这件事情,这个操作被包装了,我们看不多,把CC++的程序按照java的模式去编写,然后用java去调用了CC++的方法。
    接下来,我们只能依赖Thread实现。

方法一:通过继承Thread类

通过查看API,可以知道,多线程程序的模拟有两种方法
    A:继承Thread类
    B:实现Runnable接口 

继承Thread类的步骤:
    A:自定义类实现Thread类
    B:在自定义类中重写run()方法
        为什么要重写run()方法呢?
        因为run()方法中的封装的代码才是可以被线程执行的。
    C:创建自定义类的对象
    D:启动线程并使用
        如何启动?
        线程对象创建后,将来调用的肯定是run方法里面的代码
        但是,必须通过start()方法启动 

    run()和start()的区别
    run()封装了被线程执行的代码
    start():让线程启动,并由jvm调用run()方法。   

如何获取到运行时的线程的名字?
    getName()//返回线程名称,Thread类中的方法

设置线程的名字:
一:自定义名字:
    setName(String name)//更改线程名称
二:Thread(String name):父类的带参构造。
    自定义类调用上面的方法:
    public myThread(String name){
        super(name);
    }

方法二:通过实现Runnable接口

实现Runnable接口的方式:
    A:自定义类MyRunnable实现Runnable接口
    B:重写run()方法
    C:创建自定义类的对象
    D:创建Thread类的对象,将MyRunnable类的对象作为构造参数传递
    E:??调用Thread的对象调用start方法。

针对实现接口的这种方法,没有办法获取名称,所以java就提供了一个静态方法。获取当前正在执行的线程对象
public static Thread currentThread();
然后通过线程对象去获取对象的名称。

设置线程名字:
A:setName()
B:Thread(Runnable target, String name)通过构造函数参数直接设置名字。

问题1:继承Thread类和实现Runnable有什么区别?

因为类只能单继承!!!!!!!!!!!!!

比如:
我有一个fu类,一个Zi类,Zi类已经继承了父类,要想实现多继承,怎么办?

     实现Runnable接口。

问题2:继承Thread类的方式,子类本身就是一个线程类,实现Runnable接口的方式,具体类本身是不是一个线程实例呢?

不是!因为如果是的话,就可以直接调用setName方法去设置线程的名字了。

应用程序的执行:

CPU在多个应用程序之间进行着高效的切换运行,这个切换的时间很短,短到你感觉不到,所以,我们觉得我们的程序是在同时并发的执行, 但是准确的说:CPU在某个时间点上只能有一个程序在执行

多线程的好处

开多线程的目的是提高CPU的使用率,进而来提高程序的效率。

面试题:线程的生命周期

新建:创建线程对象
就绪:准备随时执行,具备执行资格,没有执行权
运行:执行Run中代码
死亡:run结束
阻塞:在运行过程中出现意外情况,,当情况呗解决后,就回到就绪状态。

代码一:Thread

package com.thread.ticket.thread;

/**
 * 我有一百张票,放在4个窗口卖
 * 
 * 线程是共享进程的资源
 * 一个JAVA程序是 一个进程
 *  栈:每个线程都有自己的一份
 *  堆:共享的
 *  方法区:共享的
 *  
 * 问题:
 *  A:把int ticket = 100 ;放到run()方法中,每次调用run()都会重新定义100张票。
 *  每个线程都有自己的栈空间,那么,每个线程对象都会拥有自己的100张票
 *  B:把private int tickets =100 ;定义到了成员变量的位置,还是会有问题
 *    按照正常的思绪,应该是没有问题了的,但是还是出现了问题。
 *    原因很简单,因为我们创建了四个对象,而普通的成员变量是每个对象所特有的。
 *    也就是说,每个线程对象还是拥有自己的100张票。
 *  我们的目的是:这四个线程对象共同拥有100张票,请问怎么能够实现?
 *      加static
 */
public class TicketDemo {
    public static void main(String[] args) {
        Ticket t = new Ticket("窗口1");
        Ticket t1 = new Ticket("窗口2");
        Ticket t2 = new Ticket("窗口3");
        Ticket t3 = new Ticket("窗口4");
        t.start();
        t1.start();
        t2.start();
        t3.start();

    }
}



package com.thread.ticket.thread;

public class Ticket extends Thread {
    private static int tickets = 100;

    public Ticket() {
    }

    public Ticket(String name) {
        super(name);
    }

    @Override
    public void run() {

        while (true) {
            if (tickets > 0) {
                System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
            }
        }
    }
}


代码二:Runnable优化

package com.thread.ticket.runnable;

/**
 * 我有一百张票,放在4个窗口卖
 * 通过Runnable改进卖票程序
 */
public class TicketDemo {
    public static void main(String[] args) {
        Ticket t1 = new Ticket();
        Thread t = new Thread(t1, "窗口1");
        Thread t4 = new Thread(t1, "窗口2");
        Thread t2 = new Thread(t1, "窗口3");
        Thread t3 = new Thread(t1, "窗口4");
        t.start();
        t4.start();
        t2.start();
        t3.start();

    }
}


package com.thread.ticket.runnable;

public class Ticket implements Runnable {
    private static int tickets = 100;

    @Override
    public void run() {

        while (true) {
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
            }
        }
    }
}

代码三:线程安全问题分析与讨论

package com.thread.ticket1;

/**
 * 我有一百张票,放在4个窗口卖
 * 通过Runnable改进卖票程序
 * //模拟正常的现象,我让线程稍微等一下。
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 *问题:出现相同的票卖了两次,以及出现负数票。
 *A:相同票卖了两次:
 *  CPU的每一次执行,必须是一个原子性的操作,这个操作是不能再分隔的,
 *      int i= 10;  其实是两个原子性动作。
 *      这样的话
 *      tickets-- 也不是一个原子性动作,可能在操作的中间部分,被其他的线程给执行了,
 *      这样就会有相同的票执行了多次
 *  面试题:
 *      if(true) 
 *          int a=10;
 *      //if语句没有写大括号,默认情况下,只负责处理跟在其后面的一条语句,
 *      而:int a = 10;  其实不是一个语句;
 *      等价:
 *          int a;
 *          a=10; 
 *B:负票:
 *  因为线程的随机性,
 */
public class TicketDemo {
    public static void main(String[] args) {
        Ticket t1 = new Ticket();
        Thread t = new Thread(t1, "窗口1");
        Thread t4 = new Thread(t1, "窗口2");
        Thread t2 = new Thread(t1, "窗口3");
        Thread t3 = new Thread(t1, "窗口4");
        t.start();
        t4.start();
        t2.start();
        t3.start();

    }
}



package com.thread.ticket1;

public class Ticket implements Runnable {
    private static int tickets = 100;

    /*    @Override
        public void run() {
            while (true) {
                //t1,t2,t3,t4
                //tickets = 100;
                //假设t1抢到了CPU的执行权
                if (tickets > 0) {
                    //模拟正常的现象,我让线程稍微等一下。
                    try {
                        Thread.sleep(100);//t1睡眠了。t2抢到了CPU的执行权,t2进来后,也睡眠了
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //t1醒过来,继续执行
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
                    //窗口1正在出售100张票
                    //tickets = 99 
                    //这仅仅是我们自己的理想状况,但是实际情况并不是这个样子的。
                    //实际情况是:每一次线程执行的程序应该是一个原子性的操作。也就是这个操作是不能再分割的。
                    //tickets--这个动作有tickets-1=99和tickets=99两个动作
                    //这里的话,至少是两个动作,那么在-1的动作执行时候,并没有给tickets重新赋值。
                    //t2醒过来了,这个时候tickets还是100,所以:这个时候,窗口二正在执行100张票。
                }
            }
        }*/
    @Override
    public void run() {
        while (true) {
            //t1,t2,t3,t4
            //tickets = 100;
            //假设t1抢到了CPU的执行权
            //t2抢到了
            //t3抢到了
            //t4抢到了
            if (tickets > 0) {
                //t1睡了
                //t2睡了
                //t3睡了
                //t4睡了
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
                /**
                 * t1醒过来了
                 * 窗口1正在出售第1张票 tickets = 0 
                 * t2醒过来了
                 * 窗口2正在出售第0张票 tickets = -1
                 * t3醒过来了
                 * 窗口3正在出售第-1张票 tickets = -2
                 * t4醒过来了
                 * 窗口4正在出售第-2张票 tickets = -3
                 */

            }
        }
    }
}

代码4:线程安全的问题解决

package com.thread.ticket2;

/**
 * 这里解决上面代码出现负票的方法:
 * 
 * 首先:多线程出现安全问题的原因:
 *  A:是多线程程序;
 *  B:有共享数据
 *  C:针对共享数据有多条语句操作
 *  
 * 解决:
 *  只需要把多线程环境中,操作共享数据的操作给变成单线程的就没有问题了。
 *  java针对这种情况,就提供了同步技术,,同步代码块
 *  格式:
 *      synchronized(对象){
 *          需要被同步的代码;
 *      }
 *    A:对象?
 *      如果不知道用哪个对象,就使用Object,
 *    B:需要被同步的代码块?
 *      哪些代码导致出现了问题,就把哪些代码同步起来。
 *      
 *    哪些代码会出现问题呢?
 *      有共享数据
 *      针对共享数据有多条语句操作
 *    加入同步后发现还有问题,为什么??
 *       while (true) {
            synchronized (new Object()) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");

                }
            }

        }
 *      同步代码块中的对象针对多个线程必须是同一个,
 *      其实这个对象被称为同步锁对象
 * 
 */
public class TicketDemo {
    public static void main(String[] args) {
        Ticket t1 = new Ticket();
        Thread t = new Thread(t1, "窗口1");
        Thread t4 = new Thread(t1, "窗口2");
        Thread t2 = new Thread(t1, "窗口3");
        Thread t3 = new Thread(t1, "窗口4");
        t.start();
        t4.start();
        t2.start();
        t3.start();

    }
}



package com.thread.ticket2;

/**
 * 类似火车上厕所的例子
 * 
 * @author yuliyang
 * @version $Id: Ticket.java, v 0.1 2016年11月30日 下午10:02:32 yuliyang Exp $
 */
public class Ticket implements Runnable {
    private static int   tickets = 100;
    private final Object b       = new Object();

    @Override
    public void run() {
        while (true) {
            /**
             * t1,t2,t3,t4
             * 假设 ticket = 1
             * t1过来,看到synchronized就知道下面的代码被同步了。
             * 看到obj就知道这是这是锁对象,假设t1进来前,obj这个锁是开的状态
             * 
             */
            synchronized (b) {
                /**
                 * t1进来后,就把锁对象的状态改为关的状态。
                 */
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                        //t1睡着了。
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");

                }
                //t1出来了,你们继续抢,我也还可以抢。
            }

        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值