No_16_0325 Java基础学习第二十四天—多线程学习总结

文档版本 开发工具 测试平台 工程名字 日期 作者 备注
V1.0 2016.03.25 lutianfei none

第十章 多线程

多线程概述

什么是进程?

  • 进程:就是正在运行的程序。
  • 进程是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。

多进程有什么意义呢?

  • 可以在一个时间段内执行多个任务。
  • 可以提高CPU的使用率。

什么是线程呢?

  • 在同一个进程内又可以执行多个任务,而这每一个任务我就可以看出是一个线程
  • 线程:是程序的执行单元执行路径。是程序使用CPU的最基本单位
  • 单线程:如果程序只有一条执行路径。
  • 多线程:如果程序有多条执行路径。

多线程有什么意义呢?

  • 多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率
  • 程序的执行其实都是在抢CPU的资源,CPU的执行权。
  • 多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权
  • 我们是不敢保证哪一个线程能够在哪个时刻抢到,所以线程的执行有随机性

什么是并发呢?

  • 大家注意两个词汇的区别:并行并发
  • 并行逻辑上同时发生,指在某一个时间内同时运行多个程序。
  • 并发物理上同时发生,指在某一个时间点同时运行多个程序。
  • 我们可以实现真正意义上的并发,例如:多个CPU就可以实现,不过得知道如何调度和控制它们。

Java程序运行原理

  • java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。
  • 思考:jvm虚拟机的启动是单线程的还是多线程的?
    • JVM启动至少启动了垃圾回收线程主线程,所以是多线程的

如何实现多线程

  • 由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。
  • Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。但是Java可以去调用C/C++写好的程序来实现多线程程序。
  • 由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,然后提供一些类供我们使用。我们就可以实现多线程程序了。

多线程的实现方案1

  • 方式1:继承Thread类。
  • 步骤:
    • A:自定义类MyThread继承Thread类。
    • B:MyThread类里面重写run()
    • C:创建对象
    • D:启动线程
      • 面试题:run()和start()的区别?
      • run():仅仅是封装被线程执行的代码,直接调用是普通方法
      • start():首先启动了线程,然后再由jvm去调用该线程的run()方法。
      • 面试题:为什么重写run()方法?`
      • 不是类中的所有代码都需要被线程执行的。为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。
/*
 * 需求:我们要实现多线程的程序。
 */
public class MyThreadDemo {
    public static void main(String[] args) {
        // 创建线程对象
        // MyThread my = new MyThread();
        // // 启动线程
        // my.run();
        // my.run();
        // 调用run()方法为什么是单线程的呢?
        // 因为run()方法直接调用其实就相当于普通的方法调用,所以你看到的是单线程的效果
        // 要想看到多线程的效果,就必须说说另一个方法:start()
        // 面试题:run()和start()的区别?
        // run():仅仅是封装被线程执行的代码,直接调用是普通方法
        // start():首先启动了线程,然后再由jvm去调用该线程的run()方法。
        // MyThread my = new MyThread();
        // my.start();
        // // IllegalThreadStateException:非法的线程状态异常
        // // 为什么呢?因为这个相当于是my线程被调用了两次。而不是两个线程启动。
        // my.start();

        // 创建两个线程对象
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

        my1.start();
        my2.start();
    }
}

public class MyThread extends Thread {

    @Override
    public void run() {
        // 自己写代码
        // System.out.println("好好学习,天天向上");
        // 一般来说,被线程执行的代码肯定是比较耗时的。所以我们用循环改进
        for (int x = 0; x < 200; x++) {
            System.out.println(x);
        }
    }

}

如何获取和设置线程名称

  • Thread类的基本获取和设置方法

    • public final String getName():获取线程的名称。
    • public final void setName(String name):设置线程的名称
    • 通过构造方法也可以给线程起名字
  • 思考:
    • 如何获取main方法所在的线程名称呢?
    • public static Thread currentThread()
      • 这样就可以获取任意方法所在的线程名称
/*
 * 针对不是Thread类的子类中如何获取线程对象名称呢?
 * public static Thread currentThread():返回当前正在执行的线程对象
 * Thread.currentThread().getName()
 */
public class MyThreadDemo {
    public static void main(String[] args) {
        // 创建线程对象
        //无参构造+setXxx()
        // MyThread my1 = new MyThread();
        // MyThread my2 = new MyThread();
        // //调用方法设置名称
        // my1.setName("林青霞");
        // my2.setName("刘意");
        // my1.start();
        // my2.start();

        //带参构造方法给线程起名字
        // MyThread my1 = new MyThread("林青霞");
        // MyThread my2 = new MyThread("刘意");
        // my1.start();
        // my2.start();

        //我要获取main方法所在的线程对象的名称,该怎么办呢?
        //遇到这种情况,Thread类提供了一个很好玩的方法:
        //public static Thread currentThread():返回当前正在执行的线程对象
        System.out.println(Thread.currentThread().getName());
    }
}

/*
名称为什么是:Thread-? 编号

class Thread {
    private char name[];

    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null);
    }

     private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        //大部分代码被省略了
        this.name = name.toCharArray();
    }

    public final void setName(String name) {
        this.name = name.toCharArray();
    }

    private static int threadInitNumber; //0,1,2
    private static synchronized int nextThreadNum() {
        return threadInitNumber++; //return 0,1
    }

    public final String getName() {
        return String.valueOf(name);
    }
}

class MyThread extends Thread {
    public MyThread() {
        super();
    }
}

*/

public class MyThread extends Thread {

    public MyThread() {
    }

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

    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(getName() + ":" + x);
        }
    }
}

线程调度

  • 假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?
  • 线程有两种调度模型:
    • 分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
    • 抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
    • Java使用的是抢占式调度模型
      • public final int getPriority():返回线程对象的优先级
      • public final void setPriority(int newPriority):更改线程的优
      • 线程默认优先级是5
      • 线程优先级的范围是:1-10。
      • 线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。
public class ThreadPriority extends Thread {
    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(getName() + ":" + x);
        }
    }
}

/*
 * 注意:
 * IllegalArgumentException:非法参数异常。
 * 抛出的异常表明向方法传递了一个不合法或不正确的参数。
 *
 */
public class ThreadPriorityDemo {
    public static void main(String[] args) {
        ThreadPriority tp1 = new ThreadPriority();
        ThreadPriority tp2 = new ThreadPriority();
        ThreadPriority tp3 = new ThreadPriority();

        tp1.setName("东方不败");
        tp2.setName("岳不群");
        tp3.setName("林平之");

        // 获取默认优先级
        // System.out.println(tp1.getPriority());
        // System.out.println(tp2.getPriority());
        // System.out.println(tp3.getPriority());

        // 设置线程优先级
        // tp1.setPriority(100000);

        //设置正确的线程优先级
        tp1.setPriority(10);
        tp2.setPriority(1);

        tp1.start();
        tp2.start();
        tp3.start();
    }
}

线程控制

  • 我们已经知道了线程的调度,接下来我们就可以使用如下方法对象线程进行控制
  • 线程休眠
    • public static void sleep(long millis)
  • 线程加入
    • public final void join()
  • 线程礼让
    • public static void yield()
  • 后台线程
    • public final void setDaemon(boolean on)
  • 中断线程
    • public final void stop()
    • public void interrupt()
  • 案例:线程休眠
public class ThreadSleep extends Thread {
    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(getName() + ":" + x + ",日期:" + new Date());
            // 睡眠
            // 困了,我稍微休息1秒钟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/*
 * 线程休眠
 *        public static void sleep(long millis)
 */
public class ThreadSleepDemo {
    public static void main(String[] args) {
        ThreadSleep ts1 = new ThreadSleep();
        ThreadSleep ts2 = new ThreadSleep();
        ThreadSleep ts3 = new ThreadSleep();

        ts1.setName("林青霞");
        ts2.setName("林志玲");
        ts3.setName("林志颖");

        ts1.start();
        ts2.start();
        ts3.start();
    }
}
  • 案例:线程加入
public class ThreadJoin extends Thread {
    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(getName() + ":" + x);
        }
    }
}

/*
 * public final void join():等待该线程终止。
 */
public class ThreadJoinDemo {
    public static void main(String[] args) {
        ThreadJoin tj1 = new ThreadJoin();
        ThreadJoin tj2 = new ThreadJoin();
        ThreadJoin tj3 = new ThreadJoin();

        tj1.setName("李渊");
        tj2.setName("李世民");
        tj3.setName("李元霸");

        tj1.start();
        try {
            tj1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        tj2.start();
        tj3.start();
    }
}
  • 案例:线程礼让
public class ThreadYield extends Thread {
    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(getName() + ":" + x);
            Thread.yield();
        }
    }
}

/*
 * public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
 * 让多个线程的执行更和谐,但是不能靠它保证一人一次。
 */
public class ThreadYieldDemo {
    public static void main(String[] args) {
        ThreadYield ty1 = new ThreadYield();
        ThreadYield ty2 = new ThreadYield();

        ty1.setName("林青霞");
        ty2.setName("刘意");

        ty1.start();
        ty2.start();
    }
}
  • 案例:后台线程
public class ThreadDaemon extends Thread {
    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(getName() + ":" + x);
        }
    }
}

/*
 * public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。
 * 当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。
 *
 * 游戏:坦克大战。
 */
public class ThreadDaemonDemo {
    public static void main(String[] args) {
        ThreadDaemon td1 = new ThreadDaemon();
        ThreadDaemon td2 = new ThreadDaemon();

        td1.setName("关羽");
        td2.setName("张飞");

        // 设置收获线程
        td1.setDaemon(true);
        td2.setDaemon(true);

        td1.start();
        td2.start();

        Thread.currentThread().setName("刘备");
        for (int x = 0; x < 5; x++) {
            System.out.println(Thread.currentThread().getName() + ":" + x);
        }
    }
}
  • 案例:中断线程
public class ThreadStop extends Thread {
    @Override
    public void run() {
        System.out.println("开始执行:" + new Date());

        // 我要休息10秒钟,亲,不要打扰我哦
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            // e.printStackTrace();
            System.out.println("线程被终止了");
        }

        System.out.println("结束执行:" + new Date());
    }
}

/*
 * public final void stop():让线程停止,过时了,但是还可以使用。
 * public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。
 */
public class ThreadStopDemo {
    public static void main(String[] args) {
        ThreadStop ts = new ThreadStop();
        ts.start();

        // 你超过三秒不醒过来,我就干死你
        try {
            Thread.sleep(3000);
            // ts.stop();
            ts.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程的生命周期图

多线程的实现方案2

  • 实现Runnable接口

    • 步骤:

      • A:自定义类MyRunnable实现Runnable接口
      • B:重写run()方法
      • C:创建MyRunnable类的对象
      • D:创建Thread类的对象,并把C步骤的对象作为构造参数传递
  • 实现接口方式的好处
    • 可以避免由于Java单继承带来的局限性。
    • 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。
public class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            // 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用
            System.out.println(Thread.currentThread().getName() + ":" + x);
        }
    }

}

/*
 * 方式2:实现Runnable接口
 */
public class MyRunnableDemo {
    public static void main(String[] args) {
        // 创建MyRunnable类的对象
        MyRunnable my = new MyRunnable();

        // 创建Thread类的对象,并把C步骤的对象作为构造参数传递
        // Thread(Runnable target)
        // Thread t1 = new Thread(my);
        // Thread t2 = new Thread(my);
        // t1.setName("林青霞");
        // t2.setName("刘意");

        // Thread(Runnable target, String name)
        Thread t1 = new Thread(my, "林青霞");
        Thread t2 = new Thread(my, "刘意");

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

多线程程序练习

  • 需求:

    • 某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
    • 两种方式实现
      • 继承Thread类
      • 实现Runnable接口
  • 方式一:
public class SellTicket extends Thread {

    // 定义100张票
    // private int tickets = 100;
    // 为了让多个线程对象共享这100张票,我们其实应该用静态修饰
    private static int tickets = 100;

    @Override
    public void run() {
        // 定义100张票
        // 每个线程进来都会走这里,这样的话,每个线程对象相当于买的是自己的那100张票,这不合理,所以应该定义到外面
        // int tickets = 100;

        // 是为了模拟一直有票
        while (true) {
            if (tickets > 0) {
                System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
            }
        }
    }
}

/*
 * 某电影院目前正在上映贺岁大片(红高粱,少林寺传奇藏经阁),共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
 * 继承Thread类来实现。
 */
public class SellTicketDemo {
    public static void main(String[] args) {
        // 创建三个线程对象
        SellTicket st1 = new SellTicket();
        SellTicket st2 = new SellTicket();
        SellTicket st3 = new SellTicket();

        // 给线程对象起名字
        st1.setName("窗口1");
        st2.setName("窗口2");
        st3.setName("窗口3");

        // 启动线程
        st1.start();
        st2.start();
        st3.start();
    }
}
  • 方式二:
public class SellTicket implements Runnable {
    // 定义100张票
    private int tickets = 100;

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

/*
 * 实现Runnable接口的方式实现
 */
public class SellTicketDemo {
    public static void main(String[] args) {
        // 创建资源对象
        SellTicket st = new SellTicket();

        // 创建三个线程对象
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        // 启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

关于电影院卖票程序的思考

  • 我们前面讲解过电影院售票程序,从表面上看不出什么问题,但是在真实生活中,售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟

    • 改实现接口方式的卖票程序

      • 每次卖票延迟100毫秒

改进后的电影院售票出现问题

  • 问题

    • 相同的票出现多次

      • CPU的一次操作必须是原子性的
    • 还出现了负数的票
      • 随机性和延迟导致的
  • 注意
    • 线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。
public class SellTicket implements Runnable {
    // 定义100张票
    private int tickets = 100;

//    @Override
//    public void run() {
//        while (true) {
//            // t1,t2,t3三个线程
//            // 这一次的tickets = 100;
//            if (tickets > 0) {
//                // 为了模拟更真实的场景,我们稍作休息
//                try {
//                    Thread.sleep(100); // t1就稍作休息,t2就稍作休息
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//
//                System.out.println(Thread.currentThread().getName() + "正在出售第"
//                        + (tickets--) + "张票");
//                // 理想状态:
//                // 窗口1正在出售第100张票
//                // 窗口2正在出售第99张票
//                // 但是呢?
//                // CPU的每一次执行必须是一个原子性(最简单基本的)的操作。
//                // 先记录以前的值
//                // 接着把ticket--
//                // 然后输出以前的值(t2来了)
//                // ticket的值就变成了99
//                // 窗口1正在出售第100张票
//                // 窗口2正在出售第100张票
//
//            }
//        }
//    }

    @Override
    public void run() {
        while (true) {
            // t1,t2,t3三个线程
            // 这一次的tickets = 1;
            if (tickets > 0) {
                // 为了模拟更真实的场景,我们稍作休息
                try {
                    Thread.sleep(100); //t1进来了并休息,t2进来了并休息,t3进来了并休息,
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName() + "正在出售第"
                        + (tickets--) + "张票");
                //窗口1正在出售第1张票,tickets=0
                //窗口2正在出售第0张票,tickets=-1
                //窗口3正在出售第-1张票,tickets=-2
            }
        }
    }
}

解决线程安全问题的基本思想

  • 首先想为什么出现问题?(也是我们判断是否有问题的标准)

    • 是否是多线程环境
    • 是否有共享数据
    • 是否有多条语句操作共享数据
  • 如何解决多线程安全问题呢?
    • 基本思想:让程序没有安全问题的环境。

      • 把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。

同步的特点

  • 同步的前提

    • 多个线程
    • 多个线程使用的是同一个锁对象
  • 同步的好处
    • 同步的出现解决了多线程的安全问题。
  • 同步的弊端
    • 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

解决线程安全问题实现1

  • 同步代码块
  • 格式:
    • synchronized(对象){需要同步的代码;}
  • 同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
  • 同步代码块的对象可以是哪些呢?
public class SellTicket implements Runnable {
    // 定义100张票
    private int tickets = 100;
    //创建锁对象
    private Object obj = new Object();

    @Override
        public void run() {
        while (true) {
            // t1,t2,t3都能走到这里
            // 假设t1抢到CPU的执行权,t1就要进来
            // 假设t2抢到CPU的执行权,t2就要进来,发现门是关着的,进不去。所以就等着。
            // 门(开,关)
            synchronized (obj) { // 发现这里的代码将来是会被锁上的,所以t1进来后,就锁了。(关)
                if (tickets > 0) {
                    try {
                        Thread.sleep(100); // t1就睡眠了
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "正在出售第" + (tickets--) + "张票 ");
                    //窗口1正在出售第100张票
                }
            } //t1就出来可,然后就开门。(开)
        }
    }
}

/*
 * 如何解决线程安全问题呢?
 * 注意:
 * 同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
 * 多个线程必须是同一把锁。
 */
public class SellTicketDemo {
    public static void main(String[] args) {
        // 创建资源对象
        SellTicket st = new SellTicket();

        // 创建三个线程对象
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        // 启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

解决线程安全问题实现2:同步方法

  • 就是把同步关键字加到方法上
  • 同步方法的锁对象是什么呢?
    • this
  • 如果是静态方法,同步方法的锁对象又是什么呢?
    • 类的字节码文件对象。
  • 那么,我们到底使用谁?
    • 如果锁对象是this,就可以考虑使用同步方法。
    • 否则能使用同步代码块的尽量使用同步代码块。
同步解决线程安全问题总结
  • A:同步代码块

    • synchronized(对象) {

      需要被同步的代码;

      }

    • 这里的锁对象可以是任意对象
  • B:同步方法
    • 把同步关键字加在方法上。
    • 这里的锁对象是this
  • C:静态同步方法
    • 把同步关键字加在方法上。
    • 锁对象是类的字节码文件对象。(反射会讲)
public class SellTicket implements Runnable {

    // 定义100张票
    private static int tickets = 100;

    // 定义同一把锁
    private Object obj = new Object();
    private Demo d = new Demo();

    private int x = 0;

    //同步代码块用obj做锁
//    @Override
//    public void run() {
//        while (true) {
//            synchronized (obj) {
//                if (tickets > 0) {
//                    try {
//                        Thread.sleep(100);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//                    System.out.println(Thread.currentThread().getName()
//                            + "正在出售第" + (tickets--) + "张票 ");
//                }
//            }
//        }
//    }

    //同步代码块用任意对象做锁
//    @Override
//    public void run() {
//        while (true) {
//            synchronized (d) {
//                if (tickets > 0) {
//                    try {
//                        Thread.sleep(100);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//                    System.out.println(Thread.currentThread().getName()
//                            + "正在出售第" + (tickets--) + "张票 ");
//                }
//            }
//        }
//    }

    @Override
    public void run() {
        while (true) {
            if(x%2==0){
                synchronized (SellTicket.class) {//静态方法的安全锁,若是普通同步方法则为:this
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()
                                + "正在出售第" + (tickets--) + "张票 ");
                    }
                }
            }else {
//                synchronized (d) {
//                    if (tickets > 0) {
//                        try {
//                            Thread.sleep(100);
//                        } catch (InterruptedException e) {
//                            e.printStackTrace();
//                        }
//                        System.out.println(Thread.currentThread().getName()
//                                + "正在出售第" + (tickets--) + "张票 ");
//                    }
//                }

                sellTicket();

            }
            x++;
        }
    }

//同步方法:
    //如果一个方法一进去就看到了代码被同步了,那么我就再想能不能把这个同步加在方法上呢?
//     private synchronized void sellTicket() {
//            if (tickets > 0) {
//            try {
//                    Thread.sleep(100);
//            } catch (InterruptedException e) {
//                    e.printStackTrace();
//            }
//            System.out.println(Thread.currentThread().getName()
//                        + "正在出售第" + (tickets--) + "张票 ");
//            }
//    }

//静态同步方法:
    private static synchronized void sellTicket() {
        if (tickets > 0) {
        try {
                Thread.sleep(100);
        } catch (InterruptedException e) {
                e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()
                    + "正在出售第" + (tickets--) + "张票 ");
        }
}
}

class Demo {
}

public class SellTicketDemo {
    public static void main(String[] args) {
        // 创建资源对象
        SellTicket st = new SellTicket();

        // 创建三个线程对象
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        // 启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

常见线程安全集合

public class ThreadDemo {
    public static void main(String[] args) {
        // 线程安全的类
        StringBuffer sb = new StringBuffer();
        Vector<String> v = new Vector<String>();
        Hashtable<String, String> h = new Hashtable<String, String>();

        // Vector是线程安全的时候才去考虑使用的,但是我还说过即使要安全,我也不用你
        // 那么到底用谁呢?
        // public static <T> List<T> synchronizedList(List<T> list)
        List<String> list1 = new ArrayList<String>();// 线程不安全
        List<String> list2 = Collections
                .synchronizedList(new ArrayList<String>()); // 线程安全
    }
}
时间: 2024-07-28 23:43:31

No_16_0325 Java基础学习第二十四天—多线程学习总结的相关文章

Java基础学习第二十五天——多线程学习总结(二)

文档版本 开发工具 测试平台 工程名字 日期 作者 备注 V1.0 2016.03.31 lutianfei none JDK5中Lock锁的使用 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock. Lock: void lock(): 获取锁. void unlock():释放锁. ReentrantLock是Lock的实现类. public class SellT

JAVA基础之(十四)--“多线程”

菜鸟里写的暂时够用了,菜鸟 不充点: 如果只想重写 run() 方法,而不重写其他 Thread 方法,那么应使用 Runnable 接口.这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类[java的api建议] 菜鸟中只有前半部分,线程同步,死锁等需要看书:重在理解概念和应用: 原文地址:https://www.cnblogs.com/silence-fire/p/9756442.html

python全栈开发基础【第二十四篇】(利用threading模块开线程、join与守护线程、GIL与Lock)

一多线程的概念介绍 threading模块介绍 threading模块和multiprocessing模块在使用层面,有很大的相似性. 二.开启多线程的两种方式 创建线程的开销比创建进程的开销小,因而创建线程的速度快. #开启进程的第一种方式 from multiprocessing import Process from threading import Thread import os import time def work(): print('<%s> is running'%os.g

JAVA学习第二十四课(多线程(三))- 线程的同步

继续以卖票为例 一.线程安全问题的解决 同步的第一种表现形式:同步代码块 思路: 将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程是不允许参与运算的,必须要当期线程把代码执行完毕后,其他线程才可以参与运算 在java中用同步代码块解决这个问题 同步代码块格式: synchronized(对象) { 需要被同步的代码部分 } class Ticket implements Runnable { private int num = 100; Object god = ne

Java学习第二十四天

1:多线程(理解) (1)JDK5以后的针对线程的锁定操作和释放操作 Lock锁 (2)死锁问题的描述和代码体现 (3)生产者和消费者多线程体现(线程间通信问题) 以学生作为资源来实现的 资源类:Student 设置数据类:SetThread(生产者) 获取数据类:GetThread(消费者) 测试类:StudentDemo 代码: A:最基本的版本,只有一个数据. B:改进版本,给出了不同的数据,并加入了同步机制 C:等待唤醒机制改进该程序,让数据能够实现依次的出现 wait() notify

第二十四篇 jQuery 学习6 删除元素

jQuery 学习6 删除元素 上节课我们做了添加元素,模拟的是楼主发的文章,路人评论,那么同学们这节课学了删除之后,去之前的代码上添加一个删除,模拟一个楼主删除路人的评论. jQuery的删除方法: remove() - 删除被选元素(及其子元素) empty() - 从被选元素中删除子元素 以上引用w3c教程 为了同学们更好的扩展,老师就不在上节课的基础上模拟删除,而是写出删除的功能代码和注意事项,同学们就自己多尝试,来瞧瞧代码先: <!DOCTYPE html> <html>

linux架构学习第二十四天-DNS详解及bind搭建各种DNS测试

内容: 一.DNS 1.域名系统概述 2.域名的结构 3.域名服务器 4.域名解析过程 5.域名服务器的资源记录(resource record RR) 二.bind搭建DNS服务器 1.bind的安装 2.搭建DNS服务器 3.搭建主从DNS服务器 4.实现DNS子域 5.实现DNS视图view(智能DNS) 一.DNS 1.域名系统概述 域名系统DNS(Domain Name System)是因特网使用的命名系统,用来把便于人们使用的机器名字转换成为IP地址.域名系统其实就是名字系统.为什么

JavaEE基础(二十四)/多线程

1.多线程(多线程的引入) 1.什么是线程 线程是程序执行的一条路径, 一个进程中可以包含多条线程 多线程并发执行可以提高程序的效率, 可以同时完成多项工作 2.多线程的应用场景 红蜘蛛同时共享屏幕给多个电脑 迅雷开启多条线程一起下载 QQ同时和多个人一起视频 服务器同时处理多个客户端请求 2.多线程(多线程并行和并发的区别) 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行.(需要多核CPU) 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于

java 面向对象编程--第十四章 多线程编程

1.  多任务处理有两种类型:基于进程和基于线程. 2.  进程是指一种“自包容”的运行程序,由操作系统直接管理,直接运行,有自己的地址空间,每个进程一开启都会消耗内存. 3.  线程是进程内部单一的顺序控制流.一个进程拥有多个线程.多个线程共享一个进程的内存空间. 4.  基于进程的特点是允许计算机同时运行两个或更多的程序. 5.  基于线程的多任务处理环境中,线程是最小的处理单位. 6.  基于进程所需的开销更少:每个进程都需要操作系统为其分配独立的内存空间:同意进程中的所有线程都在同意内存