一阶段:第14天:多线程同步(8.10)
一.多线程访问邻接资源
一个线程在访问临界资源的时候,如果给这个资源“上一把锁”,这个时候如果其他线程也要访问这个资源,就得在“锁”外面等待。
锁:任意的对象都可以被当做锁来使用
同步:Synchronized:有等待
异步:Asynchronized:没有等待,各执行各的
案例一:卖票(4个窗口共卖100张票)
public class Demo1 {
public static void main(String[] args) {
Ticket ticket=new Ticket();
new Thread(ticket,"1 ").start();
new Thread(ticket,"2 ").start();
new Thread(ticket,"3 ").start();
new Thread(ticket,"4 ").start();
}
}
public class Ticket implements Runnable {
private int ticket=100;
private Object lock=new Object();
@Override
public void run() {
while (true){
/*synchronized (lock) {//不能直接new,eg:synchronized (new Object())不可以,但是this可以
if (ticket<1) {
break;
}
System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
ticket--;
}*/
if (!sellTicket()){
break;
}
}
}
//同步方法
public synchronized boolean sellTicket(){//非静态方法锁就是this 静态方法锁是当前类的类对象
if (ticket<1) {
return false;
}
System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
ticket--;
return true;
}
}
案例二:存钱取钱
public class Demo2 {
public static void main(String[] args) {
BankCard bankCard=new BankCard();
AddMoney addMoney=new AddMoney(bankCard);
SubMoney subMoney=new SubMoney(bankCard);
new Thread(addMoney,"少泊").start();
new Thread(subMoney,"小海").start();
}
}
该类必须创建,为了之后保证存钱和取钱锁的是同一个对象
public class BankCard {
private double money;
public BankCard() {
}
public BankCard(double money) {
this.money = money;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
注意:创建了个card,为了保证存钱和取钱锁的是同一个对象
public class AddMoney implements Runnable {
private BankCard card;
public AddMoney(BankCard card) {
this.card = card;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (card) {
card.setMoney(card.getMoney()+1000);
System.out.println("存入1000元,余额为:"+card.getMoney());
}
}
}
}
注意:创建了个card,为了保证存钱和取钱锁的是同一个对象
public class SubMoney implements Runnable {
private BankCard card;
public SubMoney(BankCard card) {
this.card = card;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (card) {
if (card.getMoney()>1000){
card.setMoney(card.getMoney()-1000);
System.out.println("取了1000,余额为"+card.getMoney());
}else {
System.out.println("余额不足");
i--;
}
}
}
}
}
从jdk1.5之后加入新的接口 Lock,ReentrantLock是Lock接口的实现类。
通过显式定义同步锁对象来实现同步,同步锁提供了比synchronized代码块更广泛的锁定操
注意:最好将 unlock的操作放到finally块中
通过使用ReentrantLock这个类来进行锁的操作,它实现了Lock接口,使用ReentrantLock可以显式地加锁、释放锁
* 使用Lock接口来实现
public class Ticket implements Runnable {
private int ticket=100;
Lock lock=new ReentrantLock();
public Ticket() {
}
public Ticket(int ticket, Lock lock) {
this.ticket = ticket;
this.lock = lock;
}
public int getTicket() {
return ticket;
}
public void setTicket(int ticket) {
this.ticket = ticket;
}
public Lock getLock() {
return lock;
}
public void setLock(Lock lock) {
this.lock = lock;
}
@Override
public void run() {
while (true){
lock.lock();
try {
if (ticket<1) {
break;
}
System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
ticket--;
} finally {//可以跨方法,可以在别的方法里解锁
lock.unlock();
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
BankCard bankCard=new BankCard();
Lock lock=new ReentrantLock();//*
AddMoney addMoney=new AddMoney(bankCard,lock);
//SubMoney subMoney=new SubMoney(bankCard,lock);
new Thread(addMoney,"少泊").start();
//new Thread(subMoney,"小海").start();
}
}
死锁的条件:
1两个以上的线程
2至少两个锁以上
3同步中嵌套同步
二.多线程在单例中的应用
public class SingleTon {
private SingleTon(){
//禁止反射破解
synchronized (SingleTon.class) {
if (instance!=null){
throw new RuntimeException("不能使用反射创建对象");
}
}
}
//volatile:不稳定的,易挥发的
//为了保证线程可见性,禁止指令重排序,防止空指针异常
//正常:1.开辟空间 2.初始化 3.返回地址
//重排序:1.开辟空间 3.返回地址 2.初始化 --->容易发生空指针异常
private static volatile SingleTon instance;
public SingleTon getInstance(){
if (instance==null) {//双重检查 double check,为了提高效率
synchronized (SingleTon.class) {
if (instance == null) {
instance = new SingleTon();
}
}
}
return SingleTon.instance;
}
}
/*单例的其它写法
静态内部类写法:饿汉和懒汉结合
(1)节省空间
(2)不会出现线程安全问题
public class SingleTon2{
private SingleTon2(){
//禁止反射破解
synchronized (SingleTon.class) {
if (instance!=null){
throw new RuntimeException("不能使用反射创建对象");
}
}
}
static class Holder{
private static SingleTon2 INSTANCE=new SingleTon2();
}
public static SingleTon2 getInstance(){
return Holder.INSTANCE;
}
}
使用枚举
(1)没有线程安全问题(有点浪费空间)
(2)没有反射破解问题
public enum SingleTon3{
INSTANCE;//静态常量
public static SingleTon3 getInstance(){
return INSTANCE;
}
}
*/
三.线程的通信
wait(): 等待,线程执行这个方法进入等待队列(和锁有关,一个锁对应一个等待队列), 需要被唤醒
notify(): 通知唤醒,从等待队列中随机唤醒一个线程
notifyAll():全部唤醒,把等待队列中所有的线程都唤醒
public class BankCard {
private double money;
private boolean flag;//true:有钱,可以取 false:没钱,可以存
//存钱
public synchronized void save(){//this
while /*if*/ (flag==true){//有钱
try {
this.wait();//锁的wait;等待,释放cpu和锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//存
money=money+1000;
System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+money);
flag=true;//修改标记
//this.notify();//唤醒一个,取钱线程,当人多时,容易发生死锁,都睡了
this.notifyAll();//唤醒所有
}
//取钱
public synchronized void take(){//this
if (flag==false){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money=money-1000;
System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+money);
flag=false;//修改标记
//this.notify();//唤醒一个,通知存钱线程去存钱
this.notifyAll();//唤醒所有
}
}
四.读写锁
ReadWriteLock接口:可以实现多个读线程同时读取数据,写线程需要互斥执行。
特点:
读|写 、写|写 需要互斥
读|读 不需要互斥
* 读写锁
public class Demo1 {
public static void main(String[] args) {
//共享资源
Res res=new Res();
//创建锁
ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
//写入(互斥)
//匿名内部类
Runnable runnable=new Runnable() {
@Override
public void run() {
lock.writeLock().lock();
try {
int n;
n = new Random().nextInt(1000);
res.setNum(n);
System.out.println(Thread.currentThread().getName()+"写入"+n);
} finally {
lock.writeLock().unlock();
}
}
};
//读取(共享锁)
Runnable runnable1=new Runnable() {
@Override
public void run() {
lock.readLock().lock();;
try {
int num=res.getNum();
System.out.println(Thread.currentThread().getName()+"读取"+num);
} finally {
lock.readLock().unlock();
}
}
};
//创建线程
for (int i = 0; i < 10; i++) {
new Thread(runnable).start();
}
for (int i = 0; i < 100; i++) {
new Thread(runnable1).start();
}
}
}
五.线程池
线程池维护着一个队列,队列中保存着处于等待(空闲)状态的线程。不用每次都创建新的线程。
和线程池相关的接口和类存在java.util.concurrent并发包中。
1 Executor:线程池的核心接口,负责线程的创建使用和调度的根接口
2 ExecutorService: Executor的子接口,线程池的主要接口, 提供基本功能。
3 ScheduledExecutorService: ExecutorService的子接口,负责线程调度的子接口。
1 ThreadPoolExecutor:ExecutorService的实现类,负责线程池的创建使用。
2 ScheduledThreadPoolExecutor:继承 ThreadPoolExecutor,并实现 ScheduledExecutorService接口,既有线程池的功能,又具有线程调度功能。
3 Executors:线程池的工具类,负责线程池的创建。
newFixedThreadPool();创建固定大小的线程池。
newCachedThreadPool();创建缓存线程池,线程池大小没有限制。根据需求自动调整线程数量。
newSingleThreadExecutor();创建单个线程的线程池,只有一个线程。
newScheduledThreadPool();创建固定大小的线程池,可以延迟或定时执行任务。
public class Demo2 {
public static void main(String[] args) {
//创建票
Ticket ticket=new Ticket();
//创建线程池(4个线程)
ExecutorService executorService= Executors.newFixedThreadPool(4);
//分配任务
for (int i = 0; i < 4; i++) {
executorService.submit(ticket);
}
//关闭
executorService.shutdown();
}
}
* 计算1-100的和,使用线程池
public class Demo3 {
public static void main(String[] args) throws Exception{
ExecutorService executorService= Executors.newCachedThreadPool();
List<Future<Integer>> list=new ArrayList<>();
//给线程池分配任务
for (int i = 0; i < 10; i++) {
Future<Integer> future=executorService.submit(new Callable<Integer>(){
@Override
public Integer call() throws Exception {
int sum=0;
for (int i1 = 0; i1 < 100; i1++) {
sum=sum+i1;
}
System.out.println(Thread.currentThread().getName());
return sum;
}
});
list.add(future);
}
//获取结果
for (Future<Integer> future : list) {
System.out.println(future.get());
}
executorService.shutdown();
}
}
* 线程池的使用
public class Demo4 {
public static void main(String[] args) {
//创建线程池
ScheduledExecutorService scheduledExecutorService= Executors.newScheduledThreadPool(5);
//分配任务
for (int i = 0; i < 5; i++) {
int temp = i;
/* scheduledExecutorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"==="+temp);
}
});*/
/*scheduledExecutorService.schedule(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"==="+temp);
}
}, 5000, TimeUnit.SECONDS);
}*/
//周期执行
/*scheduledExecutorService.scheduleAtFixedRate(new Runnable(){//固定的
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"==="+temp);
}
}, 5, 1, TimeUnit.SECONDS);*///period(间隔时间):运行时间+每次运行间隔时间(运行<间隔),若运行时间>间隔时间;就是运行时间,间隔时间就没用了
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "===" + temp);
}
}, 5, 1, TimeUnit.SECONDS);//delay(间隔时间):无论运行时间是多久,间隔时间就=每次运行间隔时间,与运行时间无关
//关闭
//scheduledExecutorService.shutdown();
}
}
}
六.定时器Timer
public class Demo6 {
public static void main(String[] args) {
//创建Timer对象,调度用这个比较多
Timer timer=new Timer();
//创建人物
TimerTask timerTask=new TimerTask() {
int index=0;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"=="+i);
}
index++;
if (index==10){
timer.cancel();
}
//取消
//timer.cancel();
}
};
//分配任务
/*timer.schedule(timerTask,3000);
//执行完可以取消
//timer.cancel();//不能在这取消,这样可能没执行就取消了*/
//delay---运行前间隔;period---运行中间隔
timer.schedule(timerTask, 1000,2000);//无论在哪都不能取消,除非创建计数变量
}
}