Java SE ——【多线程进阶知识】(一)

多线程安全问题

  • 当多线程并发访问临界资源时,若破坏原子操作,可能会造成数据不一致。
    • 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
    • 原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。

线程同步

(一)同步方式

1. 同步代码块
 synchronized (临界资源对象) { //对临界资源对象加锁
	 //代码(原子操作)
 }
  • 每个对象都有一个互斥锁标记,用来分配给线程。
  • 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。
  • 把同步代码块锁住,只让一个线程在同步代码块中执行。
  • 必须保证多个线程使用的锁对象是同一个。
  • 线程退出同步代码块时,会释放相应的互斥锁标记。
/**
 * 实现 Runnable 接口实现卖票安全机制
 * @author Nigori
 * @date 2020/8/2
 **/
public class Ticket implements Runnable {

    private int ticket = 100;

    private Object object = new Object();
    @Override
    public void run() {
        while (true) {
            synchronized (this) {       // 也可以是object,原因:始终只有一个对象
                if (ticket<=0) break;
                System.out.println(Thread.currentThread().getId() + ": " + Thread.currentThread().getName() + "卖了第 " + ticket + "张票!");
                ticket--;
            }
        }
    }
}
2. 同步方法
//对当前对象(this) 加锁即【 new Ticket() 】
修饰符 synchronized 返回值类型 方法名称(形参列表){
 	//代码(原子操作)
 }
  • 只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。
  • 线程退出同步方法时,会释放相应的互斥锁标记。
public class Ticket implements Runnable {

    private int ticket = 100;

    private Object object = new Object();
    @Override
    public void run() {
        while (true) {
            if (!sale()) {
                break;
            }
        }
    }

    public synchronized boolean sale() {  //锁对象为this  若为静态方法:锁对象为Ticket.class
        if (ticket<=0)
            return false;
        System.out.println(Thread.currentThread().getId() + ": " + Thread.currentThread().getName() + "卖了第 " + ticket + "张票!");
        ticket--;
        return true;
    }
}
3. 锁机制

位置: java.util.concurrent.locks

  • JDK 1.5加入,常用方法:
    • void lock() 获取锁,若锁被占用,则等待
    • boolean tryLock() 常数获取锁,成功返回true ,失败返回false ,不阻塞
    • void unlock() 释放锁
3.1 重入锁

ReentrantLock 类实现 Lock 接口,具有互斥性

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Ticket implements Runnable {

    private int ticket = 10000;
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            lock.lock();	//加锁
            try {
                if (ticket <= 0) break;
                System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票!");
                ticket--;
            } finally {
                lock.unlock();		//释放锁
            }
        }
    }
}
3.2 读写锁

ReentrantReadWriteLock 类实现 Lock 接口

一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。支持多次分配读锁,使多个读操作可以并发执行。

  • 互斥规则
    • 写——写:互斥,阻塞。
    • 读——写:互斥,读阻塞写、写阻塞读。
    • 读——读:不互斥、不阻塞。
  • 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
//import java.util.concurrent.locks.Lock;
**import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class RWLock {

    private String str;

    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();  //读写锁  用时:4013
    private ReentrantReadWriteLock.ReadLock readLock = rwl.readLock();
    private ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock();

    //private Lock lock = new ReentrantLock();        //互斥锁 用时:20103

	//读操作
    public String getValue() {
        readLock.lock();
        //lock.lock();
        try {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "读出的值为: " + str);
            return str;
        } finally {
            readLock.unlock();
            //lock.unlock();
        }
    }

	//写操作
    public void setValue(String str) {
        writeLock.lock();
        //lock.lock();
        try {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "写入的值为: " + str);
            this.str = str;
        } finally {
            writeLock.unlock();
            //lock.unlock();
        }
    }
}
3.3. 死锁
  • 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
  • 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。

@Example:

public class TeamWork {

    public static Object thingsA = new Object();
    public static Object thingsB = new Object();
}
public class RunnablDeathLockDemo {

    public static void main(String[] args) throws InterruptedException {
        Runnable personA = new Runnable() {
            @Override
            public void run() {
                synchronized (TeamWork.thingsA) {
                    System.out.println("A拿到了thingsA");
                    synchronized (TeamWork.thingsB) {
                        System.out.println("A拿到了thingsB");
                        System.out.println("A可以完成整个things");
                    }
                }
            }
        };

        Runnable personB = new Runnable() {
            @Override
            public void run() {
                synchronized (TeamWork.thingsB) {
                    System.out.println("B拿到了thingsB");
                    synchronized (TeamWork.thingsA) {
                        System.out.println("B拿到了thingsA");
                        System.out.println("B可以完成整个things");
                    }
                }
            }
        };

        Thread pa = new Thread(personA);
        Thread pb = new Thread(personB);

        pa.start();
        Thread.sleep(200);	//解决死锁
        pb.start();
    }
}

(二)线程通信

public class BankCard {

    private double money;
    private boolean flag;       //true:只能取  false:只能存

    public synchronized void addMoney(double m) {
        while (flag) {
            try {
                this.wait();  //进入等待队列,同时释放锁和CPU
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money = money + m;
        System.out.println(Thread.currentThread().getName() + "存了" + m + "元,余额是:" + money);
        flag = true;
        this.notifyAll();
    }

    public synchronized void takeMoney(double m) {
        while (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money = money - m;
        System.out.println(Thread.currentThread().getName() + "取了" + m + "元,余额是:" + money);
        flag = false;
        this.notifyAll();
    }
}
1. 等待(java.lang.Object)

public final void wait(long timeout)

  • 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。
2. 通知(java.lang.Object)

public final native void notify();

  • 唤醒在此对象监视器上等待的单个线程。

public final native void notifyAll();

  • 唤醒在此对象监视器上等待的所有线程。

(三)Callable接口

 public interface Callable<V> {
 	 public V call() throws Exception; 
 }
  • JDK5加入,与Runnable接口类似,但是 Callable具有泛型返回值、可以声明异常, 实现之后代表一个线程任务。
public class CallableDemo {

    public static void main(String[] args) throws Exception {

        //创建Callable对象
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i <= 100; i++) {
                    sum+=i;
                }
                return sum;
            }
        };
        //把Callable对象转换成可执行任务
        FutureTask<Integer> futureTask = new FutureTask<>(callable);

        //创建线程
        Thread thread = new Thread(futureTask);

        //启动线程
        thread.start();
        //获取结果(等待call执行完毕才会返回结果)
        System.out.println("1~100的求和结果为:" + futureTask.get());;
    }
}

(四)Future接口

  • V get()以阻塞形式等待Future中的异步处理结果,即call()的返回值
public class PoolSumDemo_01 {

    public static void main(String[] args) throws Exception {
        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(1);

        //提交任务  Future:表示将要执行完任务的结果
        Future<Integer> future = executorService.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i <= 100; i++) {
                    sum+=i;
                }
                return sum;
            }
        });

        //获取结果:等待任务执行完毕才会返回
        System.out.println("使用Future,一个线程求0~100和结果为:" + future.get());

        //关闭线程池
        executorService.shutdown();
    }
}

(五)线程同步

形容一 次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。

在这里插入图片描述

(六)线程异步

形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立刻返回。二者竞争时间片,并发执行。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值