javaSE第二十三天

第二十三天    338

1.进程和线程的概述    338

2.多线程(理解)    339

(1)多线程:一个应用程序有多条执行路径    339

(2)Java程序的运行原理及JVM的启动是多线程的吗?    339

C:多线程代码引入    339

(3)多线程的实现方案(自己补齐步骤及代码    掌握)    341

A:继承Thread    341

1.继承了Thread类的实现类MyThread.java    341

2.测试类MyThreadDmeo.java    341

B:实现Runnable接口    342

C:多线程实现的两种方式比较    343

(4)线程名称的设置    344

A:具体设置线程对象名称的案例    344

(5)线程的调度和优先级问题    346

A:线程的调度    346

B:获取和设置线程优先级    346

1.ThreadPriority类    346

2.ThreadPriorityDemo测试类    346

(6)线程的控制(常见方法)    347

A:休眠线程    347

1.ThreadSleep类    347

2.ThreadSleepDemo测试类    348

B:加入线程    348

1.ThreadJoin类    348

2.ThreadJoinDemo测试类    349

C:礼让线程    349

1.ThreadYield类    350

2.ThreadYieldDemo测试类    350

D:后台线程    350

1. ThreadDaemon类    350

2. ThreadDaemonDemo测试类    351

E:终止线程(掌握)    351

1.ThreadStop类    352

2.ThreadStopDemo测试类    352

(7)线程的生命周期(参照    线程生命周期图解.bmp)    353

(8)电影院卖票程序的实现    353

A:继承Thread    353

1.SellTicket类(该程序存在线程安全问题,非最终代码,后续改进)    353

2.SellTicketDemo测试类(该程序存在线程安全问题,非最终代码,后续改进)    354

B:实现Runnable接口    355

1.SellTicket类(该程序存在线程安全问题,非最终代码,后续改进)    355

2.SellTicketDemo测试类(该程序存在线程安全问题,非最终代码,后续改进)    355

(9)电影院卖票程序出问题    356

1. SellTicket类    356

2. SellTicketDemo测试类    357

(10)多线程安全问题的原因(也是我们以后判断一个程序是否有线程安全问题的依据)    358

售票程序最终代码体现    358

(11)通过售票程序解析同步机制和其优缺点    360

1. SellTicket类    360

2. SellTicketDemo测试类    361

(12)同步解决线程安全问题    362

(13)回顾以前的线程安全的类    362

 

第二十三天

1.进程和线程的概述

/*

1:要想了解多线程,必须先了解线程,而要想了解线程,必须先了解进程,因为线程是依赖于进程而存在。

 

2:什么是进程?

    通过任务管理器我们就看到了进程的存在。

    而通过观察,我们发现只有运行的程序才会出现进程。

    进程:就是正在运行的程序。

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

    

3:多进程有什么意义呢?

    单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。

    举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)

    也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务。

    并且呢,可以提高CPU的使用率。

    

    问题:

        一边玩游戏,一边听音乐是同时进行的吗?

        不是。因为单CPU在某一个时间点上只能做一件事情。

        而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。

        

4:什么是线程呢?

    在同一个进程内又可以执行多个任务,而这每一个任务我就可以看出是一个线程。

    线程:是程序的执行单元,执行路径。是程序使用CPU的最基本单位。

    单线程:如果程序只有一条执行路径。

    多线程:如果程序有多条执行路径。

    

5:多线程有什么意义呢?

    多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率。

    程序的执行其实都是在抢CPU的资源,CPU的执行权。

    多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权。

    我们是不敢保证哪一个线程能够在哪个时刻抢到,所以线程的执行有随机性。

*/

 

2.多线程(理解)
    (1)多线程:一个应用程序有多条执行路径

        进程:正在执行的应用程序

        线程:进程的执行单元,执行路径

        单线程:一个应用程序只有一条执行路径

        多线程:一个应用程序有多条执行路径

        

        多进程的意义?

            提高CPU的使用率

        多线程的意义?

            提高应用程序的使用率

    (2)Java程序的运行原理及JVM的启动是多线程的吗?

        A:Java命令去启动JVMJVM会启动一个进程,该进程会启动一个主线程。

        B:JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。

        C:多线程代码引入

/*

*    进程:

*        正在运行的程序,是系统进行资源分配和调用的独立单位。

*        每一个进程都有它自己的内存空间和系统资源。

*    线程:

*        是进程中的单个顺序控制流,是一条执行路径

*        一个进程如果只有一条执行路径,则称为单线程程序。

*        一个进程如果有多条执行路径,则称为多线程程序。

*

* 举例:

*     扫雷程序,迅雷下载

*

* 大家注意两个词汇的区别:并行和并发。

*        前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。

*        后者是物理上同时发生,指在某一个时间点同时运行多个程序。

*

* Java程序的运行原理:

*         java命令启动JVMJVM启动就相当于启动了一个进程。

*         接着有该进程创建了一个主线程去调用main方法。

*

* 思考题:

*         jvm虚拟机的启动是单线程的还是多线程的?

*             多线程的。

*             原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出。

*             现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。

*/

class MyThreadDemo {

    public static void main(String[] args) {

        System.out.println("hello");

        new Object();

        new Object();

        new Object();

        new Object();

        //...

        System.out.println("world");

    }

}

 

    (3)多线程的实现方案(自己补齐步骤及代码    掌握)

        

        A:继承Thread

            1.继承了Thread类的实现类MyThread.java

/*

* 该类要重写run()方法,为什么呢?

* 不是类中的所有代码都需要被线程执行的。

* 而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。

*/

class MyThread extends Thread {

 

    @Override

    public void run() {

        // 自己写代码

        // System.out.println("好好学习,天天向上");

        // 一般来说,被线程执行的代码肯定是比较耗时的。所以我们用循环改进

        for (int x = 0; x < 200; x++) {

            System.out.println(x);

        }

    }

 

}

 

            2.测试类MyThreadDmeo.java

/*

* 需求:我们要实现多线程的程序。

* 如何实现呢?

*         由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。

*         而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。

*         Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。

*         但是呢?Java可以去调用C/C++写好的程序来实现多线程程序。

*         C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,

*         然后提供一些类供我们使用。我们就可以实现多线程程序了。

* 那么Java提供的类是什么呢?

*         Thread

*         通过查看API,我们知道了有2中方式实现多线程程序。

*

* 方式1:继承Thread类。

* 步骤

*         A:自定义类MyThread继承Thread类。

*         B:MyThread类里面重写run()?

*             为什么是run()方法呢?

*         C:创建对象

*         D:启动线程

*/

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

    }

}

 

 

 

 

        B:实现Runnable接口

            1.实现了Runnable接口的实现类MyRunnable.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.测试类MyRunnableDemo

 

/*

* 方式2:实现Runnable接口

* 步骤:

*         A:自定义类MyRunnable实现Runnable接口

*         B:重写run()方法

*         C:创建MyRunnable类的对象

*         D:创建Thread类的对象,并把C步骤的对象作为构造参数传递

*/

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

    }

}

 

        C:多线程实现的两种方式比较

 

    (4)线程名称的设置

        public final String getName()获取线程的名称

        public final synchronized void setName(String name):设置线程的名称

        还可以通过带参构造方法给线程设置名称new MyThread("张三");

 

        

        针对不是Thread类的子类如何获取线程对象的名称呢?

        public static native Thread currentThread()返回当前正在执行的线程对象

        具体代码Thread.currentThread().getName():获取当前线程对象的名称

        

        A:具体设置线程对象名称的案例

            1.MyThread.java类,继承了Thread

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

        }

    }

}

 

 

            2.MyThreadDemo.java 测试类

 

/*

* 如何获取线程对象的名称呢?

* public final String getName():获取线程的名称。

* 如何设置线程对象的名称呢?

* public final void setName(String name):设置线程的名称

* 还可以通过带参构造方法给线程设置名称new MyThread("张三");

*

* 针对不是Thread类的子类中如何获取线程对象名称呢?

* public static Thread currentThread():返回当前正在执行的线程对象

* Thread.currentThread().getName()

*/

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

    }

}

 

    (5)线程的调度和优先级问题

        A:线程的调度

            a:分时调度

            b:抢占式调度 (Java采用的是该调度方式)

        B:获取和设置线程优先级

            a:默认是优先级为5

            b:优先级范围是1-10

            public final int getPriority()返回线程对象的优先级

            public final void setPriority(int newPriority)设置线程对象的优先级

 

            具体案例:

            

            1.ThreadPriority

public class ThreadPriority extends Thread {

    @Override

    public void run() {

        for (int x = 0; x < 100; x++) {

            //获取当前线程的名称并打印数字

            System.out.println(getName() + ":" + x);

        }

    }

}

 

 

            2.ThreadPriorityDemo测试类

/*

* 我们的线程没有设置优先级,肯定有默认优先级。

* 那么,默认优先级是多少呢?

* 如何获取线程对象的优先级?

*         public final int getPriority():返回线程对象的优先级

* 如何设置线程对象的优先级呢?

*         public final void setPriority(int newPriority):更改线程的优先级。

*

* 注意:

*         线程默认优先级是5

*         线程优先级的范围是:1-10

*         线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。

*         

* 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();

    }

}

 

 

    (6)线程的控制(常见方法)

        A:休眠线程

            public static native void sleep(long millis)让线程休眠

            

             具体案例:

            1.ThreadSleep

import java.util.Date;

 

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

            }

        }

    }

}

 

            2.ThreadSleepDemo测试类

 

/*

* 线程休眠

*        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();

    }

}

 

        B:加入线程

            public final void join()等待该线程终止

            

具体案例:

1.ThreadJoin

public class ThreadJoin extends Thread {

    @Override

    public void run() {

        for (int x = 0; x < 100; x++) {

            System.out.println(getName() + ":" + x);

        }

    }

}

 

 

2.ThreadJoinDemo测试类

/*

* 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();

    }

}

 

        C:礼让线程

            public static native void yield()暂停当前正在执行的线程对象,并执行

                                                        其他线程

            目的:让多个线程的执行顺序更和谐一点,但是也不能保证一个线程执行一次

            

            具体案例

            1.ThreadYield

public class ThreadYield extends Thread {

    @Override

    public void run() {

        for (int x = 0; x < 100; x++) {

            System.out.println(getName() + ":" + x);

            //线程礼让

            Thread.yield();

        }

    }

}

 

            2.ThreadYieldDemo测试类

/*

* 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();

    }

}

 

 

        D:后台线程

            public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程

            当正在运行的线程都是守护线程时,java虚拟机退出,该方法必须在启动线程前调用

            

            最好的案例说明:坦克大战,老板死了,小弟就没有存在的必要了

            

            1. ThreadDaemon

                

public class ThreadDaemon extends Thread {

    @Override

    public void run() {

        for (int x = 0; x < 100; x++) {

            System.out.println(getName() + ":" + x);

        }

    }

}

 

 

 

            2. ThreadDaemonDemo测试类

/*

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

        }

    }

}

 

 

        E:终止线程(掌握)

            public final void stop()让线程停止,过时了,但是还可以使用

public void interrupt()中断线程,把线程的状态终止,并抛出一个InterruptedException异常

                具体案例

            1.ThreadStop

import java.util.Date;

 

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

    }

}

 

            2.ThreadStopDemo测试类

 

/*

* 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.stop()执行的结果:开始执行:Wed Sep 07 18:23:50 CST 2016

            //线程中断

            ts.interrupt();

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

}

/*

* ts.interrupt(): 执行结果:

* 开始执行:Wed Sep 07 18:21:41 CST 2016

    线程被终止了

    结束执行:Wed Sep 07 18:21:44 CST 2016

*/

 

 

    (7)线程的生命周期(参照    线程生命周期图解.bmp)

        A:新建

        B:就绪

        C:运行

        D:阻塞

        E:死亡

    (8)电影院卖票程序的实现

        A:继承Thread

            1.SellTicket类(该程序存在线程安全问题,非最终代码,后续改进)

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--) + "张票");

            }

        }

    }

}

         2.SellTicketDemo测试类(该程序存在线程安全问题,非最终代码,后续改进)

/*

* 某电影院目前正在上映贺岁大片(红高粱,少林寺传奇藏经阁),共有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();

    }

}

 

 

        B:实现Runnable接口

 

        1.SellTicket类(该程序存在线程安全问题,非最终代码,后续改进)

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--) + "张票");

            }

        }

    }

}

 

 

        2.SellTicketDemo测试类(该程序存在线程安全问题,非最终代码,后续改进)

/*

*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();

    }

}

 

 

    (9)电影院卖票程序出问题

        A:为了更符合真实的场景,加入了休眠100毫秒。

        B:卖票问题

            a:同票多次

            b:负数票

        代码案例体现

 

        1. SellTicket

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

            }

        }

    }

}

 

        2. SellTicketDemo测试类

/*

* 实现Runnable接口的方式实现

*

* 通过加入延迟后,就产生了连个问题:

* A:相同的票卖了多次

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

* 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();

    }

}

 

 

    (10)多线程安全问题的原因(也是我们以后判断一个程序是否有线程安全问题的依据)

        A:是否有多线程环境

        B:是否有共享数据

        C:是否有多条语句操作共享数据

 

     售票程序最终代码体现

        1. SellTicket

package cn.itcast_23;

 

public class SellTicket implements Runnable {

    // 定义100张票

    private int tickets = 100;

    //创建锁对象

    private Object obj = new Object();

 

/*    @Override

    public void run() {

        while (true) {

            synchronized(new Object()){

                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 (obj) {

                if (tickets > 0) {

                    try {

                        Thread.sleep(100);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    System.out.println(Thread.currentThread().getName()

                            + "正在出售第" + (tickets--) + "张票");

                }

            }

        }

    }

}

 

 

        2. SellTicketDemo测试类

/*

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

*

* 要想解决问题,就要知道哪些原因会导致出问题:(而且这些原因也是以后我们判断一个程序是否会有线程安全问题的标准)

* A:是否是多线程环境

* B:是否有共享数据

* C:是否有多条语句操作共享数据

*

* 我们来回想一下我们的程序有没有上面的问题呢?

* A:是否是多线程环境    

* B:是否有共享数据    

* C:是否有多条语句操作共享数据    

*

* 由此可见我们的程序出现问题是正常的,因为它满足出问题的条件。

* 接下来才是我们要想想如何解决问题呢?

* AB的问题我们改变不了,我们只能想办法去把C改变一下。

* 思想:

*         把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行。

* 问题是我们不知道怎么包啊?其实我也不知道,但是Java给我们提供了:同步机制。

*

* 同步代码块:

*         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();

    }

}

 

    (11)通过售票程序解析同步机制和其优缺点

        1. SellTicket

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就出来可,然后就开门。()

        }

    }

}

 

 

        2. SellTicketDemo测试类

/*

* 举例:

*         火车上厕所。

*

* 同步的特点:

*         前提:

*             多个线程

*        解决问题的时候要注意:

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

* 同步的好处

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

* 同步的弊端

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

*/

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

    }

}

 

 

    (12)同步解决线程安全问题

        A:同步代码块

            synchronized(对象) {

                需要被同步的代码;

            }

            

            这里的锁对象可以是任意对象

            

        B:同步方法

            把同步加在方法上。

            

            这里的锁对象是this

            例如private synchronized void sellTicket()

            

        C:静态同步方法

            把同步加在方法上。

            

            这里的锁对象是当前类的字节码文件对象(反射再讲字节码文件对象)

            例如

            

synchronized (SellTicket.class) {

                    if (tickets > 0) {

                        try {

                            Thread.sleep(100);

                        } catch (InterruptedException e) {

                            e.printStackTrace();

                        }

                        System.out.println(Thread.currentThread().getName()

                                + "正在出售第" + (tickets--) + "张票 ");

                    }

 

    (13)回顾以前的线程安全的类

        A:StringBuffer

        B:Vector

        C:Hashtable

        D:如何把一个线程不安全的集合类变成一个线程安全的集合类

            用Collections工具类的方法即可。

 

    具体案例    

import java.util.ArrayList;

import java.util.Collections;

import java.util.Hashtable;

import java.util.List;

import java.util.Vector;

 

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

    }

}

 

 

原文地址:https://www.cnblogs.com/Prozhu/p/5874641.html