十、并发多线程
启动一个线程
创建多线程-继承线程类
Txt.java
import java.util.ArrayList;
import java.util.List;
public class Txt {
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
Person p1 = new Person("张一",50,6500);
Person p2 = new Person("张二",30,18000);
Consumption cs1 = new Consumption(p1,500);
Consumption cs2 = new Consumption(p2,1000);
cs1.start();
cs2.start();
}
}
Consumption.java
public class Consumption extends Thread{
public Person p;
public int i;
public Consumption(Person p ,int i) {
this.p = p;
this.i = i;
}
@Override
public void run() {
super.run();
while (p.salary != 0){
p.consumption(i);
}
}
}
Person.java增加的方法
public void consumption(int i){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i<=salary){
salary = salary-i;
System.out.println(name+"消费了"+i+"元,余额"+salary+"元");
}else {
System.out.println("余额不足");
salary=0;
}
}
创建多线程-实现Runnable接口
创建Thread对象—>创建继承Runnable接口类的对象—>将该对象传递给Thread()参数—>.start()启动线程
Txt.java
import java.util.ArrayList;
import java.util.List;
public class Txt {
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
Person p1 = new Person("张一",50,6500);
Person p2 = new Person("张二",30,18000);
Consumption cs1 = new Consumption(p1,500);
Consumption cs2 = new Consumption(p2,1000);
new Thread(cs1).start();
new Thread(cs2).start();
}
}
Consumption.java
public class Consumption implements Runnable{
public Person p;
public int i;
public Consumption(Person p ,int i) {
this.p = p;
this.i = i;
}
@Override
public void run() {
while (p.salary != 0){
p.consumption(i);
}
}
}
创建多线程-匿名类
import java.util.ArrayList;
import java.util.List;
public class Txt {
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
Person p1 = new Person("张一",50,6500);
Person p2 = new Person("张二",30,18000);
Thread th1 = new Thread(){
@Override
public void run() {
super.run();
while (p1.salary != 0){
p1.consumption(200);
}
}
};
Thread th2 = new Thread(){
@Override
public void run() {
super.run();
while (p2.salary != 0){
p2.consumption(500);
}
}
};
th1.start();
th2.start();
}
}
常见的线程方法
当前线程暂停
Thread.sleep(1000); 表示当前线程暂停1000毫秒 ,其他线程不受影响
因为当前线程sleep的时候,有可能被停止,这时就会抛出 InterruptedException
加入到当前线程中
所有进程,至少会有一个线程即主线程,即main方法开始执行,就会有一个看不见的主线程存在
.join表明在主线程中加入该线程
主线程会等待该线程结束完毕或指定时间后(时间是join的参数), 才会往下运行。
线程优先级
.setPriority(10)
当线程处于竞争关系的时候,优先级高的线程会有更大的几率获得CPU资源
临时暂停
Thread.yield();
当前线程,临时暂停,使得其他线程可以有更多的机会占用CPU资源
守护线程
.setDaemon
守护线程的概念是: 当一个进程里,所有的线程都是守护线程的时候,结束当前进程。
守护线程通常会被用来做日志,性能统计等工作。
多线程同步问题
同步产生问题演示
public class Txt {
public static int money = 10000;
public static void main(String[] args) {
int n = 10000;
Thread[] addThreads = new Thread[n];
Thread[] reduceThreads = new Thread[n];
for (int i = 0; i < n; i++) {
Thread th1 = new Thread(){
@Override
public void run() {
money++;
}
};
th1.start();
addThreads[i] = th1;
}
for (int i = 0; i < n; i++) {
Thread th2 = new Thread(){
@Override
public void run() {
money--;
}
};
th2.start();
reduceThreads[i] = th2;
}
//如下代码是把当前线程加入主线程,直观感受就是要等当前线程执行完毕,主线程才继续执行
for (Thread t : addThreads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (Thread t : reduceThreads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(money);
}
}
同步问题产生原因
两个线程同时访问一个数据时,在一个线程更改未完成时,另一个线程操作该数据,导致获取到非期望的数据
解决方法
总体解决思路是: 在一个线程访问数据期间,其他线程不可以访问该数据
synchronized 同步对象概念
Object someObject =new Object();
synchronized (someObject){
//此处的代码只有占有了someObject后才可以执行
}
释放同步对象的方式: synchronized 块自然结束,或者有异常抛出
someObject 又叫同步对象,所有的对象,都可以作为同步对象
使用synchronized 解决同步问题
public class Txt {
public static int money = 10000;
public static void main(String[] args) {
Object obj = new Object();
int n = 10000;
Thread[] addThreads = new Thread[n];
Thread[] reduceThreads = new Thread[n];
for (int i = 0; i < n; i++) {
Thread th1 = new Thread(){
@Override
public void run() {
synchronized (obj){
money++;
}
}
};
th1.start();
addThreads[i] = th1;
}
for (int i = 0; i < n; i++) {
Thread th2 = new Thread(){
@Override
public void run() {
synchronized (obj){
money--;
}
}
};
th2.start();
reduceThreads[i] = th2;
}
//如下代码是把当前线程加入主线程,直观感受就是要等当前线程执行完毕,主线程才继续执行
for (Thread t : addThreads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (Thread t : reduceThreads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(money);
}
}
使用当前对象作为同步对象
这样在操作该对象的属性时,其他线程就无法操作该对象
也可在对象中的方法中使用synchronized (this) {}
,哪个对象调用该方法就锁定哪个对象
在方法前,加上修饰符synchronized
//两个方法同步效果一样
public synchronized void add(){
salary=salary+1;
}
public void subtract(){
//使用this作为同步对象
synchronized (this) {
salary=salary-1;
}
}
线程安全的类
如果一个类,其方法都是有synchronized修饰的,那么该类就叫做线程安全的类
同一时间,只有一个线程能够进入 这种类的一个实例 的去修改数据,进而保证了这个实例中的数据的安全(不会同时被多线程修改而变成脏数据)
常见的线程安全相关的面试题
HashMap和Hashtable的区别
HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式
HashMap可以存放 null,不是线程安全的类
Hashtable不能存放null,是线程安全的类
StringBuffer和StringBuilder的区别
StringBuffer 是线程安全的,StringBuilder 是非线程安全的
所以当进行大量字符串拼接操作的时候,如果是单线程就用StringBuilder会更快些,如果是多线程,就需要用StringBuffer 保证数据的安全性
非线程安全的为什么会比线程安全的 快? 因为不需要同步嘛,省略了些时间
ArrayList和Vector的区别
他们的区别也在于,Vector是线程安全的类,而ArrayList是非线程安全的。
把非线程安全的集合转换为线程安全
ArrayList是非线程安全的,换句话说,多个线程可以同时进入一个ArrayList对象的add方法
借助Collections.synchronizedList,可以把ArrayList转换为线程安全的List
与此类似的,还有HashSet,LinkedList,HashMap等等非线程安全的类,都通过工具类Collections转换为线程安全的
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = Collections.synchronizedList(list1);
}
死锁
当业务比较复杂,多线程应用里有可能会发生死锁
发生在同步的嵌套里
两个线程都在等待对方线程释放占用对象,谁也就没办法占用
public class Txt {
public static void main(String[] args) {
Person p1 = new Person("丽萨",28,5800);
Person p2 = new Person("瑞格",31,18800);
Thread th1 = new Thread(){
@Override
public void run() {
System.out.println("线程一开始启动");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (p1){
System.out.println("线程一绑定p1对象");
synchronized (p2){
System.out.println("线程一绑定p2对象");
}
}
}
};
Thread th2 = new Thread(){
@Override
public void run() {
System.out.println("线程二开始启动");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (p2){
System.out.println("线程二绑定p2对象");
synchronized (p1){
System.out.println("线程二绑定p1对象");
}
}
}
};
th1.start();
th2.start();
}
}
交互
关于wait、notify和notifyAll
这里需要强调的是,wait方法和notify方法,并不是Thread线程上的方法,它们是Object上的方法。
因为所有的Object都可以被用来作为同步对象,所以准确的讲,wait和notify是同步对象上的方法。
wait()的意思是: 让占用了这个同步对象的线程,临时释放当前的占用,并且等待。 所以调用wait是有前提条件的,一定是在synchronized块里,否则就会出错。
notify() 的意思是:通知一个等待在这个同步对象上的线程,你可以苏醒过来了,有机会重新占用当前对象了。
notifyAll() 的意思是:通知所有的等待在这个同步对象上的线程,你们可以苏醒过来了,有机会重新占用当前对象了。
Person类的方法
public synchronized void income(int i){
balance = balance+i;
System.out.println(name+"收入了"+i+"元,余额"+balance+"元");
if(balance>5000){//余额大于5000时,让该线程唤醒
this.notify();
}
}
public synchronized void consumption(int i){
if(balance <1000){//余额小于1000时,让该线程等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
balance = balance-i;
System.out.println(name+"消费了"+i+"元,余额"+balance+"元");
}
线程池
为了解决线程启动和结束耗费资源和时间的问题,引入了线程池的概念
线程池的模式很像生产者消费者模式,消费的对象是一个一个的能够运行的任务
线程池设计思路
import java.util.LinkedList;
public class ThreadPool {
LinkedList<Runnable> list = new LinkedList<>();
public ThreadPool() {
synchronized (list){
for (int i = 0; i < 10; i++) {
new ThreadTest("线程"+i,list).start();
}
}
}
public void add(Runnable task){
synchronized (list){
list.add(task);
list.notifyAll();
}
}
}
class ThreadTest extends Thread{
public String name;
public LinkedList<Runnable> list;
public Runnable temp;//将移除的任务缓存一下,以便在锁的外面执行,因为移除命令为保证安全必须放在锁内
public ThreadTest(String name,LinkedList<Runnable> list) {
this.name = name;
this.list = list;
}
@Override
public void run() {
System.out.println(name+"启动");
while (true){
synchronized (list){
while (list.isEmpty()){
try {
// System.out.println(name+"进入等待状态。。。。。");
list.wait();
// System.out.println(name+"等待结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
temp = list.removeLast();//这句放在锁内,保证数据安全
// list.notifyAll();
}
System.out.println(name + " 获取到任务,并开始执行");
temp.run();//放在锁外,保证执行任务时的并发,如果放在锁内,执行时就会一个一个执行,类似单线程
// System.out.println(name + " 执行完毕");
}
}
}
public static void main(String[] args) {
ThreadPool tp = new ThreadPool();
//启动线程后,延时再有任务过来
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(100);//每隔固定时间发一个任务
} catch (InterruptedException e) {
e.printStackTrace();
}
int j = i;
Runnable task = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);//任务执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务"+ j +"执行");
}
};
tp.add(task);
}
}
java自带线程池
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Txt {
public static void main(String[] args) {
ThreadPoolExecutor tpe = new ThreadPoolExecutor(2,5,30,
TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>());
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1000);//每隔固定时间发一个任务
} catch (InterruptedException e) {
e.printStackTrace();
}
int j = i;
Runnable task = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);//任务执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务"+ j +"执行");
}
};
tpe.execute(task);
}
}
}
lock对象
Lock对象实现同步
Lock是一个接口,为了使用一个Lock对象,需要用到Lock lock = new ReentrantLock();
synchronized占用结束自动释放,Lock必须调用unlock方法,手动释放,为了保证释放的执行,往往会把unlock() 放在finally中进行
static Lock lock = new ReentrantLock();//为了保证锁的唯一性,加个static,或者在创建线程时,使用一个runnable对象
try {
lock.lock();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
trylock方法
synchronized 是不占用到手不罢休的,会一直试图占用下去
trylock会在指定时间范围内试图占用,占成功了,就啪啪啪。 如果时间到了,还占用不成功,扭头就走
注意: 因为使用trylock有可能成功,有可能失败,所以后面unlock释放锁的时候,需要判断是否占用成功了,如果没占用成功也unlock,就会抛出异常
Lock lock = new ReentrantLock();
boolean locked = false;
try {
locked = lock.tryLock(1,TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(locked){
lock.unlock();
}
}
线程交互
使用synchronized方式进行线程交互,用到的是同步对象的wait,notify和notifyAll方法
首先通过lock对象得到一个Condition对象,然后分别调用这个Condition对象的:await, signal,signalAll 方法
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
try {
lock.lock();
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
总结Lock和synchronized的区别
Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,Lock是代码层面的实现。
Lock可以选择性的获取锁,如果一段时间获取不到,可以放弃。synchronized不行,会一根筋一直获取下去。 借助Lock的这个特性,就能够规避死锁,synchronized必须通过谨慎和良好的设计,才能减少死锁的发生
synchronized在发生异常和同步块结束的时候,会自动释放锁。而Lock必须手动释放, 所以如果忘记了释放锁,一样会造成死锁。
读写锁
在写和写,写和读之间是互斥锁,在读与读之间是共享锁(通俗,写时不能再写和读,读时可以再读)
static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
readWriteLock.readLock().lock();//读加锁
readWriteLock.readLock().unlock();//读释放锁
readWriteLock.writeLock().lock();//写加锁
readWriteLock.writeLock().unlock();//写释放锁
原子操作
概念
所谓的原子性操作即不可中断的操作,比如赋值操作
原子性操作本身是线程安全的
AtomicInteger
JDK6 以后,新增加了一个包java.util.concurrent.atomic,里面有各种原子类,比如AtomicInteger
而AtomicInteger提供了各种自增,自减等方法,这些方法都是原子性的。 换句话说,自增方法 incrementAndGet 是线程安全的,同一个时间,只有一个线程可以调用这个方法。
import java.util.concurrent.atomic.AtomicInteger;
public class test {
static int i = 0;
static AtomicInteger ami = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
for (int j = 0; j < 10000; j++) {
new Thread(new Runnable() {
@Override
public void run() {
i++;
ami.getAndIncrement();
}
}).start();
}
Thread.sleep(5000);
System.out.println(i+","+ami);
}
}
练习
1)练习-同步查找文件内容
public static void search(File folder, String search);
假设你的项目目录是 e:/project,遍历这个目录下所有的java文件(包括子文件夹),找出文件内容包括 Magic的那些文件,并打印出来
遍历所有文件,当遍历到文件是.java的时候,创建一个线程去查找这个文件的内容,不必等待这个线程结束,继续遍历下一个文件
import java.io.*;
public class Exercise {
public static void main(String[] args) throws IOException {
File f = new File("F:/Java/JavaProject");
search(f,"Magic");
}
public static void search(File folder, String search) throws IOException {
String str;
for (File f : folder.listFiles()) {
if (f.isFile()){
new Thread(new MyThread(f,search)).start();
}else {
search(f,search);
}
}
}
}
class MyThread implements Runnable{
String str;
File f;
String search;
public MyThread(File f,String search) {
this.f = f;
this.search = search;
}
@Override
public void run() {
try {
FileReader fr = new FileReader(f);
BufferedReader br = new BufferedReader(fr);
while ((str = br.readLine()) != null){
if(str.contains(search)){
System.out.println(f.getAbsolutePath());
break;
}
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2)人消费
人每隔一秒消费一次,但是只能连续消费3次。消费3次后需要等待5秒,才能再次消费
public class Exercise {
public static void main(String[] args) throws InterruptedException {
Person person = new Person("张安",35,3500,30000);
int cash = 320;
int count = 0;
while (person.balance>cash){
Thread.sleep(1000);
person.consumption(cash);
count++;
if(count == 3){
count=0;
System.out.println("--------------------------");
Thread.sleep(5000);
}
}
}
}
3)练习-破解密码
1. 生成一个长度是3的随机字符串,把这个字符串当作 密码
2. 创建一个破解线程,使用穷举法,匹配这个密码
3. 创建一个日志线程,打印都用过哪些字符串去匹配,这个日志线程设计为守护线程
提示: 破解线程把穷举法生成的可能密码放在一个容器中,日志线程不断的从这个容器中拿出可能密码,并打印出来。 如果发现容器是空的,就休息1秒,如果发现不是空的,就不停的取出,并打印
import java.util.LinkedList;
public class Exercise {
static LinkedList<String> linkedList = new LinkedList<>();
public static void main(String[] args) throws InterruptedException {
String code = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
StringBuilder password = new StringBuilder();
for (int i = 0; i < 3; i++) {
password.append(code.charAt((int) (Math.random() * 61)));
}
new Thread(new CrackThread(password)).start();
Thread th = new Thread(new LogThread());
th.setDaemon(true);
th.start();
}
}
class CrackThread implements Runnable {
StringBuilder password;
public CrackThread(StringBuilder password) {
this.password = password;
}
@Override
public void run() {
String code = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
for (int i = 0; i < 61; i++) {
for (int j = 0; j < 61; j++) {
for (int k = 0; k < 61; k++) {
String str = "" + code.charAt(i) + code.charAt(j) + code.charAt(k);
Exercise.linkedList.add(str);
if (str.equals(password.toString())) {
System.out.println("密码是:" + str);
return;
}
}
}
}
}
}
class LogThread implements Runnable {
@Override
public void run() {
int count = 0;//每10个输出一行
while (true) {
if (Exercise.linkedList.isEmpty()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.print(Exercise.linkedList.removeFirst() + " ");
count++;
if (count == 10) {
System.out.println();
count = 0;
}
}
}
}
}
4)练习-在类方法前面加修饰符synchronized
在对象方法前,加上修饰符synchronized ,同步对象是当前实例。
那么如果在类方法前,加上修饰符 synchronized,同步对象是什么呢?
提示:要完成本练习,需要反射reflection的知识,如果未学习反射,可以暂时不做
答案应该是:class,在多个线程调用对象方法(synchronized修饰的)时,是锁不住的,因为锁为this
在多个线程调用类方法(synchronized修饰的)时,可以锁住,因为锁为同一个class
public class Exercise {
public static void main(String[] args) throws InterruptedException {
new Thread(new demoThread1()).start();
new Thread(new demoThread2()).start();
}
//修饰类方法可以锁住,两个线程依次执行
public synchronized static void classMethod() throws InterruptedException {
System.out.println("我是类方法开始");
Thread.sleep(5000);
System.out.println("我是类方法结束");
}
//修饰对象方法没锁住,两个线程同时进行
public synchronized void objectMethod() throws InterruptedException {
System.out.println(this+"我是对象方法开始");
Thread.sleep(5000);
System.out.println(this+"我是对象方法结束");
}
}
class demoThread1 implements Runnable{
@Override
public void run() {
try {
// new Exercise().objectMethod();
Exercise.classMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class demoThread2 implements Runnable{
@Override
public void run() {
try {
// new Exercise().objectMethod();
Exercise.classMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
5)练习-线程安全的MyStack
使用LinkedList实现Stack栈 中的MyStack类,改造为线程安全的类
import java.util.LinkedList;
public class MyStack implements Stack{
public LinkedList<Person> llist;
public MyStack() {
llist = new LinkedList<>();
}
@Override
public synchronized void push(Person h) {
llist.push(h);
}
@Override
public synchronized Person pull() {
return llist.poll();
}
@Override
public synchronized Person peek() {
return llist.peek();
}
}
6)练习-死锁
3个同步对象a, b, c ;3个线程 t1,t2,t3 ;故意设计场景,使这3个线程彼此死锁
public class Exercise {
public static final Object a = new Object();
public static final Object b = new Object();
public static final Object c = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new demoThread1());
Thread t2 = new Thread(new demoThread2());
Thread t3 = new Thread(new demoThread3());
t1.start();
t2.start();
t3.start();
}
}
class demoThread1 implements Runnable{
@Override
public void run() {
synchronized (Exercise.a) {
System.out.println("t1占有对象a");
synchronized (Exercise.b) {
System.out.println("t1占有对象b");
synchronized (Exercise.c) {
System.out.println("t1占有对象c");
}
System.out.println("t1释放对象c");
}
System.out.println("t1释放对象b");
}
System.out.println("t1释放对象a");
}
}
class demoThread2 implements Runnable{
@Override
public void run() {
synchronized (Exercise.b) {
System.out.println("t2占有对象b");
synchronized (Exercise.c) {
System.out.println("t2占有对象c");
synchronized (Exercise.a) {
System.out.println("t2占有对象a");
}
System.out.println("t2释放对象a");
}
System.out.println("t2释放对象c");
}
System.out.println("t2释放对象b");
}
}
class demoThread3 implements Runnable{
@Override
public void run() {
synchronized (Exercise.c) {
System.out.println("t3占有对象c");
synchronized (Exercise.a) {
System.out.println("t3占有对象a");
synchronized (Exercise.b) {
System.out.println("t3占有对象b");
}
System.out.println("t3释放对象b");
}
System.out.println("t3释放对象a");
}
System.out.println("t3释放对象c");
}
}
7)练习-线程交互
假设收入的线程更加频繁,最大收入设置为100000,设计收入和支出的线程交互,让收入达到最大时,收入线程等待,直到有支出线程支出
public class Exercise {
public static void main(String[] args) throws InterruptedException {
Person person = new Person("张三",23,3500,2000);
Object lock = new Object();//公共锁
Thread t1 = new Thread(new IncomeThread(person,lock));
Thread t2 = new Thread(new ExpenseThread(person,lock));
t1.start();
t2.start();
}
}
class IncomeThread implements Runnable{
Person person;
Object lock;
public IncomeThread(Person person,Object lock) {
this.person = person;
this.lock = lock;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
person.income(1000);
if (person.balance>10000){
try {
lock.wait();
System.out.println("收入被唤醒了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class ExpenseThread implements Runnable{
Person person;
Object lock;
public ExpenseThread(Person person,Object lock) {
this.person = person;
this.lock = lock;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
if (person.balance>1500) {
person.consumption(1500);
}else {
lock.notifyAll();
}
}
}
}
}
8)练习-生产者消费者问题
生产者消费者问题是一个非常典型性的线程交互的问题。
1、使用栈来存放数据
1.1 把栈改造为支持线程安全
1.2 把栈的边界操作进行处理,当栈里的数据size是0的时候,访问pull的线程就会等待。 当栈里的数据是size是200的时候,访问push的线程就会等待
2、提供一个生产者(Producer)线程类,生产随机大写字符压入到堆栈
3、提供一个消费者(Consumer)线程类,从堆栈中弹出字符并打印到控制台
4、提供一个测试类,使两个生产者和三个消费者线程同时运行
public class Exercise {
public static final MyStack1<Character> myStack = new MyStack1<>();
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(new Producer()).start();
}
for (int i = 0; i < 3; i++) {
new Thread(new Consumer()).start();
}
}
}
class Producer implements Runnable {
public String code = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
@Override
public void run() {
while (true) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Exercise.myStack) {
if(Exercise.myStack.size() == 20){
try {
System.out.println("暂停压入");
Exercise.myStack.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
Exercise.myStack.notifyAll();
char c = code.charAt((int) (Math.random() * 25));
Exercise.myStack.push(c);
System.out.println("压入:"+c);
}
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Exercise.myStack) {
if(Exercise.myStack.size()==0){
try {
System.out.println("暂停取出");
Exercise.myStack.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
Exercise.myStack.notifyAll();
System.out.println("取出:"+Exercise.myStack.pull());
}
}
}
}
}
MyStack1.java
import java.util.LinkedList;
public class MyStack1<T> implements Stack1<T>{
public LinkedList<T> llist;
public MyStack1() {
llist = new LinkedList<>();
}
public int size(){
return llist.size();
}
@Override
public void push(T h) {
llist.push(h);
}
@Override
public T pull() {
return llist.poll();
}
@Override
public T peek() {
return llist.peek();
}
}
9)练习- 借助线程池同步查找文件内容
在 练习-同步查找文件内容 ,如果文件特别多,就会创建很多的线程。 改写这个练习,使用线程池的方式来完成
初始化一个大小是10的线程池
遍历所有文件,当遍历到文件是.java的时候,创建一个查找文件的任务,把这个任务扔进线程池去执行,继续遍历下一个文件
import java.io.*;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Exercise {
public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,15,
10, TimeUnit.SECONDS,new LinkedBlockingDeque<>());
public static void main(String[] args) throws IOException {
File f = new File("F:/Java/JavaProject");
search(f,"Magic");
threadPoolExecutor.shutdown();//有序关闭,已提交任务继续执行,不接受新任务
}
public static void search(File folder, String search){
String str;
for (File f : folder.listFiles()) {
if (f.isFile()){
threadPoolExecutor.execute(new MyThread(f,search));
}else {
search(f,search);
}
}
}
}
class MyThread implements Runnable{
String str;
File f;
String search;
public MyThread(File f,String search) {
this.f = f;
this.search = search;
}
@Override
public void run() {
try {
FileReader fr = new FileReader(f);
BufferedReader br = new BufferedReader(fr);
while ((str = br.readLine()) != null){
if(str.contains(search)){
System.out.println(f.getAbsolutePath());
break;
}
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
10)练习-借助Lock,把MyStack修改为线程安全的类
在练习-线程安全的MyStack 练习中,使用synchronized把MyStack修改为了线程安全的类。
接下来,借助Lock把MyStack修改为线程安全的类
import java.util.LinkedList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyStack implements Stack{
public LinkedList<Person> llist;
public static Lock lock = new ReentrantLock();
public MyStack() {
llist = new LinkedList<>();
}
@Override
public void push(Person h) {
lock.lock();
llist.push(h);
lock.unlock();
}
@Override
public Person pull() {
lock.lock();
Person p = llist.poll();
lock.unlock();
return p;
}
@Override
public Person peek() {
lock.lock();
Person p = llist.peek();
lock.unlock();
return p;
}
}
11)练习-借助tryLock 解决死锁问题
当多个线程按照不同顺序占用多个同步对象的时候,就有可能产生死锁现象。
死锁之所以会发生,就是因为synchronized 如果占用不到同步对象,就会苦苦的一直等待下去,借助tryLock的有限等待时间,解决死锁问题
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Exercise {
public static final Lock lock1 = new ReentrantLock();
public static final Lock lock2 = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new demoThread1());
Thread t2 = new Thread(new demoThread2());
t1.start();
t2.start();
}
}
class demoThread1 implements Runnable{
@Override
public void run() {
try {
if(Exercise.lock1.tryLock(3, TimeUnit.SECONDS)){
System.out.println("t1占有对象lock1");
if(Exercise.lock2.tryLock(3, TimeUnit.SECONDS)){
System.out.println("t1占有对象lock2");
Exercise.lock2.unlock();
System.out.println("t1释放对象lock2");
}else {
System.out.println("t1占有lock2失败");
}
Exercise.lock1.unlock();
System.out.println("t1释放对象lock1");
}else {
System.out.println("t1占有lock1失败");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class demoThread2 implements Runnable{
@Override
public void run() {
try {
if(Exercise.lock2.tryLock(3, TimeUnit.SECONDS)){
System.out.println("t2占有对象lock2");
if(Exercise.lock1.tryLock(3, TimeUnit.SECONDS)){
System.out.println("t2占有对象lock1");
Exercise.lock1.unlock();
System.out.println("t2释放对象lock1");
}else {
System.out.println("t2占有lock1失败");
}
Exercise.lock2.unlock();
System.out.println("t2释放对象lock2");
}else{
System.out.println("t2占有lock2失败");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
12)练习-生产者消费者问题
在练习-生产者消费者问题这个练习中,是用wait(), notify(), notifyAll实现了。
接下来使用Condition对象的:await, signal,signalAll 方法实现同样的效果