线程(下)

线程的实际应用

需求:某电影院出售某些电影的票(复联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;

}

猜你喜欢

转载自blog.csdn.net/qq_35501660/article/details/80530505