多线程学习(二)

线程同步

多个线程操作同一个资源

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

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

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

三大不安全案例

//不安全的买票
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方法出现异常");
        }
    }

}

输出结果:

posted @ 2020-06-24 10:33  大盘鸡嘹咋咧  阅读(182)  评论(0编辑  收藏  举报