1.多线程程序的引入
红色代表一个执行的流程,线程
2.进程概述及多进程的意义
A:要想了解多线程,必须先了解线程,而要想了解线程,必须先了解进程,因为线程是依赖于进程而存在。
B:什么是进程?
通过任务管理器我们就看到了进程的存在。
而通过观察,我们发现只有运行的程序才会出现进程。
进程:就是正在运行的程序。
进程是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
C:多进程有什么意义呢?
单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。
举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)。
也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务。
并且呢,可以提高CPU的使用率。
问题:
一边玩游戏,一边听音乐是同时进行的吗?
不是。因为单CPU在某一个时间点上只能做一件事情。
而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。
================================
单进程:例如windows下cmd的dos命令
多进程:例如我们的计算机
================================
3.线程概述及多线程的意义
A:什么是线程呢?
在同一个进程内又可以执行多个任务,而这每一个任务我就可以看出是一个线程。
线程:是程序的执行单元,执行路径。是程序使用CPU的最基本单位。
单线程:如果程序只有一条执行路径。
多线程:如果程序有多条执行路径。
B:多线程有什么意义呢?
多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率。
程序的执行其实都是在抢CPU的资源,CPU的执行权。
多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权。
我们是不敢保证哪一个线程能够在哪个时刻抢到,所以线程的执行有随机性。
4.多线程举例及并行和并发的区别
进程:
正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
线程:
是进程中的单个顺序控制流,是一条执行路径
一个进程如果只有一条执行路径,则称为单线程程序。
一个进程如果有多条执行路径,则称为多线程程序。
举例:
扫雷程序,迅雷下载
大家注意两个词汇的区别:并行和并发。
前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。
后者是物理上同时发生,指在某一个时间点同时运行多个程序。
5.Java程序运行原理和JVM的启动是否是多线程的
Java程序的运行原理:
由java命令启动JVM,JVM启动就相当于启动了一个进程。
接着有该进程创建了一个主线程去调用main方法。
思考题:
jvm虚拟机的启动是单线程的还是多线程的?
多线程的。
原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出。
现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。
JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
6.多线程方式1的代码实现
需求:我们要实现多线程的程序。
如何实现呢?
由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。
而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。
Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。
但是呢?Java可以去调用C/C++写好的程序来实现多线程程序。
由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,
然后提供一些类供我们使用。我们就可以实现多线程程序了。
那么Java提供的类是什么呢?
Thread
通过查看API,我们知道了有2中方式实现多线程程序。
方式1:继承Thread类。
步骤
A:自定义类MyThread继承Thread类。
B:MyThread类里面重写run()?
为什么是run()方法呢?
C:创建对象
D:启动线程
==============================================================
以下为注意事项
直接调用run()方法并没有启动一个新的线程,只是执行了这个方法的内容
my.run();
my.run();
调用run()方法为什么是单线程的呢?
因为run()方法直接调用其实就相当于普通的方法调用,所以你看到的是单线程的效果
要想看到多线程的效果,就必须说说另一个方法:start()
要想看到多线程的效果,必须调用start()方法
以下为错误示例
IllegalThreadStateException:非法的线程状态异常
为什么呢?因为这个相当于是my线程被调用了两次。而不是两个线程启动。
面试题:run()和start()的区别?
run():仅仅是封装被线程执行的代码,直接调用是普通方法
start():首先启动了线程,然后再由jvm去调用该线程的run()方法。
最终版代码
MyThread类(继承父类Thread)
/*
* 该类要重写run()方法,为什么呢?
* 不是类中的所有代码都需要被线程执行的。
* 而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。
*/
public class MyThread extends Thread {
@Override
public void run() {
// 自己写代码
// System.out.println("好好学习,天天向上");//一般不会那么无聊为了输出一句话而重新开一个线程(浪费)
// 一般来说,被线程执行的代码肯定是比较耗时的。所以我们用循环改进
for (int x = 0; x < 200; x++) {//不一定是200.可能需要更大才能看到效果
System.out.println(x);
}
}
}
============================================
测试类
public class MyThreadDemo {
public static void main(String[] args) {
// 创建两个线程对象
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
my1.start();
my2.start();
}
===================================================
运行示例(x<900)
可以看到两个0~899的输出过程互相抢夺资源,并不是输出0~899了再输出0~899的
7.获取和设置线程对象名称
如何获取线程对象的名称呢?
public final String getName():获取线程的名称。
如何设置线程对象的名称呢?
public final void setName(String name):设置线程的名称
设置线程的名称(如果不设置名称的话,默认是Thread-? 编号)
在测试类中:
方法一:无参构造+setXxx()
// 创建线程对象
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
//调用方法设置名称
my1.setName("林青霞");
my2.setName("刘意");
my1.start();
my2.start();
前提是MyThread类要改一下run()方法(getName()就是用来获取线程名字的)
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
方法二(稍微麻烦,要手动写MyThread的带参构造方法,方法一不用):
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);//红字相当于this.getName()
}
}
}
====================================
然后在测试类中:
//带参构造方法给线程起名字
MyThread my1 = new MyThread("林青霞");
MyThread my2 = new MyThread("刘意");
my1.start();
my2.start();
=====================================
还有一个问题:我要获取main方法所在的线程对象的名称,该怎么办呢?
这时候注意:main方法所在的测试类并不继承Thread类,因此并不能直接使用getName()方法来获取名称。
//遇到这种情况,Thread类提供了一个很好玩的方法:
//public static Thread currentThread():返回当前正在执行的线程对象,返回值是Thread,而Thread恰巧可以调用getName()方法
System.out.println(Thread.currentThread().getName());
运行输出main
8.线程调度及获取和设置线程优先级
线程调度
==========================================================
我们的线程没有设置优先级,肯定有默认优先级。
那么,默认优先级是多少呢?
如何获取线程对象的优先级?
public final int getPriority():返回线程对象的优先级
如何设置线程对象的优先级呢?
public final void setPriority(int newPriority):更改线程的优先级。
注意:
线程默认优先级是5。
线程优先级的范围是:1-10。
线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。
IllegalArgumentException:非法参数异常。
抛出的异常表明向方法传递了一个不合法或不正确的参数。
==========================================================
线程优先级:最大为10,最小为1,默认为5
public static void main(String[] args) {
ThreadPriority tp1 = new ThreadPriority();
ThreadPriority tp2 = new ThreadPriority();
ThreadPriority tp3 = new ThreadPriority();
tp1.setName("东方不败");
tp2.setName("岳不群");
tp3.setName("林平之");
//设置正确的线程优先级
tp1.setPriority(10);
tp2.setPriority(1);
tp1.start();
tp2.start();
tp3.start();
}
9.线程控制之休眠线程
线程休眠(静态方法)
public static void sleep(long millis)
======================================
代码
主要在ThreadSleep中改动
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 {//只能用try catch,不能throws,因为父类没抛这个异常,子类也不能抛
Thread.sleep(1000);//其实在这里也可以直接写sleep(1000);而省略Thread
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
==============================
再写一个类测试一下
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();
}
10.线程控制之加入线程
join()方法(非静态方法)
public final void join():等待该线程终止。
=================================
代码
ThreadJoin类
public class ThreadJoin extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
===============================
以下为测试类
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();
}
}
==========================================
以下是本人写的测试类以及运行情况
11.线程控制之礼让线程
yield()----静态方法
public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
让多个线程的执行更和谐,但是不能靠它保证一人一次。(理论上是一人执行一次,轮流来,也就是一个线程执行一次后,等待下一个线程执行,然后第一个线程再执行第二次,……实际上可能有"误差")
代码
ThreadYield类
public class ThreadYield extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
Thread.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();
}
}
=========================================
可以看见上面 刘意进程与林青霞进程相互交替进行,但不保证一定这样。
12.线程控制之守护线程
setDaemon---非静态
public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。
当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。
ThreadDaemon类
public class ThreadDaemon extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
==================================
测试类
public class ThreadDaemonDemo {
public static void main(String[] args) {
ThreadDaemon td1 = new ThreadDaemon();
ThreadDaemon td2 = new ThreadDaemon();
td1.setName("关羽");
td2.setName("张飞");
// 设置守护线程(注意:必须在start()方法之前设置,否则会有异常!!)
td1.setDaemon(true);//true代表线程设置为"守护"
td2.setDaemon(true);//设置后,当主线程执行结束(主线程名字为刘备),td1,td2线程也结束了(守护任务已完成)
td1.start();
td2.start();
Thread.currentThread().setName("刘备");//改一改main线程的名字
for (int x = 0; x < 5; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
================================================
13.线程控制之中断线程
public final void stop():让线程停止,过时了,但是还可以使用。(不建议使用,太暴力)
public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。
ThreadStop 类
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 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();
}
}
}
================================================
几个问题:
14.线程生命周期图解
15.多线程方式2的思路及代码实现
方式2:实现Runnable接口
步骤:
A:自定义类MyRunnable实现Runnable接口
B:重写run()方法
C:创建MyRunnable类的对象
D:创建Thread类的对象,并把C步骤的对象作为构造参数传递
MyRunnable 类
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
// 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
测试类
public class MyRunnableDemo {
public static void main(String[] args) {
// 创建MyRunnable类的对象
MyRunnable my = new MyRunnable();//多个相同程序的代码去处理同一个资源
//注意:MyRunnable对象只需要创建一个即可,多个Thread对象可以接收同一个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, "林青霞");//同一个对象my,而不需要my1,my2,……
Thread t2 = new Thread(my, "刘意");//同一个对象my 而不需要my1,my2,……
t1.start();
t2.start();
}
}
//注意红字
=============================================================
16.多线程两种方式的图解比较及区别
如何理解------可以避免由于Java单继承带来的局限性
比如说,某个类已经有父类了,而这个类想实现多线程,但是这个时候它已经不能直接继承Thread类了(接口可以多实现implements,但是继承extends只能单继承),它的父类也不想继承Thread因为不需要实现多线程。
17.两种方式实现卖电影票案例
方式一:继承Thread类的方式卖电影票案例
某电影院目前正在上映贺岁大片(红高粱,少林寺传奇藏经阁),共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
继承Thread类来实现。
=====================================
SellTicket类(注意tickets变量是static的!)
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--) + "张票");
}
}
}
}
===========================================================
测试类SellTicketDemo
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();
}
}
============================================================
说明的是,通过继承Thread类来实现题中的需求并不是很好(tickets要用static修饰,并不太好),其实用Runnable接口更好地进行数据分离
以下为方式2---实现Runnable接口的方式卖电影票案例
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--) + "张票");
}
}
}
}
=================================================
测试类
/*
* 实现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();
}
}
=====================================================
18.买电影票出现了同票和负数票的原因分析
首先在SellTicket类中添加sleep方法,延迟一下线程,拖慢一下执行的速度
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
降低速度之后出现了问题
通过加入延迟后,就产生了连个问题:
A:相同的票卖了多次
CPU的一次操作必须是原子性的(在读取tickets--的原来的数值和减1之后的中间挤进了两个线程而出现重复)
B:出现了负数票
随机性和延迟导致的(三个线程同时挤进一个循环里,tickets--的减法操作有可能在同一个循环中被执行了多次而出现越界的情况,比如说tickets要大于0却越界到了-1)
也就是说,线程1执行的同时线程2也可能在执行,而不是线程1执行的时候线程2不能执行。
19.线程安全问题的产生原因分析
如何解决线程安全问题呢?
要想解决问题,就要知道哪些原因会导致出问题:(而且这些原因也是以后我们判断一个程序是否会有线程安全问题的标准)
A:是否是多线程环境
B:是否有共享数据
C:是否有多条语句操作共享数据
我们来回想一下我们的程序有没有上面的问题呢?
A:是否是多线程环境 是
B:是否有共享数据 是
C:是否有多条语句操作共享数据 是
由此可见我们的程序出现问题是正常的,因为它满足出问题的条件。
20.同步代码块的方式解决线程安全问题
接下来才是我们要想想如何解决问题呢?
A和B的问题我们改变不了,我们只能想办法去把C改变一下。
思想:
把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行。
问题是我们不知道怎么包啊?其实我也不知道,但是Java给我们提供了:同步机制。
同步代码块:
synchronized(对象){
需要同步的代码;
}
A:对象是什么呢?
我们可以随便创建一个对象试试。
B:需要同步的代码是哪些呢?
把多条语句操作共享数据的代码的部分给包起来
注意:
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
多个线程必须是同一把锁。
在SellTicket类中改写如下
public class SellTicket implements Runnable {
// 定义100张票
private int tickets = 100;
//创建锁对象
private Object obj = new Object();//把这个关键的锁对象定义到run方法(独立于线程之外)之外。造成同一把锁
// @Override
// public void run() {
// while (true) {
// synchronized(new Object()){//这里用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) {//obj相当于一把锁,把synchroned包含的代码锁住
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票");
}
}
}
}
}
=====================================================
再运行就没有问题了
21.同步代码块解决线程安全问题的解释
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就出来可,然后就开门。(开)(门开后,三个线程又开始抢占)
}
}
}
22.同步的特点及好处和弊端
举例:
火车上厕所。
同步的特点:
前提:
多个线程
解决问题的时候要注意:
多个线程使用的是同一个锁对象
同步的好处
同步的出现解决了多线程的安全问题。
同步的弊端
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
23.同步代码快的锁及同步方法应用和锁的问题
概述:
A:同步代码块的锁对象是谁呢?
任意对象。
B:同步方法的格式及锁对象问题?
把同步关键字加在方法上。
同步方法的锁对象是谁呢?
this
C:静态方法及锁对象问题?
静态方法的锁对象是谁呢?
类的字节码文件对象。(反射会讲)
========================================================
分部解析(测试类都一样,下面是SellTicket类的代码变化)
同步代码块的锁对象是任意对象。
public class SellTicket implements Runnable {
// 定义100张票
private int tickets = 100;
// 定义同一把锁
private Object obj = new Object();
private Demo d = new Demo();//任意对象
//同步代码块用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--) + "张票 ");
}
}
}
}
class Demo {
}
=============================================================
同步方法的格式及锁对象问题?
把同步关键字加在方法上。( sellTicket()方法)
同步方法的锁对象是---------------------- this
// 定义100张票
private int tickets = 100;
// 定义同一把锁
private Object obj = new Object();
private int x = 0;
@Override
public void run() {
while (true) {
if(x%2==0){
synchronized (this) {//注意,这里必须用this对象而不能用其它锁对象,否则同步功能会失败
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
}
}
}else {
sellTicket();//用方法代替
}
x++;
}
}
private void sellTicket() {
synchronized (d) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
}
}
}
=================================================================
静态方法及锁对象问题?
静态方法的锁对象是谁呢?
类的字节码文件对象。(Xxx.class文件)
public class SellTicket implements Runnable {
// 定义100张票
private static int tickets = 100;//使用了静态方法,所以变量也要改为static
// 定义同一把锁
private Object obj = new Object();
private int x = 0;
@Override
public void run() {
while (true) {
if(x%2==0){
synchronized (SellTicket.class) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
}
}
}else {
sellTicket();
}
x++;
}
}
}
private static synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
}
}
回顾以前线程安全的类
// 线程安全的类,效率低
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>()); // 线程安全
Collections.synchronizedList(接收List对象)
示例
day23笔记补充
Java程序的运行原理及JVM的启动是多线程的吗?
A:Java命令去启动JVM,JVM会启动一个进程,该进程会启动一个主线程。
B:JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。
多线程的实现方案(掌握)
A:继承Thread类
B:实现Runnable接口
以前的线程安全的类
A:StringBuffer
B:Vector
C:Hashtable
D:如何把一个线程不安全的集合类变成一个线程安全的集合类
用Collections工具类的方法即可。