匿名类的一个好处是可以很方便的访问外部的局部变量。
前提是外部的局部变量需要被声明为final。(JDK7以后就不需要了)
======================
同步方法1:普通式
同步方法2:在对象方法里 写关键字,用this
同步方法3:在方法前,加上修饰符synchronized,效果等同方法2
==================
题目1--同步查找文件内容
把 练习-查找文件内容 改为多线程查找文件内容
原练习的思路是遍历所有文件,当遍历到文件是 .java的时候,查找这个文件的内容,查找完毕之后,再遍历下一个文件
现在通过多线程调整这个思路:
遍历所有文件,当遍历到文件是.java的时候,创建一个线程去查找这个文件的内容,不必等待这个线程结束,继续遍历下一个文件
package zsc.czy.zhonghe;
import java.io.File;
import java.io.FileReader;
public class findContent {
public static void main(String[] args) {
File f = new File("d:/test");
search(f, "Hello");
}
public static void search(File f, String search) {
System.out.println(f); //d:\\find.txt
System.out.println(f.getAbsolutePath());
// System.out.println(f.getName());//find.txt
if (f.isFile()) {
if (f.getName().toLowerCase().endsWith(".java")) {
new Thread(new Runnable() {
@Override
public void run() {
String fileContent = readFileConent(f);
if (fileContent.contains(search)) {
System.out.printf("找到子目标字符串%s,在文件:%s%n", search, f);
}
}
}).start();
}
}
if(f.isDirectory()){
File[] fs = f.listFiles();
for(File file :fs){
search(file,search);
}
}
}
private static String readFileConent(File f) {
try (FileReader fr = new FileReader(f);) {
char[] c = new char[(int) f.length()];
fr.read(c);
String s = new String(c);
return s;
} catch (Exception e) {
return null;
}
}
}
题目2--英雄充能
英雄有可以放一个技能叫做: 波动拳-a du gen。
每隔一秒钟,可以发一次,但是只能连续发3次。
发完3次之后,需要充能5秒钟,充满,再继续发。
package zsc.czy.thread;
public class Hero {
String name;
int hp;
int damage;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getHp() {
return hp;
}
public void setHp(int hp) {
this.hp = hp;
}
public int getDamage() {
return damage;
}
public void setDamage(int damage) {
this.damage = damage;
}
public boolean isDead() {
return 0 >= hp ? true : false;
}
public void attackHero(Hero h) {
try {
// 为了表示攻击需要时间,每次攻击暂停1000毫秒
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
h.hp -= damage;
System.out.format("%s 正在攻击 %s,%s的血编程了 %.0f%n", name, h.name, h.name,
h.hp);
if (h.isDead()) {
System.out.println(h.name + "死了");
}
}
int totalTime = 3;
public void adugen() {
while (true) {
for (int i = 0; i < totalTime; i++) {
System.out.printf("波动拳第%d发%n", i + 1);
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
}
}
System.out.println("开始为时5秒的充能");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Hero h = new Hero();
h.name = "红仔";
h.adugen();
}
}
题目3--破解密码
-
生成一个长度是3的随机字符串,把这个字符串作为当做密码
-
创建一个破解线程,使用穷举法,匹配这个密码
-
创建一个日志线程,打印都用过哪些字符串去匹配,这个日志线程设计为守护线程
提示: 破解线程把穷举法生成的可能密码放在一个容器中,日志线程不断的从这个容器中拿出可能密码,并打印出来。 如果发现容器是空的,就休息1秒,如果发现不是空的,就不停的取出,并打印。
==============================
HashMap不严格 可以存放 null --不是线程安全的类
Hashtable严格
ArrayList是非线程安全的
Vector是线程安全的类
====================
题目4-线程安全的MyStack
借助把非线程安全的集合转换为线程安全,用另一个方式完成 练习-线程安全的MyStack
题目5--死锁
3个同步对象a, b, c
3个线程 t1,t2,t3
故意设计场景,使这3个线程彼此死锁
//我这样设计是不成功的
package zsc.czy.thread;
public class SiSuo {
public static void main(String[] args) {
final Hero ahri = new Hero();
ahri.name = "九尾妖狐";
final Hero annie = new Hero();
annie.name = "安妮";
final Hero leqing = new Hero();
annie.name = "李青";
Thread t1 = new Thread() {
public void run() {
// 占有九尾妖狐
synchronized (ahri) {
System.out.println("t1 已占有九尾妖狐");
try {
// 停顿1000毫秒,另一个线程有足够的时间占有安妮
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("t1 试图占有安妮");
System.out.println("t1 等待中 。。。。");
synchronized (annie) {
System.out.println("do something");
}
}
}
};
t1.start();
Thread t2 = new Thread() {
public void run() {
// 占有安妮
synchronized (annie) {
System.out.println("t2 已占有安妮");
try {
// 停顿1000秒,另一个线程有足够的时间占有暂用九尾妖狐
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("t2 试图占有李青");
System.out.println("t2 等待中 。。。。");
synchronized (leqing) {
System.out.println("do something");
}
}
}
};
t2.start();
Thread t3 = new Thread(){
public void run() {
synchronized (leqing) {
System.out.println("t3已占有李青");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t3 试图占有九尾妖狐");
System.out.println("t3 等待中 。。。。");
}
synchronized (ahri) {
System.out.println("do something");
}
};
};
t3.start();
}
}
正确做法为:
package multiplethread;
public class TestThread {
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
Object c = new Object();
Thread t1 =new Thread(){
public void run(){
synchronized (a) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (b) {
synchronized (c) {
}
}
}
}
};
Thread t2 =new Thread(){
public void run(){
synchronized (c) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (a) {
synchronized (b) {
}
}
}
}
};
Thread t3 =new Thread(){
public void run(){
synchronized (b) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (c) {
synchronized (a) {
}
}
}
}
};
t1.start();
t2.start();
t3.start();
}
}
================
使用wait和notify进行线程交互
this.wait()表示 让占有this的线程等待,并临时释放占有
this.notify() 表示通知那些等待在this的线程,可以苏醒过来了。
public synchronized void recover() {
hp = hp + 1;
System.out.printf("%s 回血1点,增加血后,%s的血量是%.0f%n", name, name, hp);
// 通知那些等待在this对象上的线程,可以醒过来了,如第20行,等待着的减血线程,苏醒过来
this.notify();
}
// 掉血 同步方式和上面效果一样 这个方式
public void hurt() {
synchronized (this) {
if (hp == 1) {
// 让占有this的减血线程,暂时释放对this的占有,并等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
hp = hp - 1;
System.out.printf("%s 减血1点,减少血后,%s的血量是%.0f%n", name, name, hp);
}
}
别忘了Thread 父类也是Object
题目--线程交互
假设加血线程运行得更加频繁,英雄的最大血量是1000
设计加血线程和减血线程的交互,让回血回满之后,加血线程等待,直到有减血线程减血
// 回血
public synchronized void recover() {
if(hp>=1000){
try {
this.wait(); //后加的
} catch (InterruptedException e) {
e.printStackTrace();
}
}
hp = hp + 1;
System.out.printf("%s 回血1点,增加血后,%s的血量是%.0f%n", name, name, hp);
// 通知那些等待在this对象上的线程,可以醒过来了,如第73行,等待着的减血线程,苏醒过来
this.notify();
}
// 掉血 同步方式和上面效果一样 这个方式
public void hurt() {
synchronized (this) {
if (hp == 1) {
// 让占有this的减血线程,暂时释放对this的占有,并等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
hp = hp - 1;
System.out.printf("%s 减血1点,减少血后,%s的血量是%.0f%n", name, name, hp);
//notify() 的意思是,通知一个等待在这个同步对象上的线程,你可以苏醒过来了,有机会重新占用当前对象了。
//掉血之后,唤醒等待的线程
this.notify();//后加的
}
}
题目--多线程交互
在上面的练习的基础上,增加回血线程到2条,减血线程到5条,同时运行。
运行一段时间,观察会发生的错误,分析错误原因,并考虑解决办法
题目--生产者消费者问题
生产者消费者问题是一个非常典型性的线程交互的问题。
- 使用栈来存放数据
1.1 把栈改造为支持线程安全
1.2 把栈的边界操作进行处理,当栈里的数据是0的时候,访问pull的线程就会等待。 当栈里的数据时200的时候,访问push的线程就会等待 - 提供一个生产者(Producer)线程类,生产随机大写字符压入到堆栈
- 提供一个消费者(Consumer)线程类,从堆栈中弹出字符并打印到控制台
- 提供一个测试类,使两个生产者和三个消费者线程同时运行,结果类似如下 :
==================
Lock
题目
在练习-线程安全的MyStack 练习中,使用synchronized把MyStack修改为了线程安全的类。
接下来,借助Lock把MyStack修改为线程安全的类
把synchronized去掉
使用lock占用锁
使用unlock释放锁
必须放在finally执行,万一heros.addLast抛出异常也会执行
package multiplethread;
import java.util.LinkedList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import charactor.Hero;
public class MyStack {
LinkedList<Hero> heros = new LinkedList<Hero>();
Lock lock = new ReentrantLock();
//把synchronized去掉
public void push(Hero h) {
try{
//使用lock占用锁
lock.lock();
heros.addLast(h);
}
finally{
//使用unlock释放锁
//必须放在finally执行,万一heros.addLast抛出异常也会执行
lock.unlock();
}
}
//把synchronized去掉
public Hero pull() {
try{
//使用lock占用锁
lock.lock();
return heros.removeLast();
}
finally{
//使用unlock释放锁
//必须放在finally执行,万一heros.removeLast();抛出异常也会执行
lock.unlock();
}
}
public Hero peek() {
return heros.getLast();
}
public static void main(String[] args) {
}
}
题目--借助tryLock 解决死锁问题
当多个线程按照不同顺序占用多个同步对象的时候,就有可能产生死锁现象。
死锁之所以会发生,就是因为synchronized 如果占用不到同步对象,就会苦苦的一直等待下去,借助tryLock的有限等待时间,解决死锁问题
package multiplethread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestThread {
public static void main(String[] args) throws InterruptedException {
Lock lock_ahri = new ReentrantLock();
Lock lock_annie = new ReentrantLock();
Thread t1 = new Thread() {
public void run() {
// 占有九尾妖狐
boolean ahriLocked = false;
boolean annieLocked = false;
try {
ahriLocked = lock_ahri.tryLock(10, TimeUnit.SECONDS);
if (ahriLocked) {
System.out.println("t1 已占有九尾妖狐");
// 停顿1000秒,另一个线程有足够的时间占有安妮
Thread.sleep(1000);
System.out.println("t1 试图在10秒内占有安妮");
try {
annieLocked = lock_annie.tryLock(10, TimeUnit.SECONDS);
if (annieLocked)
System.out.println("t1 成功占有安妮,开始啪啪啪");
else{
System.out.println("t1 老是占用不了安妮,放弃");
}
} finally {
if (annieLocked){
System.out.println("t1 释放安妮");
lock_annie.unlock();
}
}
}
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} finally {
if (ahriLocked){
System.out.println("t1 释放九尾狐");
lock_ahri.unlock();
}
}
}
};
t1.start();
Thread.sleep(100);
Thread t2 = new Thread() {
public void run() {
boolean annieLocked = false;
boolean ahriLocked = false;
try {annieLocked = lock_annie.tryLock(10, TimeUnit.SECONDS);
if (annieLocked){
System.out.println("t2 已占有安妮");
// 停顿1000秒,另一个线程有足够的时间占有安妮
Thread.sleep(1000);
System.out.println("t2 试图在10秒内占有九尾妖狐");
try {
ahriLocked = lock_ahri.tryLock(10, TimeUnit.SECONDS);
if (ahriLocked)
System.out.println("t2 成功占有九尾妖狐,开始啪啪啪");
else{
System.out.println("t2 老是占用不了九尾妖狐,放弃");
}
}
finally {
if (ahriLocked){
System.out.println("t2 释放九尾狐");
lock_ahri.unlock();
}
}
}
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} finally {
if (annieLocked){
System.out.println("t2 释放安妮");
lock_annie.unlock();
}
}
}
};
t2.start();
}
}
题目--生产者消费者问题
在练习-生产者消费者问题这个练习中,是用wait(), notify(), notifyAll实现了。
接下来使用Condition对象的:await, signal,signalAll 方法实现同样的效果
package multiplethread;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyStack<T> {
LinkedList<T> values = new LinkedList<T>();
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void push(T t) {
try {
lock.lock();
while (values.size() >= 200) {
try {
condition.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
condition.signalAll();
values.addLast(t);
} finally {
lock.unlock();
}
}
public T pull() {
T t=null;
try {
lock.lock();
while (values.isEmpty()) {
try {
condition.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
condition.signalAll();
t= values.removeLast();
} finally {
lock.unlock();
}
return t;
}
public T peek() {
return values.getLast();
}
}
题目--使用AtomicInteger来替换Hero类中的synchronized
在给Hero的方法加上修饰符synchronized 这个知识点中,通过给hurt和 recover方法加上synchronized来达到线程安全的效果。
这一次换成使用AtomicInteger来解决这个问题
提示:int基本类型对应的是AtomicInteger,但是float基本类型没有对应的AtomicFloat。 所以在这个练习中,把hp改为AtomicInteger即可。