【JavaSE 十】并发多线程

十、并发多线程

启动一个线程

创建多线程-继承线程类

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 方法实现同样的效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值