Java基础_多线程3[多线程的俩种实现以及线程安全]

多线程的实现方案2

1.第二种方式使用步骤
方式2:实现Runnable接口
步骤:
A:自定义类MyRunnable实现Runnable接口
B:重写run()方法
C:创建MyRunnable类的对象
D:创建Thread类的对象,并把C步骤的对象作为构造参数传递


2.一个基本的代码

(1).多线程类
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).测试类
public class MyRunnableDemo {
public static void main(String[] args) {
// 创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
//把thread类的对象作为参数进行传递
// Thread(Runnable target)
// Thread t1 = new Thread(my);
// Thread t2 = new Thread(my);
// t1.setName("林青霞");
// t2.setName("刘意");

Thread t1 = new Thread(my, "林青霞");
Thread t2 = new Thread(my, "刘意");

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


3.俩种实现多线程方式比较


4.多线程练习
(1).需求:某电影院正在上映贺岁大片,公有1000张票要出售,而又3个售票窗口可以售票,请设计一个模拟程序模拟该电影院售票。

(2).分析:
有1000张票需要从三个窗口同时可以流出,这是一个使用多线程的典型案例。


(3)使用Thread类实现该需求(方式1)
//多线程类
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--) + "张票");
}
}
}
}
//测试类
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();
}
}

(4).使用Runnable接口的方式实现
//多线程类
public class SellTicket implements Runnable {
// 定义100张票,不加static修饰
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) {
// 创建资源对象,这里是创建了一个多线程类的对象,所以可以看出实现runnable接口更适合使用这一种方法
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();
}
}

(5).在上述的(3)方法中出现了相同的数据源(即有一个相同的数据会被使用了俩次,这一个是错误的),在实际生活中,我们买票的时候会存在
一小点的延时(无论网络延时还是系统延时等情况),那么我们在上述代码中加入延时,延时100毫秒
//多线程类
public class SellTicket implements Runnable {
// 定义100张票,不加static修饰
private int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets > 0) {
//抛异常
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (tickets--) + "张票");
}
}
}
}

但是通过加入延时以后,又出现了俩个问题,那就是出现了卖出同一张票多次,还出现了负票。
原因分析:
出现同票:
CPU的操作必须是原子性的(所谓原子性就是最基本,最基础的不做任何的其他操作),但是这里面有tickets--操作,也就是说,这样
不是原子性的操作,由于随机性在没有ticksts--生效之前,另外一个线程就已经获取tickets的内容,这就会导致出现同票。
出现负票:
由于CPU存在着随机性和延迟导致的。


5.线程安全问题
(1).在上述的(4),操作中,出现了问题:同票和负票的问题,这其实就是线程安全的问题。

(2). 如何解决线程安全问题呢?
要想解决问题,就要知道哪些原因会导致出问题:(而且这些原因也是以后我们判断一个程序是否会有线程安全问题的标准)
A:是否是多线程环境(要实现多条路劲畅通)
B:是否有共享数据
C:是否有多条语句操作共享数据(非原子性操作:tickets>0,tickets--,输出tickets)

我们来回想一下我们的程序有没有上面的问题呢?
A:是否是多线程环境 是
B:是否有共享数据 是
C:是否有多条语句操作共享数据 是

由此可见我们的程序出现问题是正常的,因为它满足出问题的条件。
接下来才是我们要想想如何解决问题呢?
A和B的问题我们改变不了,我们只能想办法去把C改变一下。
思想:
把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行。
问题是我们不知道怎么包啊?其实我也不知道,但是Java给我们提供了:同步机制。

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

A:对象是什么呢?
我们可以随便创建一个对象试试。
B:需要同步的代码是哪些呢?
把多条语句操作共享数据的代码的部分给包起来

注意:
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
多个线程必须是同一把锁。就是这一个对象必须要使用同一个。


代码:
//多线程类
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();
}
}



6.同步的好处和弊端

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




7.同步代码块的锁以及同步方法应用和锁的问题

(1):同步代码块的锁对象是谁呢?
任意对象。

(2):同步方法的格式及锁对象问题?
把同步关键字加在方法上。

同步方法的锁对象是谁呢?
this

(3):静态方法及锁对象问题?
静态方法的锁对象是谁呢?
类的字节码文件对象(class文件)。(反射会讲)



(4).代码:
A:同步方法
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;
@Override
public void run() {
while (true) {
if(x%2==0){
synchronized (this) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
}
}
}else {
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--) + "张票 ");
}
}

B:静态方法
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;
@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 synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
}
}


8.线程安全类回顾

(1).所谓的线程安全类,我们查看类里面源码都是线程安全的,但是这样效率是比较低的。


(2).获取线程安全的集合

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


9.总结(多线程的基础)课程总结

1:多线程(理解)
(1)多线程:一个应用程序有多条执行路径
进程:正在执行的应用程序
线程:进程的执行单元,执行路径
单线程:一个应用程序只有一条执行路径
多线程:一个应用程序有多条执行路径

多进程的意义?
提高CPU的使用率
多线程的意义?
提高应用程序的使用率
(2)Java程序的运行原理及JVM的启动是多线程的吗?
A:Java命令去启动JVM,JVM会启动一个进程,该进程会启动一个主线程。
B:JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。
(3)多线程的实现方案(自己补齐步骤及代码 掌握)
A:继承Thread类
B:实现Runnable接口
(4)线程的调度和优先级问题
A:线程的调度
a:分时调度
b:抢占式调度 (Java采用的是该调度方式)
B:获取和设置线程优先级
a:默认是5
b:范围是1-10
(5)线程的控制(常见方法)
A:休眠线程
B:加入线程
C:礼让线程
D:后台线程
E:终止线程(掌握)
(6)线程的生命周期(参照 线程生命周期图解.bmp)
A:新建
B:就绪
C:运行
D:阻塞
E:死亡
(7)电影院卖票程序的实现
A:继承Thread类
B:实现Runnable接口
(8)电影院卖票程序出问题
A:为了更符合真实的场景,加入了休眠100毫秒。
B:卖票问题
a:同票多次
b:负数票
(9)多线程安全问题的原因(也是我们以后判断一个程序是否有线程安全问题的依据)
A:是否有多线程环境
B:是否有共享数据
C:是否有多条语句操作共享数据
(10)同步解决线程安全问题
A:同步代码块
synchronized(对象) {
需要被同步的代码;
}

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

B:同步方法
把同步加在方法上。

这里的锁对象是this

C:静态同步方法
把同步加在方法上。

这里的锁对象是当前类的字节码文件对象(反射再讲字节码文件对象)
(11)回顾以前的线程安全的类
A:StringBuffer
B:Vector
C:Hashtable
D:如何把一个线程不安全的集合类变成一个线程安全的集合类
用Collections工具类的方法即可。

猜你喜欢

转载自www.cnblogs.com/nwxayyf/p/9573190.html
今日推荐