Java线程及其多线程入门

Java多线程

_01Thread类及Runnable接口

线程执行具有随机性。


* 创建新执行线程的两种方法:
    1. 将类声明为 Thread 的子类。该类应重写 Thread 类的 run()方法。创建对象,开启线程。run方法相当于其他线程的main方法。
    2. 声明一个实现 Runnable 接口的类。实现 run()方法,然后创建Runnable 的子类对象,并传到某个线程的构造方法中,开启线程。

1、实现线程程序继承Thread

Thread是程序中的执行线程。JAVA虚拟机允许程序并发地运行多个执行线程。


例:继承Thread

/*
 * 定义Thread子类,重写run()方法
 */
public class SubThread extends Thread{
    public void run(){
        for (int i = 0; i < 5; i++){
            System.out.println("run..."+i);
        }
    }

    @Test
    /*
     * 创建和启动一个线程
     *      创建Thread子类对象
     *      子类对象调用方法start():
     *          让线程程序执行,JVM调用线程中的run
     */
    public void testSubThread(){
        SubThread st0 = new SubThread();
        SubThread st1 = new SubThread();
        st0.start();
        st1.start();
        for(int i = 0; i < 50; i++) {
            System.out.println(i);
        }
    }

2、为什么要继承Thread类

1*、:我们为什么要继承Thread类,并调用其的start方法才能开启线程呢?
     继承Thread类:因为Thread类用来描述线程,具备线程应该有功能。那为什么不直接创建Thread类的对象呢?
     如下代码:
     Thread t1 = new Thread();
     t1.run();//这样做没有错,但是该start调用的是Thread类中的run方法
              //而这个run方法没有做什么事情,更重要的是这个run方法中并没有定义我们需要让线程执行的代码。

2*、: 创建线程的目的是什么?
    是为了建立程序单独的执行路径,让多部分代码实现同时执行。也就是说线程创建并执行需要给定线程要执行的任务。
     对于之前所讲的主线程,它的任务定义在main函数中。自定义线程需要执行的任务都定义在run方法中。

3*、:多线程执行时,到底在内存中是如何运行的呢?
    多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。
    当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。

3、获取线程名字Thread类方法

  1. 获取线程名字Thread类方法getName
public class NameThread extends Thread {
    public void run(){
        System.out.println(getName());
    }
}
    @Test
    /*
     * 每个线程,都有自己的名字
     * 新建的线程的默认名字为"Thread-0","Thread-1",...
     */
    public void testThreadName(){
        NameThread nt = new NameThread();
        nt.start();
    }
  1. 获取线程名字Thread类方法currentThread
    @Test
    /*
     * Thread中
     *      static Thread currentThread()返回正在执行的线程对象
     */
    public void testCurrentThread(){
        System.out.println(Thread.currentThread());
    }

4、线程名字设置

  1. 通过调用父类Thread的构造器
public class NameThread extends Thread {
    public NameThread(){
        super("小猫");
    }
    public void run(){
        System.out.println(getName());
    }
}
  1. 通过调用setName()方法设置
    public void testThreadName(){
        NameThread nt = new NameThread();
        nt.setName("��");
        nt.start();
    }

5、Thread类方法sleep

    @Test
    public void testSleepThread() throws Exception{
        SleepThread st = new SleepThread();
        st.start();
        for (int i = 0; i < 5; i++) {
            //睡眠500ms,500ms已到并且cpu切换到该线程继续向下执行
            Thread.sleep(500);
            System.out.println(i);
        }
    }

6、实现线程的另一种方式实现Runnable接口

/*
 * 实现线程的另一方式,接口实现
 * 实现接口Runnable,重写run方法
 */
public class SubRunnable implements Runnable{
    public void run(){
        for (int i = 0; i < 50; i++) {
            System.out.println("run..."+i);
        }
    }
}
    @Test
    /*
     * 实现接口方式的线程
     *      创建Thread类对象,构造方法中,传递Runnable接口实现类
     *      调用Thread类方法start()
     */
    public void testRunnable(){
        SubRunnable sr = new SubRunnable();
        Thread t= new Thread(sr);
        t.start();
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread()+"..."+i);
        }
    }

7、实现接口方式的好处

* 实现接口方式的好处:
     第二种方式实现Runnable接口避免了单继承的局限性,所以较为常用。
     实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。
    继承Thread类,线程对象和线程任务耦合在一起。
    一旦创建Thread类的子类对象,既是线程对象,有又有线程任务。
    实现runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。
    (降低紧密性或者依赖性,创建线程和执行任务不绑定)

8、匿名内部类实现线程程序

public class NiMing {
    public static void main(String[] args) {
        new Thread(){
            public void run(){
                System.out.println("继承方式");
            }
        }.start();

        new Thread(new Runnable(){
            public void run(){
                System.out.println("实现接口方式");
            }
        }).start();
    }
}

_02 线程池

1、线程池原理

  1.在java中,如果每个请求到达就创建一个新线程,开销是相当大的。
  2.在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。
  3.除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。
    如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。
    为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。
  线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。

2、JDK5实现线程池

    /*
     * JDK1.5新特性,实现线程池程序
     * 使用工厂类 Executors中的静态方法创建线程对象,指定线程的个数
     *       static ExecutorService newFixedThreadPool(int 个数) 
     *      返回的是ExecutorService接口的实现类 (线程池对象)
     * 接口实现类对象,调用方法submit (Ruunable r) 提交线程执行任务
     */
    @Test
    public void testThreadPool(){
        //调用工厂类的静态方法,创建线程池对象
        //返回线程池对象,是返回的接口
        ExecutorService es = Executors.newFixedThreadPool(2);
        //调用接口实现类对象es中的方法submit提交线程任务
        //将Runnable接口实现类对象传递给submit
        es.submit(new ThreadPoolRunnable());
        es.submit(new ThreadPoolRunnable());
        es.submit(new ThreadPoolRunnable());
    }

public class ThreadPoolRunnable implements Runnable {
    public void run(){
        System.out.println(Thread.currentThread().getName()+"线程提交任务");
    }
}

3、实现线程的Callable接口方式

    /*
     * 实现线程池的Callable接口方式
     * 实现步骤:
     *      工厂类 Executors静态方法newFixedThreadPool方法,创建线程池对象
     *      返回线程池对象ExecutorService接口类型,调用submit提交进程任务
     *      submit(Callable c)
     */
    @Test
    public void testThreadPool1() throws InterruptedException, ExecutionException{
        ExecutorService es = Executors.newFixedThreadPool(2);
        //提交线程任务的方法submit方法返回 Future接口的实现类
        Future<String> f = es.submit(new ThreadPoolCallable());
        String s = f.get();
        System.out.println(s);
    }

public class ThreadPoolCallable implements Callable<String>{
    public String call(){
        return "Callable方式可以具有返回值";
    }
}

4、线程实现异步计算案例

 *:同步代码块的执行原理
     同步代码块: 在代码块声明上 加上synchronized
        synchronized (锁对象) {
        可能会产生线程安全问题的代码
    }
    同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
public class ThreadPoolCallable implements Callable<String>{
    public String call(){
        return "Callable方式可以具有返回值";
    }
}

    /*
     * 使用多线程技术,求和
     * 两个线程,一个计算1+100,另一个计算1+200的和
     * 两个线程的异步计算
     */
    @Test
    public void testThreadPoolAdd() throws InterruptedException, ExecutionException{
        ExecutorService es = Executors.newFixedThreadPool(2);
        Future<Integer> f1 = es.submit(new GetSumCallable(100));
        Future<Integer> f2 = es.submit(new GetSumCallable(200));
        System.out.println(f1.get());
        System.out.println(f2.get());
        es.shutdown();
    }

_03 线程安全

*A:线程操作共享数据的安全问题
    如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。
    程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

1、同步代码块解决线程安全问题

public class TestTickets {
    public static void main(String[] args) {
        //创建Runnable接口实现类对象
        Tickets t = new Tickets();
        Thread t0 = new Thread(t);
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);

        t0.start();
        t1.start();
        t2.start();
    }
}
/*
 * 通过线程休眠,出现安全问题
 * 解决安全问题,Java程序,提供技术,同步技术
 * 公式:
 *      synchronized(任意对象){
 *          线程要操作的共享数据
 *      }
 *      同步代码块
 */
public class Tickets implements Runnable{
    private int ticket = 100;
    private Object obj = new Object();

    public void run(){
        while(true){
            //线程共享块,保证安全,加入同步代码块
            synchronized (obj) {
                if(ticket > 0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"出售的第"+ticket--);
                }
            }
        }
    }
}

2、同步的上厕所原理

a:不使用同步:线程在执行的过程中会被打扰
     线程比喻成人
     线程执行代码就是上一个厕所
     第一个人正在上厕所,上到一半,被另外一个人拉出来
b:使用同步:
    线程比喻成人
    线程执行代码就是上一个厕所
    锁比喻成厕所门
  第一个人上厕所,会锁门
  第二个人上厕所,看到门锁上了,等待第一个人上完再去上厕所

3、同步方法

  *  采用同步方法形式,解决线程的安全问题
  *  好处: 代码简洁
  *  将线程共享数据,和同步,抽取到一个方法中
  *  在方法的声明上,加入同步关键字
  *  
  *  问题:
  *    同步方法有锁吗,肯定有,同步方法中的对象锁,是本类对象引用 this
  *    如果方法是静态的呢,同步有锁吗,绝对不是this
  *    锁是本类自己.class 属性
  *    静态方法,同步锁,是本类类名.class属性

例:

public class Tickets implements Runnable{
    private int ticket = 100;

    public void run() {
        while (true) {
            payTicket();
        }
    }

    public synchronized void payTicket() {
        if (ticket > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "出售的第" + ticket--);
        }
    }
}

_04JDK1.5新特性Lock接口

*A:JDK1.5新特性Lock接口
    * Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
    * Lock接口中的常用方法
        void lock()
        void unlock()
    * Lock提供了一个更加面对对象的锁,在该锁中提供了更多的操作锁的功能。
    * 我们使用Lock接口,以及其中的lock()方法和unlock()方法替代同步,

1、例:对电影院卖票案例中Ticket

public class Tickets implements Runnable{
    //定义出售的资源
    private int ticket = 100;
    //在类的成员位置,创建Lock接口的实现类
    private Lock lock = new ReentrantLock();

    public void run(){
        while(true){
            //调用Lock接口方法lock获取锁
            lock.lock();
            //对票数贩毒案,大于0,可以出售,
            if(ticket > 0){
                try{
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
                }catch(Exception e){

                }finally{
                    //释放锁,调用Lock接口方法unlock
                    lock.unlock();
                }
            }
        }
    }
}

2、线程的死锁代码实现

public class LockA {
    private LockA(){}
    public static final LockA locka = new LockA();
}

public class LockB {
    private LockB(){}
    public static final LockB lockb = new LockB();
}

public class DeadLock implements Runnable{
    private int i = 0;
    public void run(){
        while(true){
            if(i % 2 == 0){
                //先进入A同步,在进入B同步
                synchronized (LockA.locka) {
                    System.out.println("if...locka");
                    synchronized (LockB.lockb) {
                        System.out.println("if...lockb");
                    }
                }
            }else{
                //先进入B同步,再进入A同步
                synchronized (LockB.lockb) {
                    System.out.println("else...lockb");
                    synchronized (LockA.locka) {
                        System.out.println("else...locka");
                    }
                }
            }
            i++;
        }
    }
}

public class DeadLockDemo {
    public static void main(String[] args) {        
        DeadLock dead = new DeadLock();
        Thread t0 = new Thread(dead);
        Thread t1 = new Thread(dead);
        t0.start();
        t1.start();
    }
}

3、线程等待与唤醒案例

*A:线程等待与唤醒案例介绍 
    等待唤醒机制所涉及到的方法:
       wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。
       notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。
       notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。
   其实,所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是,这些方法都是在 同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。

例:


资源类编写:

/*
 * 定义资源类,有2个成员变量
 * name,sex
 * 同时有2个线程,对资源中的变量操作
 * 1个对name,age赋值
 * 2个对name,age做变量的输出打印
 */
public class Resource {
    public String name;
    public String sex;
}

线程等待与唤醒案例输入进程:

public class Input implements Runnable{
    private Resource r = new Resource();

    public void run() {
        int i = 0;
        while (true) {
            if (i % 2 == 0) {
                r.name = "张三";
                r.sex = "男";
            } else {
                r.name = "lisi";
                r.sex = "nv";
            }
            i++;
        }
    }
}

线程等待与唤醒案例输出进程:

public class Output implements Runnable{
    private Resource r = new Resource();
    public void run() {
        while(true){
            System.out.println(r.name+"..."+r.sex);
        }
    }
}

线程等待与唤醒案例测试类:

public class ThreadDemo {
    public static void main(String[] args) {
        Resource r = new Resource();
        Input in = new Input();
        Output out = new Output();

        Thread tin = new Thread(in);
        Thread tout = new Thread(out);

        tin.start();
        tout.start();
    }
}

出现的null值问题是由于r不是同一个。


线程等待与唤醒案例null值解决:

public class Input implements Runnable{
    private Resource r;
    public Input(Resource r){
        this.r = r;
    }

    public void run() {
        int i = 0;
        while (true) {
            if (i % 2 == 0) {
                r.name = "张三";
                r.sex = "男";
            } else {
                r.name = "lisi";
                r.sex = "nv";
            }
            i++;
        }
    }
}

public class Output implements Runnable {
    private Resource r;
    public Output(Resource r){
        this.r = r;
    }
    public void run() {
        while(true){
            System.out.println(r.name+"..."+r.sex);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        Resource r = new Resource();
        Input in = new Input(r);
        Output out = new Output(r);

        Thread tin = new Thread(in);
        Thread tout = new Thread(out);

        tin.start();
        tout.start();
    }
}

线程等待与唤醒案例数据安全解决

public class Resource {
    public String name;
    public String sex;
    public boolean flag = false;
}

public class Input implements Runnable{
    private Resource r;
    public Input(Resource r){
        this.r = r;
    }

    public void run() {
        int i = 0;
        while (true) {
            synchronized (r) {
                if(r.flag){
                    try {
                        r.wait();
                    } catch (InterruptedException e) {}
                }
            if (i % 2 == 0) {
                r.name = "张三";
                r.sex = "男";
            } else {
                r.name = "lisi";
                r.sex = "nv";
            }
            //将对方线程唤醒,标记改为true
            r.flag = true;
            r.notify();
            }
            i++;
        }
    }
}

public class Output implements Runnable {
    private Resource r;
    public Output(Resource r){
        this.r = r;
    }
    public void run() {
        while(true){
            synchronized (r) {
                if(!r.flag){
                    try {
                        r.wait();
                    } catch (InterruptedException e) {}
                }
                System.out.println(r.name+"..."+r.sex);
                r.flag = false;
                r.notify();
            }
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        Resource r = new Resource();
        Input in = new Input(r);
        Output out = new Output(r);

        Thread tin = new Thread(in);
        Thread tout = new Thread(out);

        tin.start();
        tout.start();
    }
}


猜你喜欢

转载自blog.csdn.net/longge0508/article/details/80177299