图片 1

线程同步,多线程学习总结

java多线程系列3-线程同步,java多线程3-线程

如果一个资源被多个线程同时访问,可能会遭到破坏,这篇文章介绍java线程同步来解决这类问题

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

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

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

引入问题

某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。

方法一:继承Thread类

public class SellTicket extends Thread {

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

    @Override
    public void run() {
        // 定义100张票
        // 是为了模拟一直有票
        while (true) {
            if (tickets > 0) {
                System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
            }
        }
    }
}
/*
 * 继承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();
    }
}

方法二:实现Runnable接口

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毫秒,代码如下:

public class SellTicket implements Runnable {
    // 定义100张票
    private int tickets = 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
            }
        }
    }
}
/*
 * 实现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();
    }
}

出现问题:

相同的票出现多次

CPU的一次操作必须是原子性的

还出现了负数的票

随机性和延迟导致的

第十章 多线程

[TOC]

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

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

  • 是否是多线程环境
  • 是否有共享数据
  • 是否有多条语句操作共享数据

如何解决多线程安全问题呢?

基本思想:让程序没有安全问题的环境。

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

解决线程安全问题实现1–同步代码块

格式:synchronized(对象){需要同步的代码;}

同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。

修改上面的代码如下:

public class SellTicket implements Runnable {
    // 定义100张票
    private int tickets = 100;
    //创建锁对象
    private Object obj = new Object();

    @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--) + "张票");
                }
            }
        }
    }
}
/*
 * 同步代码块:
 *         synchronized(对象){
 *             需要同步的代码;
 *         }
 * 
 *         A:对象是什么呢?
 *             我们可以随便创建一个对象试试。
 *         B:需要同步的代码是哪些呢?
 *             把多条语句操作共享数据的代码的部分给包起来
 * 
 *         注意:
 *             同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
 *             多个线程必须是同一把锁。
 */
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–同步方法

就是把同步关键字加到方法上

1、同步方法的锁对象:this

public class SellTicket implements Runnable {
    private static int tickets = 100;
    private Object obj = new Object();
    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "正在出售第" + (tickets--) + "张票 ");
                }
            }
        }
    }
    private synchronized void sellTicket() {
        if(tickets > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()
                    + "正在出售第" + (tickets--) + "张票 ");
        }
    }
}

2、静态方法的锁对象:类的字节码文件对象。

public class SellTicket implements Runnable {
    private static int tickets = 100;
    private Object obj = new Object();
    @Override
    public void run() {
        while (true) {
            synchronized (SellTicket.class) {
                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) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()
                    + "正在出售第" + (tickets--) + "张票 ");
        }
    }
}

同步的前提:

  • 多个线程
  • 多个线程使用的是同一个锁对象

同步的好处:同步的出现解决了多线程的安全问题。

同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

解决线程安全问题实现3–Lock锁的使用

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

ReentrantLock (Java Platform SE 6)

一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellTicket implements Runnable {

    // 定义票
    private int tickets = 100;

    // 定义锁对象
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                // 加锁
                lock.lock();
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "正在出售第" + (tickets--) + "张票");
                }
            } finally {
                // 释放锁
                lock.unlock();
            }
        }
    }

}

 

如果一个资源被多个线程同时访问,可能会遭到破坏,这篇文章介绍java线程同步来解决这类问…

多线程概述

多线程概述

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

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

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

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

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

什么是进程?

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

Java程序运行原理

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

  • 思考:jvm虚拟机的启动是单线程的还是多线程的?

    • JVM启动至少启动了垃圾回收线程主线程,所以是多线程的

多进程有什么意义呢?

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

如何实现多线程

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

什么是线程呢?

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

多线程的实现方案1

  • 方式1:继承Thread类。

  • 步骤:

    • A:自定义类MyThread继承Thread类。
    • B:MyThread类里面重写run()
    • C:创建对象
    • D:启动线程
  • 面试题:run的区别?

    • 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的区别? // 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; } }}
  • 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; } 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; }}class MyThread extends Thread { public MyThread() { super(); }}*/public class MyThread extends Thread { public MyThread() { } public MyThread(String name){ super; } @Override public void run() { for (int x = 0; x < 100; x++) { System.out.println + ":" + x); } }}

多线程有什么意义呢?

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

线程调度

  • 假如我们的计算机只有一个 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 + ":" + 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; //设置正确的线程优先级 tp1.setPriority; tp2.setPriority; tp1.start(); tp2.start(); tp3.start(); }}

什么是并发呢?

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


线程控制

  • 我们已经知道了线程的调度,接下来我们就可以使用如下方法对象线程进行控制

  • 线程休眠

    • 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 + ":" + x + ",日期:" + new Date; // 睡眠 // 困了,我稍微休息1秒钟 try { Thread.sleep; } 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 + ":" + 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 + ":" + 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 + ":" + 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; td2.setDaemon; 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; } 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; // ts.stop(); ts.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } }}

Java程序运行原理

java 命令会启动 java 虚拟机,启动
JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个
“主线程” ,然后主线程去调用某个类的 main 方法。所以
main方法运行在主线程中。在此之前的所有程序都是单线程的。

思考:jvm虚拟机的启动是单线程的还是多线程的?

JVM启动至少启动了垃圾回收线程主线程,所以是多线程的


线程的生命周期图

图片 1

如何实现多线程

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


多线程的实现方案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; // Thread t2 = new Thread; // t1.setName; // t2.setName; // Thread(Runnable target, String name) Thread t1 = new Thread(my, "林青霞"); Thread t2 = new Thread; t1.start(); t2.start(); }}

多线程的实现方案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);
        }
    }

}

 

多线程程序练习

  • 需求:
    • 某电影院目前正在上映贺岁大片,共有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  { if (tickets > 0) { System.out.println + "正在出售第" + (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; st2.setName; st3.setName; // 启动线程 st1.start(); st2.start(); st3.start(); }}
  • 方式二:

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

如何获取和设置线程名称

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);
        }
    }
}

 

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

  • 首先想为什么出现问题?(也是我们判断是否有问题的标准)
    • 是否是多线程环境
    • 是否有共享数据
    • 是否有多条语句操作共享数据
  • 如何解决多线程安全问题呢?
    • 基本思想:让程序没有安全问题的环境。
      • 把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。

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

  • 同步代码块
  • 格式:
    • synchronized{需要同步的代码;}
  • 同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
  • 同步代码块的对象可以是哪些呢?

public class SellTicket implements Runnable { // 定义100张票 private int tickets = 100; //创建锁对象 private Object obj = new Object(); @Override public void run() { while  { // t1,t2,t3都能走到这里 // 假设t1抢到CPU的执行权,t1就要进来 // 假设t2抢到CPU的执行权,t2就要进来,发现门是关着的,进不去。所以就等着。 // 门 synchronized  { // 发现这里的代码将来是会被锁上的,所以t1进来后,就锁了。 if (tickets > 0) { try { Thread.sleep; // 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(); }}
  • 就是把同步关键字加到方法上
  • 同步方法的锁对象是什么呢?
    • this
  • 如果是静态方法,同步方法的锁对象又是什么呢?
    • 类的字节码文件对象。
  • 那么,我们到底使用谁?
    • 如果锁对象是this,就可以考虑使用同步方法。
    • 否则能使用同步代码块的尽量使用同步代码块。

线程调度

假如我们的计算机只有一个 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();
    }
}

 

同步解决线程安全问题总结
  • 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  {// synchronized  {// if (tickets > 0) {// try {// Thread.sleep;// } catch (InterruptedException e) {// e.printStackTrace();// }// System.out.println(Thread.currentThread().getName()// + "正在出售第" + (tickets--) + "张票 ");// }// }// }// } //同步代码块用任意对象做锁// @Override// public void run() {// while  {// synchronized  {// if (tickets > 0) {// try {// Thread.sleep;// } catch (InterruptedException e) {// e.printStackTrace();// }// System.out.println(Thread.currentThread().getName()// + "正在出售第" + (tickets--) + "张票 ");// }// }// }// } @Override public void run() { while  { if{ synchronized (SellTicket.class) {//静态方法的安全锁,若是普通同步方法则为:this if (tickets > 0) { try { Thread.sleep; } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票 "); } } }else {// synchronized  {// if (tickets > 0) {// try {// Thread.sleep;// } catch (InterruptedException e) {// e.printStackTrace();// }// System.out.println(Thread.currentThread().getName()// + "正在出售第" + (tickets--) + "张票 ");// }// } sellTicket(); } x++; } }//同步方法: //如果一个方法一进去就看到了代码被同步了,那么我就再想能不能把这个同步加在方法上呢?// private synchronized void sellTicket() {// if (tickets > 0) {// try {// Thread.sleep;// } catch (InterruptedException e) {// e.printStackTrace();// }// System.out.println(Thread.currentThread().getName()// + "正在出售第" + (tickets--) + "张票 ");// }// }//静态同步方法: private static synchronized void sellTicket() { if (tickets > 0) { try { Thread.sleep; } 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 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();
        }
    }
}

 

常见线程安全集合

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>; // 线程安全 }}

线程的生命周期图

图片 2

多线程的实现方案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 v = new Vector();
        Hashtable h = new Hashtable();

        // Vector是线程安全的时候才去考虑使用的,但是我还说过即使要安全,我也不用你
        // 那么到底用谁呢?
        // public static  List synchronizedList(List list)
        List list1 = new ArrayList();// 线程不安全
        List list2 = Collections
                .synchronizedList(new ArrayList()); // 线程安全
    }
}

 

Java基础学习第二十四天—多线程学习总结 文档版本 开发工具 测试平台
工程名字 日期 作者 备注 V1.0 2016.03.25 lutianfei none 第十章…