您的位置:首页 > 编程语言 > Java开发

【JavaSE学习笔记】多线程02_Lock,死锁,等待唤醒机制,线程组和线程池,Timer定时器

2017-08-19 12:08 459 查看
多线程02

A.Lock

1)概述

上一章中,使用同步机制synchronized解决了线程的安全问题

但我们并没有看到具体的锁对象是谁,JDK5以后java提供了接口Lock

Lock提供了比使用synchronized方法和语句可获得更广泛的锁定操作

由于该Lock接口不能实例化,提供了子实现类:ReentrankLock



2)方法

public void lock():获取锁

public void unlock():释放锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Tickets implements Runnable {
private static int tickets = 100;

private Lock l = new ReentrantLock();

@Override
public void run() {
while (tickets > 0) {
try {
l.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售电影《战狼2》第"
+ (100 - (--tickets)) + "张票!余票" + tickets + "张!");
}
} finally {
l.unlock();
}
}
}
}
测试类

public class TicketsDemo {
public static void main(String[] args) {
Tickets t = new Tickets();

Thread t1 = new Thread(t, "窗口1");
Thread t2 = new Thread(t, "窗口2");
Thread t3 = new Thread(t, "窗口3");

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


B.死锁

1)线程安全的弊端

使用Lock锁定的操作或者是同步锁synchronized来解决线程安全问题

线程安全的弊端:

a.执行效率低了

b.容易产生死锁

2)死锁出现的原因

两个或两个以上的线程,抢占CPU的执行权,然后出现了互相等待的情况

下面写一个死锁程序

a.自定义一个类:提供两把锁对象

public class MyLock {
// 创建两把锁对象
public static final Object objA = new Object();
public static final Object objB = new Object();
}
b.创建一个线程对象

public class DieLock extends Thread {
private boolean flag;

public DieLock(boolean flag) {
super();
this.flag = flag;
}

@Override
public void run() {
if (flag) {
synchronized (MyLock.objA) {
System.out.println("if objA...");
synchronized (MyLock.objB) {
System.out.println("if objB...");
}
}
/**
* if ObjA--->如果想要执行 ifObjB,那么需要else里面的ObjB同步机制执行完毕
*/
} else {
synchronized (MyLock.objB) {
System.out.println("else objB...");
synchronized (MyLock.objA) {
System.out.println("else objA...");
}
}
}
}
}
c.创建测试类

public class DieLockDemo {
public static void main(String[] args) {
DieLock d1 = new DieLock(true);
DieLock d2 = new DieLock(false);

d1.start();
d2.start();
/**
* 第一种: dl1线程先抢占到CPU的执行权
* 			if ObjA
* 				else ObjB
*
* 第二种: dl2线程先抢占到CPU的执行权
* 			else ObjB
* 				if ObjA
*/
}
}



解析:可以看出:连个线程分别抢占两个代码块,并执行完第一步

然后都在等待对方执行完,就出现了死锁状态

换种方式解释:



if 和 else 两个人分别从桥的两端上桥,打算过河

if        objA-----------------objB

else   objB-----------------objA

两人走到中间,if需要等else走完objB,而else又要等if走完objA,两人都在等

就出现了死锁状态

3)死锁解决方法

通过线程的通讯问题上面独木桥问题,两人上桥总有个先后顺序

else发现if先上就等待if走完,else再上去走

线程的通信:两个或者两个以上的线程只能针对同一资源进行操作

4)需求

模拟生产消费关系

分析:当前的资源情况

Dinner:共同资源

public class Dinner {
String food;
String juice;
}


setThread:设置晚餐数据(生产者)

public class SetThread implements Runnable {
private Dinner d;

public SetThread(Dinner d) {
super();
this.d = d;
}

@Override
public void run() {
// Dinner d = new Dinner();

d.food = "盖浇饭";
d.juice = "可乐";
}
}


getThread:获取晚餐数据(消费者)

public class GetThread implements Runnable {

private Dinner d;

public GetThread(Dinner d) {
super();
this.d = d;
}

@Override
public void run() {
// Dinner d = new Dinner();
System.out.println(d.food + "---" + d.juice);
}

}


DinnerDemo:测试类

public class DinnerDemo {
public static void main(String[] args) {
Dinner d = new Dinner();

SetThread st = new SetThread(d);
GetThread gt = new GetThread(d);

Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);

t1.start();
t2.start();
}
}
可以将代码中的注释部分放开,将Set/Get类成员变量和构造方法注释掉

观察结果,会发现打印出来的是null---0(没有传进去)

5)针对上面的需求,改进

给每一个线程加入while循环,打印多个数据

    盖浇饭-----可乐

    盖浇面-----雪碧

SetThread

private int x = 0 ;

@Override
public void run() {
while(true){
synchronized (d) {
if(x%2==0){
d.food = "盖浇饭" ;
d.juice = "可乐" ;
}else{
d.food = "盖浇面" ;
d.juice = "雪碧" ;
}

x ++ ;
}
}

}
GetThread
@Override
public void run() {
while(true){
synchronized(d){
System.out.println(d.food +"---"+ d.juice);
}

}
}


改进之后出现的问题:

a.同一数据打印了多次

b.会出现搭配错误!线程的随机性导致

继续改进:(最终版代码在下一模块C-2))

加如synchronized同步锁:但会出现一大片数据(自己尝试)

我想让两者交替变化的出现(两个线程交替运行)

如何解决?

java提供了另一种机制:等待唤醒机制

C.等待唤醒机制

1)概述

Object类中提供了一些方法

wait():线程等待

public final void notify():唤醒正在等待的单个线程

public final void notifyAll():唤醒所有线程

2)针对上面的案例,加入唤醒等待机制

再做以下改进:

将Dinner类中的成员私有化

在Dinner类中使用同步方法,进行set方法/get()封装起来

在SetThread或者是GetThread只需要调用set()/get()

形成最终版代码

晚餐类

public class Dinner {
private String food;
private String juice;
private boolean flag;// 默认是没有数据的,如果是true,说明有数据

// 生产者
public synchronized void set(String food, String jucie) {
// 使用等待唤醒机制
if (this.flag) {
try {
// 如果有饭,等待消费者来消费
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

// 如果没有饭
// 设置数据(做饭)
this.food = food;
this.juice = jucie;

// 修改标记(饭做好了,该叫消费者取餐)
this.flag = true;
this.notify();
}

// 消费者
public synchronized void get() {
if (!this.flag) {
try {
// 如果没有饭,需要等待生产者生产数据
// 点完餐等待中
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

// 如果有,直接消费
System.out.println(this.food + "---" + this.juice);

// 消费结束,修改标记
// 告诉生产者,吃完了,该做饭了
this.flag = false;
this.notify();
}
}
生产者类

public class SetThread implements Runnable {
private Dinner d;

public SetThread(Dinner d) {
super();
this.d = d;
}

// 定义变量(不同的饭交替)
private int x = 0;

@Override
public void run() {
while (true) {
if (x % 2 == 0) {
d.set("盖浇饭", "可乐");
} else {
d.set("盖浇面", "雪碧");
}
x++;
}
}
}


消费者类

public class GetThread implements Runnable {

private Dinner d;

public GetThread(Dinner d) {
super();
this.d = d;
}

@Override
public void run() {
while (true) {
d.get();
}
}
}


测试类

public class DinnerDemo {
public static void main(String[] args) {
Dinner d = new Dinner();

SetThread st = new SetThread(d);
GetThread gt = new GetThread(d);

Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);

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



3)面试题

这几个方法都是线程有关的方法,为什么把这个方法不在Thread类里面?

刚才这个案例,使用的锁对象进行调用,锁对象可以是任意对象!

而Object本身就是代表所有的类根类:代表所有对象!

D.线程组和线程池

1)线程组ThreadGroup

表示一个线程的集合,里面包含了一些线程

2)方法

public final ThreadGroup getThreadGroup():返回该线程所在的线程组

public final String getName()返回此线程组的名称。

public class MyRunnable implements Runnable {

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}

}
测试类

public class ThreadGroupDemo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();

Thread t1 = new Thread(mr, "线程1");
Thread t2 = new Thread(mr, "线程2");

// 返回该线程所在的线程组
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();

// 返回此线程组的名称
String name1 = tg1.getName();
String name2 = tg2.getName();

System.out.println(name1);// main
System.out.println(name2);// mian

// 通过测试:发现线程默认情况线程组属于main线程:
System.out.println(Thread.currentThread().getThreadGroup().getName());// mian
}
}


3)设置一个新的线程

public ThreadGroup(String name)构造一个新线程组。

public final void setDaemon(boolean daemon):设置线程组是否是一个守护线程

public class ThreadGroupDemo {
public static void main(String[] args) {
ThreadGroup tg = new ThreadGroup("线程组");

MyRunnable mr = new MyRunnable();

Thread t1 = new Thread(tg, mr, "线程1");
Thread t2 = new Thread(tg, mr, "线程2");

ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();

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

String name1 = tg1.getName();
String name2 = tg2.getName();

System.out.println(name1);
System.out.println(name2);

tg.setDaemon(true); // 该线程组中的线程都是守护线程!

}
}


4)线程池

启动新的线程,很耗费成本

线程池有一个好处:里面可以存储多条线程

每一条线程执行完毕,不会变成垃圾,等待下次继续使用!

a.创建线程池对象

Executors工厂:专门用来创建线程池的:提供了一个方法

public static ExecutorService newFixedThreadPool(int nThreads)

b.这些方法的返回值是ExecutorService对象,该对象表示一个线程池

可以执行Runnable对象或者Callable对象代表的线程

Future<?> submit(Runnable task):Runnable接口作为一个参数:要该类的子实现类对象

c.线程池结束

void shutdown()

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorsDome {
public static void main(String[] args) {
// 创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);

// 使用ExecutorsService接口中方法:
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());

// 结束线程池
pool.shutdown();
}
}


6)实现多线程的方式3

a.自定义一个累,实现Callable接口

b.实现里面的call方法

import java.util.concurrent.Callable;

public class MyCallable implements Callable {

@Override
public Object call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return null;
}

}


c.主线程中创建线程对象

d.用线程池对象提交任务

e.提交后结束线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CallableDemo {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(2);

threadPool.submit(new MyCallable());
threadPool.submit(new MyCallable());

threadPool.shutdown();
}
}


7)<T> Future<T> submit(Callable<T> task):Future 表示异步计算的结果 接口

需求,分别计算1-100和1-200的和

import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer> {

private int number;

public MyCallable(int number) {
super();
this.number = number;
}

@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= this.number; i++) {
sum += i;
}
return sum;
}

}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableDemo {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(2);

Future<Integer> f1 = threadPool.submit(new MyCallable(100));
Future<Integer> f2 = threadPool.submit(new MyCallable(200));

try {
Integer i1 = f1.get();
Integer i2 = f2.get();

System.out.println(i1);
System.out.println(i2);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}

threadPool.shutdown();
}
}


8)匿名内部类实现多线程

本质:继承该类或者或者实现该接口的子类对象!

a.接口的匿名内部类的方式new Thread(new Runnable(){}).start();

b.Thread类的方式new Thread(new Thread(){}).start();

c.接口的匿名内部类的方式:有点难度// new Thread(new Runnable(){}){}.start();

public class Demo {
public static void main(String[] args) {
// 接口的匿名内部类的方式
// new Thread(new Runnable(){}).start();
new Thread(new Runnable() {

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}).start();

// Thread类的方式
// new Thread(new Thread(){}).start();
new Thread(new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}).start();

// 接口的匿名内部类的方式:有点难度
// new Thread(new Runnable(){}){}.start();
new Thread(new Runnable() {

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("hello" + ":" + i);
}
}
}){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("world" + ":" + i);
}
}
}.start();

}
}


F.定时器Timer和TimerTask

1)概述

可以进行任务的重复操作,定时器要依赖于两个类

Timer和TimerTask

可安排任务执行一次,或者定期重复执行

2)方法

public Timer()创建一个定时器

public void schedule(TimerTask task, Date time)安排在指定的时间执行指定的任务

参数1:task - 所要安排的任务

参数2:time - 执行该任务的时间毫秒值

public boolean cancel()取消计时器任务

需求:3s后执行爆炸操作

import java.util.Timer;
import java.util.TimerTask;

// 创建MyTask任务
class MyTask extends TimerTask {
private Timer t;

public MyTask() {
super();
}

public MyTask(Timer t) {
super();
this.t = t;
}

@Override
public void run() {
System.out.println("boom!");
t.cancel();// 任务完成后取消
}

}

public class TimerDemo {
public static void main(String[] args) {
Timer t = new Timer();
t.schedule(new MyTask(t), 3000);
}
}


public void schedule(TimerTask task,  Date firstTime,long period):每隔多少毫秒进行重复性的 任务

需求:执行一个定时器,3秒之后爆炸,并且每隔2秒继续执行

import java.util.Timer;
import java.util.TimerTask;

// 创建MyTask任务
class MyTask extends TimerTask {

@Override
public void run() {
System.out.println("boom!");
}

}

public class TimerDemo {
public static void main(String[] args) {
Timer t = new Timer();
t.schedule(new MyTask(), 3000, 2000);
}
}


3)需求

需求:在指定的时间删除我们的指定目录(我使用项目路径下的demo)



删除demo文件夹

import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

class DeleteFolder extends TimerTask {

private Timer t;

public DeleteFolder(Timer t) {
super();
this.t = t;
}

@Override
public void run() {
// 封装当前目录下的文件
System.out.println("到达指定时间!启动删除程序!开始删除!");
System.out.println("----------------------删除中----------------------");
File srcFloder = new File("demo");
deleteFloder(srcFloder);
System.out.println("------------------------------------------------");
System.out.println("删除完毕!");
t.cancel();
}

private void deleteFloder(File srcFloder) {
// 获取当前目录的所有的文件夹以及文件File数组
File[] fileArr = srcFloder.listFiles();
// 非空判断
if (fileArr != null) {
// 遍历
for (File file : fileArr) {
// 获取到每一个File对象,判断对象是否是一个文件夹
if (file.isDirectory()) {
deleteFloder(file);
} else {
System.out.println("正在删除:" + file.getName() + "---是否删除成功:" + file.delete());
}
}
System.out.println("正在删除:" + srcFloder.getName() + "---是否删除成功:" + srcFloder.delete());
} else {
System.out.println("删除失败!找不到制定路径");
System.exit(0);
}
}
}

public class TimerTest {
public static void main(String[] args) {
// 创建定时器对象
Timer t = new Timer();

// 设置一个删除时间,日期文本表现
String deleteTime = "2017-8-19 12:03:00";

// 创建SimpleDateFormat对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
Date date = sdf.parse(deleteTime);
System.out.println("删除程序就绪!预定执行时间" + deleteTime);
System.out.println("----------------------等待中----------------------");
t.schedule(new DeleteFolder(t), date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}



运行结果(等待中)



删除完毕



当路径不存在的情况下

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐