线程的实际应用
需求:某电影院出售某些电影的票(复联3,红高粱....),有三个窗口同时进行售票(100张票),请您设计一个程序,模拟电影院售票
两种方式:
继承
接口
第一种方法中继承类代码展示
public class MyThreadextends Thread {
//最为关键的部分,没有它没法完成票数同步,因为是存放在静态区域,所以改变的时候变化是//同步的
private static int tickets = 100;
@Override
public void run() {
while(true) {
if (tickets>0) {
System.out.println(getName()+":"+tickets--);
}
}
}
}
//最为关键的部分,没有它没法完成票数同步,因为是存放在静态区域
改变的时候变化是同步的,每一个线程使用的是同一个数据
private static int tickets = 100;
第二种方法展示
由于第二方式是使用实现类
public class MyThread2 implements Runnable{
//因为整个类都是作为资源类共享的,所以它被线程调用时,具有共享性
private int tickets = 100;
@Override
public void run() {
while(true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName()+"正在出售的票号"+tickets--);
}
}
}
}
因为整个类都是作为资源类共享的,所以它被线程调用时,具有共享性
线程的声明周期:
线程从开始创建的时候,一直到线程的执行,最终到线程的终止
新建线程:此时的线程没有执行资格,没有执行权
线程就绪:线程有执行资格了,但还没有执行权,在线程执行前,还可能会阻塞
线程执行:线程执行完毕,会被垃圾回收线程中垃圾回收器及时从内存中释放掉
为了模拟更真实的场景,加入延迟操作(让我们线程睡100毫秒)
程序的设计是好的,但是结果有一些问题
1)同一张票被卖了多次
CPU的执行有一个特点(具有原子性操作:最简单最基本的操作)
原子操作性:记录以前的值
例如:窗口一进来了,正当他要输出数据之前,窗口二和窗口三进来了,直接读取的是之前那个tickets的值
2)出现了0或者负票 (这是因为延迟操作和线程的随机性导致)
代码如下:
public void run() {
while(true) {
if (tickets > 0) {
try {
//睡眠一段时间,相当于延时操作
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售的票号"+tickets--);
}
}
}
如何解决线程安全问题
校验一个多线程程序是否有安全问题隐患的前提条件是:
1) 当前程序是否是多线程环境
2) 是否有共享数据
3) 是否有多条语句对共享数据进行操作
当上述条件全部满足的时候存在多线程安全的问题
解决安全问题
1) 多线程环境(不能解决)
2) 对共享数据进行优化(不能解决)
3) 解决将多条语句对共享数据这一环进行解决
解决方案:将多条语句堆共享数据操作的代码.用一个代码包以来---->代码--->同步代码块
格式:
Synchronized(锁对象){
针对多条语句对共享数据操作代码
}
锁对象:肯定一个对象,随便创建一个对象(匿名对象)
给刚才的这个程序加入了同步代码块,但是锁对象使用的匿名对象(每一个线程进来都有自己的锁),还是没有解决!
锁对象:每一个线程最总使用的锁对象,只能是同一把锁
注意:使用匿名对象作为锁对象的时候要慎重,因为不同线程会创建不同的锁,这样就没办法保重它们是同步的了
public class MyThread3 implements Runnable{
private int tickets = 100;
//创建锁对象
Object obj = new Object();
@Override
public void run() {
while(true) {
//设置锁
synchronized(obj) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售的票号"+(tickets--));
}
}
}
}
如果一个方法一进来就是同步代码块,那么可不可以将同步放到方法来进行声明呢? 可以
非静态的方法:同步方法(需要底层源码,一些方法会声明synchronized)的锁对象:this同步方法 :里面锁对象是谁? this
静态的同步方法:和反射有关 (静态同步方法的锁对象:类名.class)
Lock锁
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构
可以使用Lock锁进行具体的锁定操作类 提供了具体的实现类:ReentrantLock
加锁并且去释放锁
Jdk5.0以后,java提供了一个具体的锁: 接口:Lock
注意 :记得要创建锁的实现类对象,或者使用多态创建锁
获取锁操作后最后要解锁
public void run() {
while (true) {
try {
// 获取锁
rl.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售的票号" + (tickets--));
}
} finally {
// 释放锁
rl.unlock();
}
}
}
线程的一些其他问题
解决了多线程安全问题,但是还是有些问题:
1)执行效率低
2)会产生死锁
两个或两个以上的线程,在执行的过程中出现互相等待的情况,就叫做死锁!
死锁类代码如下
public class MyLock {
//两个锁对象分别是objA 和objB
public static final Object objA = new Object() ;
public static final Object objB = new Object() ;
}
线程类代码如下
public void run() {
if(flag) {
synchronized (MyLock.objA) {
System.out.println("if ObjA");
synchronized (MyLock.objB) {
System.out.println("if objB");
}
}
}else {
synchronized (MyLock.objB) {
System.out.println("else objB");
synchronized (MyLock.objA) {
System.out.println("else objA");
}
}
}
}
测试类代码如下
public class DieLockDemo {
public static void main(String[] args) {
//创建线程了对象
DieLock dl1 = new DieLock(true) ;
DieLock dl2 = new DieLock(false) ;
//启动线程
dl1.start();
dl2.start();
}
}
结果可能出现三种情况
第一种情况:
if ObjA
else objB
//这种情况是因为第一个线程先抢到执行权,但是再第二个线程执行的时候对应的锁没有执行完毕,就没有释放出来,故而第二个线程中的无法用A锁,而第二个线程中B锁使用了因为程序没有执行完毕,故而无法释放B锁,而第一个线程需要B锁,这样就造成了死锁
第二种情况
else objB
if ObjA
第三种情况:
理想状态
else objB
else objA
if ObjA
if objB
if ObjA
if objB
else objB
else objA
生产 消费 者模式简单设计
Student类: 资源类
SetThread:设置学生的数据(生产者线程)
GetThread:获取(输出)学生数据(消费者线程)
StudentDemo:测试类
需求:SetThread线程给学生对象进行赋值,在通过消费者线程输出该学生数据,设计这样一个程序!
null----0按照刚才的思路,发现有一个问题,的数据null---0
解决方案:线程死锁的注意事项:要保证生产者线程和消费者线程针对同一个对象进行操作的!
在外部创建一个学生对象,将这个学生对象通过构造方法传入到各个线程中
需求:消费者线程,和生产者线程加入循环操作,改进
注意:必须对同一个对象进行操作,不然很容易无效
public class SetStudent implements Runnable {
private Student s ;
public SetStudent(Student s) {
this.s = s;
}
@Override
public void run() {
//Student s= new Student();
s.age = 17;
s.name = "小智";
}
}public class GetStuent implements Runnable{
private Student s;
public GetStuent(Student s) {
this.s= s;
}
@Override
public void run() {
//Student s= new Student();
System.out.println(s.name+"---"+s.age);
}
}
public static void main(String[] args) {
//第一次尝试由于生产者和消费的学生类对象不一致,就导致生产出来的东西没有消费者,消费者则没有东西可以消费
//第二次尝试直接设置Student类型的数据,作为两者的公用对象
Student s = new Student();
//第二次由于是使用的同一个对象,故而生产的产生的东西消费者能用上
SetStudent st = new SetStudent(s);
GetStuent gs = new GetStuent(s);
Thread t1 = new Thread(st, "生产者");
Thread t2 = new Thread(gs, "消费者");
t1.start();
t2.start();
}
}
注意:因为要使用对一个对象,那么只能通过测试类通过参数的方式公共赋值,且为了方便,参数直接在构造方法上就可以调用
继续上面的例子,又加入新的需求:生产者和消费者线程加入循环:
又有问题:
1)同一个人的姓名和年龄出现多次
2)姓名和年龄不符
为什么?
1)CPU的一点点时间片,在某一个时间点,足够它执行很多次
2)线程具有随机性
解决方案:
1)是否是多线程环境是
2)是否有功共享数据是
3)是否有多条语句对共享数据进行操作 有
同步机制(同步代码块/同步方法)
开发中,使用synchronized(Lock锁也可以)同步代码块将多条语句对共享数据的操作包起来!
对于上面的代码改动的是生产者代码和消费者代码(加入了同步锁),具体代码如下
package signclass2;
public class Set implements Runnable{
private Student s ;
public Set(Student s) {
this.s = s;
}
//设置一个值可以形成循环
private int num = 0;
@Override
public void run() {
while(true) {
synchronized(s) {
if (num%2 == 0 ) {
s.name = "小智";
s.age = 17 ;
}
else {
s.name = "小黄毛";
s.age = 20;
}
num++;
}
}
}
}
package signclass2;
public class Get implements Runnable{
private Student s ;
public Get(Student s) {
this.s = s;
}
@Override
public void run() {
//循环读取
while(true) {
synchronized(s) {
System.out.println(s.name+"----"+s.age);
}
}
}
}
继续改进:
上面的代码改进之后,虽然加入了同步机制,但是打印一打印一大片同样,让数据依次打印数据!
如何解决:
就使用的是Java的等待唤醒机制
上面程序可能会出现的问题:
1) 如果Get的消费者线程先抢占到CPU的执行权,意味着会执行默认值,对于打印默认值这块要进行优化
2) 如果是SetThread的生产者线程先抢占到了CPU执行权,生成一些学生数据,还具有执行权,又需要不断地产生数据,还是有问题的,需要等待消费者线程先获取这些数据,再进行生产
解决思路
1) 生产和线程抢占到执行权,如果本身有数据线等待消费者消费,没有数据,先产生数据
2) 消费者先抢占到执行权,需要生产者生产数据,再消费
这里面有时候回产生死锁,若是生产者有没有数据,等待生产,消费者有数据,等待消费
相互等待,产生死锁
wait()和sleep(long times) 的区别?
wait和sleep的区别是,wait释放锁,而sleep不释放锁
面试题:
wait(),notify(),notifyAll() 这些方法为什么会定义在Object类中呢?
这些方法好像就属于线程的方法,但是Thread类中并没有这些方法,多线程中同步锁对象:任意的Java类
这些方法都和锁对象有关系,所以定义在Object类
线程组:
程组表示一个线程的集合。此外,线程组也可以包含其他线程组
public ThreadGroup(String name)构造一个新线程组
Thread(ThreadGroup group, Runnable target, String name)
//分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,并作为 group 所引用的线程组的一员。
线程池:
多个线程执行完毕,它会重新回到线程池中,等待被利用,不会变成垃圾!
和线程池有关的类
类 Executors: 一种工厂类
方法:
和线程池的创建有关系
public static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池
ExecutorService:可以执行异步任务
创建一个线程池,执行接口中的方法
提交:Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)提交一个返回值的任务用于执行,会执行call()方法 返回一个表示任务的未决结果的 Future
Future:接口
Future 表示异步计算的结果
线程池调用完毕可以关闭的
void shutdown():关闭之前,会提交刚才的任务
package lager;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class xianchengpool {
public static void main(String[] args) {
//创建能容纳两个线程的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
//提交,
es.submit(new MyThread());
es.submit(new MyThread());
//关闭线程池
es.shutdown();
}
}
多线程实现方式第三种:
前提:自定义类实现Callable接口
1)创建线程池对象: Executors 里面的那个方法,返回的是ExecutorsService
2) 然后调用ExecutorsService里面的提交任务的方法:
<T> Future<T> submit(Callable<T> task)提交一个返回值的任务用于执行
3)关闭线程池
(代码如下)
package org.westos_14;
import java.util.concurrent.Callable;
//Callable的泛型的类型它是call方法的返回值类型
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
for(int x = 0 ; x < 100 ; x ++) {
System.out.println(Thread.currentThread().getName()+":"+x);
}
return null;
}
}
注意:Callable的泛型的类型它是call方法的返回值类型
package lager;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CallTest {
public static void main(String[] args) {
//第三种方式前提已经有了,已经实现了callable的接口
//1.创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
//2调用提交方法
pool.submit(new Call());
pool.submit(new Call());
//关闭线程池
pool.shutdown();
}
}
package lager;
import java.util.concurrent.Callable;
public class Call implements Callable {
@Override
public Object call() throws Exception {
for(int x = 0 ; x < 100 ; x ++) {
System.out.println(Thread.currentThread().getName()+":"+x);
}
return null;
}
package lager;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CallTest {
public static void main(String[] args) {
//第三种方式前提已经有了,已经实现了callable的接口
//1.创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
//2调用提交方法
pool.submit(new Call());
pool.submit(new Call());
//关闭线程池
pool.shutdown();
}
}
package lager;
import java.util.concurrent.Callable;
public class Call implements Callable {
@Override
public Object call() throws Exception {
for(int x = 0 ; x < 100 ; x ++) {
System.out.println(Thread.currentThread().getName()+":"+x);
}
return null;
}