zoukankan      html  css  js  c++  java
  • 多线程学习(二)

    查看多线程教学视频,请点击 《狂神说java》: https://www.bilibili.com/video/BV1V4411p7EF?p=1 记得投币三连呀~~

    线程同步

    多个线程操作同一个资源

    并发:同一个对象被多个线程同时操作

    队列和锁:实现线程同步需要队列和锁

    队列和锁的理解可以联想排队上厕所:队列就像等待上厕所的队伍,锁就像蹲坑的门,当你进去时把门关上,则其他人就进不去了,如果没有门(锁)就不能独占资源,也就没有了安全性

    三大不安全案例

    //不安全的买票
    public class UnsafeBuyTicket {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		BuyTicket station = new BuyTicket();
    		new Thread(station,"苦逼的我").start();
    		new Thread(station,"牛逼的大家").start();
    		new Thread(station,"可恶的黄牛").start();
    	}
    
    }
    class BuyTicket implements Runnable{
    	//票
    	private int ticketNums = 10;
    	boolean flag = true;//外部停止方式
    	@Override
    	public void run() {
    		//买票
    		while(flag)
    		{
    			try{
    				buy();
    			}catch(InterruptedException e)
    			{
    				e.printStackTrace();
    			}
    		}
    	}
    	private void buy() throws InterruptedException{
    		//判断是否有票
    		if(ticketNums<=0)
    		{
    			return;
    		}
    		
    		//模拟延时
    		Thread.sleep(100);
    		//买票
    		System.out.println(Thread.currentThread().getName()+"拿到了"+ticketNums--+"票");
    	}
    }
    

    输出结果:(拿票混乱,且出现负数)

    //不安全的取钱
    //两个人去银行取钱,账户
    public class UnsafeBank {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		Account a = new Account(100,"结婚基金");
    		
    		Drawing you = new Drawing(a,50,"你");
    		Drawing girl = new Drawing(a,100,"girl");
    		
    		you.start();
    		girl.start();
    		
    	}
    
    }
    //账户
    class Account{
    	int money;//余额
    	String name;//卡名
    	public Account(int money,String name)
    	{
    		this.money = money;
    		this.name = name;
    	}
    }
    //银行:模拟取款
    class Drawing extends Thread{
    	Account account;//账户
    	int drawingMoney;//取了多少钱
    	int nowMoney;//现在手里有多少钱
    	public Drawing(Account account,int drawingMoney,String name) {
    		super(name);   //定义线程名
    		this.account = account;
    		this.drawingMoney = drawingMoney;
    	}
    	//取钱
    	@Override
    	public void run() {
    		//判断有没有钱
    		if(account.money-drawingMoney<0)
    		{
    			System.out.println(Thread.currentThread().getName()+"钱不够");
    			return;
    		}
    		//sleep可以放大问题的发生性
    		try {
    			Thread.sleep(1000);
    		}catch(Exception e)
    		{
    			e.printStackTrace();
    		}
    		//卡内余额=余额-你取的钱
    		account.money = account.money-drawingMoney;
    		//你手里的钱
    		nowMoney = nowMoney + drawingMoney;
    		System.out.println(account.name+"余额为"+account.money);
    		
    		//这两个操作等价
    		//this.getName()=Thread.currentThread().getName()
    		System.out.println(this.getName()+"手里的钱"+nowMoney);
    	}
    }
    

    输出结果:(存款为负数)

    //线程不安全的集合
    public class UnsafeList {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		List<String> list = new ArrayList<String>();
    		for(int i=0;i<10000;i++)
    		{
    			new Thread(()->{
    				list.add(Thread.currentThread().getName());
    			}).start();
    		}
    		try {
    			Thread.sleep(3000);
    		}catch(Exception e)
    		{
    			e.printStackTrace();
    		}
    		System.out.println(list.size());
    	}
    
    }
    

    输出结果:(不足10000,因为存在多个线程同时看到一个标志,于是name就存在被覆盖掉的可能)

    同步方法及同步块(synchronized)(把不安全的改成安全的)

    //加锁,同步方法,实现安全的买票
    public class UnsafeBuyTicket {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            BuyTicket station = new BuyTicket();
            new Thread(station,"苦逼的我").start();
            new Thread(station,"牛逼的大家").start();
            new Thread(station,"可恶的黄牛").start();
        }
    
    }
    class BuyTicket implements Runnable{
        //票
        private int ticketNums = 10;
        boolean flag = true;//外部停止方式
        @Override
        public void run() {
            //买票
            while(flag)
            {
                try{
                    buy();
                }catch(InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
        }
        
        //同步方法,锁的是BuyTicket类实例出的对象
        private synchronized void buy() throws InterruptedException{
            //判断是否有票
            if(ticketNums<=0)
            {
                return;
            }
    
            //模拟延时
            Thread.sleep(100);
            //买票
            System.out.println(Thread.currentThread().getName()+"拿到了"+ticketNums--+"票");
        }
    }
    

    输出结果:(输出结果整洁有序,没有重复,没有负数)

    同步方法的弊端

    同步块

    //使用方法块,找准修改的对象作为同步监视器来实现安全的取钱
    //两个人去银行取钱,账户
    public class UnsafeBank {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Account a = new Account(100,"结婚基金");
    
            Drawing you = new Drawing(a,50,"你");
            Drawing girl = new Drawing(a,100,"girl");
    
            you.start();
            girl.start();
    
        }
    
    }
    //账户
    class Account{
        int money;//余额
        String name;//卡名
        public Account(int money,String name)
        {
            this.money = money;
            this.name = name;
        }
    }
    //银行:模拟取款
    class Drawing extends Thread{
        Account account;//账户
        int drawingMoney;//取了多少钱
        int nowMoney;//现在手里有多少钱
        public Drawing(Account account,int drawingMoney,String name) {
            super(name);   //定义线程名
            this.account = account;
            this.drawingMoney = drawingMoney;
        }
        //取钱
        // 如果直接在run方法前加锁,默认是锁的是调用该方法的对象,即Drawing类的实例化对象,
        // 而我们这里实际上是对account这个共享资源做修改,故应该对account加锁
        @Override
        public void run() {
    
            // 是对account加锁,account称之为同步监视器
            // 锁的对象就是变化的量,需要增删改操作的对象
            synchronized (account){
                //判断有没有钱
                if(account.money-drawingMoney<0)
                {
                    System.out.println(Thread.currentThread().getName()+"钱不够");
                    return;
                }
                //sleep可以放大问题的发生性
                try {
                    Thread.sleep(1000);
                }catch(Exception e)
                {
                    e.printStackTrace();
                }
                //卡内余额=余额-你取的钱
                account.money = account.money-drawingMoney;
                //你手里的钱
                nowMoney = nowMoney + drawingMoney;
                System.out.println(account.name+"余额为"+account.money);
    
                //这两个操作等价
                //System.out.println(this.getName()+Thread.currentThread().getName());
                System.out.println(this.getName()+"手里的钱"+nowMoney);
            }
    
        }
    }
    

    输出结果:(不会出现负数了)

    //使用同步块实现线程安全的集合
    public class UnsafeList {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            List<String> list = new ArrayList<String>();
    
            for(int i=0;i<10000;i++)
            {
                new Thread(()->{
                    //使用同步块
                    synchronized (list){
                        list.add(Thread.currentThread().getName());
                    }
                }).start();
            }
            try {
                Thread.sleep(3000);
            }catch(Exception e)
            {
                e.printStackTrace();
            }
            System.out.println(list.size());
        }
    
    }
    

    输出结果:(size正确)

    CopyOnWriteArrayList

    import java.util.concurrent.CopyOnWriteArrayList;
    
    //测试JUC安全类型的集合,使用juc下的集合不用添加同步块就可实现线程的同步
    public class TestJUC {
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
            for(int i=0;i<10000;i++)
            {
                new Thread(()->{
                    list.add(Thread.currentThread().getName());
                }).start();
            }
            try {
                Thread.sleep(3000);
            }catch(Exception e)
            {
                e.printStackTrace();
            }
            System.out.println(list.size());
        }
    
    }
    

    死锁

    注意:一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”问题!

    制造死锁:

    //死锁:多个线程相互抱着对方需要的资源,然后形成死锁
    public class DeadLock {
    
        public static void main(String[] args) {
            Makeup g1 = new Makeup(0,"灰姑娘");
            Makeup g2 = new Makeup(1,"白雪公主");
    
            g1.start();
            g2.start();
        }
    }
    
    //口红
    class Lipstick{
    
    }
    
    //镜子
    class Mirror{
    
    }
    
    class Makeup extends Thread{
    
        //需要的资源只有一份,用static来保证只有一份
        static Lipstick lipstick  = new Lipstick();
        static Mirror mirror = new Mirror();
    
        int choice; //选择
        String girlName; //使用化妆品的人
    
        Makeup(int chioce,String girlName){
            this.choice = chioce;
            this.girlName = girlName;
        }
    
        @Override
        public void run(){
            //化妆
            try {
                makeup();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        //化妆,互相持有对方的锁,就是需要拿到对象的资源
        private void makeup() throws InterruptedException {
            if (choice==0){
                synchronized (lipstick){
                    System.out.println(this.girlName+"获得口红的锁");
                    Thread.sleep(1000);
                    //下面代码是在synchronized (lipstick){}里面
                    synchronized (mirror){ //一秒钟后想获得镜子
                        System.out.println(this.girlName+"获得镜子的锁");
                    }
                }
            }else{
                synchronized (mirror){  //获得镜子的锁、
                    System.out.println(this.girlName+"获得镜子的锁");
                    Thread.sleep(2000);
                    //下面代码是在synchronized (mirror){}里面
                    synchronized (lipstick){  //一秒后想获得镜子
                        System.out.println(this.girlName+"获得口红的锁");
                    }
                }
            }
        }
    }
    

    这种代码就是产生死锁的关键点:(一个同步块包含两个对象)

    synchronized (lipstick){
                    System.out.println(this.girlName+"获得口红的锁");
                    Thread.sleep(1000);
                    //下面代码是在synchronized (lipstick){}里面
                    synchronized (mirror){ //一秒钟后想获得镜子
                        System.out.println(this.girlName+"获得镜子的锁");
                    }
    

    输出结果:(发生死锁)

    解除死锁:

    //解除死锁
    public class DeadLock {
    
        public static void main(String[] args) {
            Makeup g1 = new Makeup(0,"灰姑娘");
            Makeup g2 = new Makeup(1,"白雪公主");
    
            g1.start();
            g2.start();
        }
    }
    
    //口红
    class Lipstick{
    
    }
    
    //镜子
    class Mirror{
    
    }
    
    class Makeup extends Thread{
    
        //需要的资源只有一份,用static来保证只有一份
        static Lipstick lipstick  = new Lipstick();
        static Mirror mirror = new Mirror();
    
        int choice; //选择
        String girlName; //使用化妆品的人
    
        Makeup(int chioce,String girlName){
            this.choice = chioce;
            this.girlName = girlName;
        }
    
        @Override
        public void run(){
            //化妆
            try {
                makeup();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        //化妆,互相持有对方的锁,就是需要拿到对象的资源
        private void makeup() throws InterruptedException {
            if (choice==0){
                synchronized (lipstick){
                    System.out.println(this.girlName+"获得口红的锁");
                    Thread.sleep(1000);
                }
                //将下面代码从上面同步块中拿出,即可化解死锁
                synchronized (mirror){ //一秒钟后想获得镜子
                    System.out.println(this.girlName+"获得镜子的锁");
                }
            }else{
                synchronized (mirror){  //获得镜子的锁、
                    System.out.println(this.girlName+"获得镜子的锁");
                    Thread.sleep(2000);
                }
                //将下面代码从上面同步块中拿出,即可化解死锁
                synchronized (lipstick){  //一秒后想获得镜子
                    System.out.println(this.girlName+"获得口红的锁");
                }
            }
        }
    }
    

    输出结果:(不发生死锁)

    避免死锁的方法

    Lock(锁)

    下面使用可重入锁ReentrantLock对共享资源进行显示加锁、解锁:

    import java.util.concurrent.locks.ReentrantLock;
    
    public class TestLock {
        public static void main(String[] args) {
            TestLock2 testLock2 = new TestLock2();
    
            new Thread(testLock2).start();
            new Thread(testLock2).start();
            new Thread(testLock2).start();
        }
    }
    
    class TestLock2 implements Runnable{
    
        int ticketNums = 10;
    
        //定义lock锁
        private final ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run(){
            while(true){
    
                try {
                    lock.lock(); //加锁
                    if (ticketNums>0){
                        try{
                            Thread.sleep(1000);
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                        System.out.println(ticketNums--);
                    }else{
                        break;
                    }
                }finally {
                    lock.unlock(); //解锁
                }
    
            }
        }
    }
    

    输出结果:

    synchronized与Lock的对比

    线程协作

    生产者消费者问题

    线程通信

    解决方式1

    //测试:生产者消费者模型 --->利用缓存区解决:管程法
    
    public class TestPC {
    
        public static void main(String[] args) {
    
            SyncContainer container = new SyncContainer();
    
            new ProviderThread(container).start();
            new ConsumerThread(container).start();
    
        }
    }
    
    //生产者
    class ProviderThread extends Thread {
    
        //创建好的缓冲区
        private SyncContainer syncContainer;
    
        public ProviderThread(SyncContainer syncContainer) {
            this.syncContainer = syncContainer;
        }
    
        @Override
        public void run() {
            for (int i = 1; i <= 100; i++) {
                syncContainer.push(new Product(i));
                System.out.println("生产了第" + i + "只鸡!");
            }
        }
    }
    
    //消费者
    class ConsumerThread extends Thread {
    
        private SyncContainer syncContainer;
    
        public ConsumerThread(SyncContainer syncContainer) {
            this.syncContainer = syncContainer;
        }
    
        @Override
        public void run() {
            for (int i = 1; i <= 100; i++) {
                Product pop = syncContainer.pop();
                System.out.println("消费了第"+ pop.getId() + "号产品");
            }
        }
    }
    
    //产品
    class Product {
        private int id;
    
        public Product(int id) {
            this.id = id;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    }
    
    //缓冲区
    class SyncContainer {
    
        //容器大小,product[0]为空,不使用,即最多放10件产品
        Product[] products = new Product[11];
        //容器计数器
        int count = 0;
    
        //生产者放入产品
        public synchronized void push(Product product){
            //如果容器满了,就要等待消费者
            //这里为什么减2才能实现最多连续存储10只鸡,而减1会出现最多连续存储11只鸡,我不清楚,比较费解
            if(count == products.length-2) {
                //等待消费者消费,生产者等待
                try {
                    this.wait();
                } catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            //如果容器没有满,我们就要丢入产品
            count++;
            products[count] = product;
    
    
            //可以通知消费者消费
            this.notifyAll();
        }
    
    
        //消费者消费产品
        public synchronized Product pop(){
            //判断容器是否为空
            if(count == 0){
                //等待生产者生产.消费者等待
                try {
                    this.wait();
                } catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
    
            //如果可以消费
    
            Product product =  products[count];
            count--;
    
            //通知生产者生产
            this.notifyAll();
            return product;
        }
    }
    

    小疑问:

    count == products.length-2 //容器实际能存放10件产品,数组长度为11,减2则count为9,对应product[9],而实际可以存放至product[10],因为下标为0时,不存放产品。为什么-2才能最大连续打至出生产了第10只鸡,而-1会连续打印至生产第11只鸡
    

    输出结果:(符合要求)

    解决方式2

    //测试生产者消费者问题2:信号灯法,标志位解决
    public class TestPC2 {
    
        public static void main(String[] args) {
            TV tv = new TV();
            new Player(tv).start();
            new Watcher(tv).start();
        }
    }
    
    //生产者-->演员
    class Player extends Thread{
    
        TV tv;
        public Player(TV tv){
            this.tv = tv;
        }
    
        @Override
        public void run(){
            for (int i = 0;i<20;i++){
                if (i%2==0){
                    this.tv.play("快乐大本营播放中");
                }else{
                    this.tv.play("抖音:记录美好生活");
                }
            }
        }
    }
    
    //消费之-->观众
    class Watcher extends Thread{
        TV tv;
        public Watcher(TV tv){
            this.tv = tv;
        }
    
        @Override
        public void run(){
            for (int i=0;i<20;i++){
                tv.watch();
            }
        }
    
    }
    
    //产品-->节目
    class TV {
        //演员表演,观众等待   T
        //观众观看,演员等待   F
        String voice; //表演的节目
        boolean flag = true;  //flag为真时,观众等待,为假时演员等待
    
        //表演
        public synchronized void play(String voice){
    
            if (!flag){
                try{
                    this.wait();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            System.out.println("演员表演了:"+voice);
            //通知观众观看
            this.notifyAll(); //通知唤醒
            this.voice = voice;
            this.flag = !this.flag;
        }
    
        //观看
        public synchronized void watch(){
            if (flag){
                try {
                    this.wait();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            System.out.println("观看了:"+voice);
            //通知演员表演
            this.notifyAll();
            this.flag = !this.flag;
        }
    }
    

    输出结果:

    使用线程池

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    //测试线程池
    public class TestPool {
    
        public static void main(String[] args) {
            //1.创建服务,创建线程池
            //newFixedThreadPool 参数为线程池大小
            ExecutorService service = Executors.newFixedThreadPool(10);
    
            //执行runnable接口实现类对象
            service.execute(new MyThread());
            service.execute(new MyThread());
            service.execute(new MyThread());
            service.execute(new MyThread());
            
            //2.关闭连接
            service.shutdown();
        }
    }
    
    class MyThread implements Runnable{
        @Override
        public void run(){
            System.out.println(Thread.currentThread().getName());
        }
    }
    

    输出结果:

    package com.ztx.gaoji;
    
    import org.apache.commons.io.FileUtils;
    
    import java.io.File;
    import java.io.IOException;
    import java.net.URL;
    
    //JUC并发编程
    import java.util.concurrent.*;
    
    public class TestCallable implements Callable<Boolean> {
    
        private String url;
        private String name;
    
        public TestCallable(String url,String name){
            this.url = url;
            this.name = name;
        }
    
        @Override
        public Boolean call() throws Exception {
            //下载图片
            WebDownloader webDownloader = new WebDownloader();//下载器
            webDownloader.downloader(url,name);//下载文件的方式
            System.out.println("下载了图片-->"+name);
            return true;
        }
    
        //启动线程
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            TestCallable t1 = new TestCallable("https://img2020.cnblogs.com/blog/1732557/202006/1732557-20200617142339091-966351471.png","你好1.jpg");
            TestCallable t2 = new TestCallable("https://img2020.cnblogs.com/blog/1732557/202006/1732557-20200617142428143-1598758167.png","你好2.jpg");
            TestCallable t3 = new TestCallable("https://img2020.cnblogs.com/blog/1732557/202006/1732557-20200617142459110-1793347461.png","你好3.jpg");
    
            //创建执行服务:
            ExecutorService ser = Executors.newFixedThreadPool(3);
    
            //提交执行:
            Future<Boolean> result1 = ser.submit(t1);
            Future<Boolean> result2 = ser.submit(t2);
            Future<Boolean> result3 = ser.submit(t3);
    
            //获取结果
            boolean r1 = result1.get();
            boolean r2 = result2.get();
            boolean r3 = result3.get();
    
            //判断线程是否顺利结束或者有异常
            System.out.println(r1);
            System.out.println(r2);
            System.out.println(r3);
    
            //关闭服务
            ser.shutdownNow();
        }
    }
    
    
    
    //下载图片
    class WebDownloader{
    
        //下载方法
        public void downloader(String url,String name){
            try {
                FileUtils.copyURLToFile(new URL(url),new File(name));
            } catch (IOException e) {
                e.printStackTrace();
                //输出异常信息
                System.out.println("downloader方法出现异常");
            }
        }
    
    }
    

    输出结果:

  • 相关阅读:
    .net 下webservice 的WebMethod的属性
    做一个项目,平时都用到哪些工具提高效率(James Li)
    Android之解析Android Map地图返回的Json数据
    歌词文件LRC的解析,可用于音乐播放器实现歌词同步操作
    Android之创建程序快捷方式
    Android之Bitmap使用心得(持续更新)
    Socket编程之旅(服务器与客户端沟通)
    Android之应用自定义相机拍照并且对拍照文字(英文)进行识别
    android之App widget实际应用Demo
    Android之创建实时文件夹
  • 原文地址:https://www.cnblogs.com/churujianghudezai/p/13186211.html
Copyright © 2011-2022 走看看