Java基础之线程(二)

一、多线程安全与同步问题

当run()方法体内的代码操作到了成员变量(共享数据)时,就可能会出现多线程安全问题(线程不同步问题)。

编程技巧:

       在方法中尽量少操作成员变量(在不需要共享资源时),多使用局部变量。

线程的同步

1、在Java语言中,引入对象互斥锁的概念,保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记保证在任一时刻,只能有一个线程访问对象。

2、关键字synchronized用来与对象的互斥锁关联。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问(这个对象就变成了同步对象)。

3、一个方法使用关键字synchronized修饰后,当一个线程A使用这个方法时,其他线程想使用这个方法就必须等待,直到线程A使用完这个方法(前提是这些线程使用的是同一个同步对象)。

同步方法的同步对象

1、对于非静态方法来说,this当前对象充当了同步对象

2、对于静态方法来说,同步对象是“类对象”,“类对象”代表的是这个类本身,所有通过类实例化的普通对象共享这个“类对象”。

二、使用synchronized关键字实现同步对象(卖票问题)

synchronized的使用方法:

同步代码块:synchronized放在对象前面限制一段代码的执行

       synchronized(同步对象){

                需要同步的代码;

       }

同步方法:synchronized放在方法声明中,表示整个方法为同步方法。

       public synchronized void method{

             ........

       }

加上 同步机制后,效率低的原因:

1、会丧失Java多线程的并发优势。在执行到同步代码块(或同步方法)时,只能有一个线程执行,其他线程必须等待执行同步代码块(同步方法)的线程释放同步对象的锁。

2、其它等待锁释放的线程会不断检查锁的状态。

三、死锁问题

死锁的原因:

线程1锁住资源A等待资源B,线程2锁住资源B等待资源A,两个线程都在等待自己需要的资源,而这些资源被另外的线程锁住,这些线程你等我,我等你,谁也不愿意让出资源,这样死锁就产生了。

哲学家进餐问题

问题描述:

      n哲学家进餐问题描述有五个哲学家,他们的生活方式是交替地进行思考和进餐,n哲学家们共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五支筷子,n哲学家平时进行思考,饥饿时便试图取其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐,n进餐完毕,放下筷子又继续进行思考。

解决死锁的方法:

1、枷锁顺序一致

2、加大锁的力度


四、Lock实现同步

java.util.concurrent.locks包下的相关接口与类

1、Lock接口

通常使用Lock接口中的方法用来获取锁,其中lock()方法是使用得最多的一个方法。

2、ReentrantLock类("可重入锁")

ReentrantLock是实现了Lock接口的类

3、ReadWriteLock接口

包括两个方法:

Lock readLock();用来获取“读锁”

Lock writeLock();用来获取“写锁”

4、ReentrantReadWriteLock类

是ReadWriteLock接口的实现类

关于线程的读锁和写锁:

       如果有一个线程已经占用了读锁 ,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁;但其他线程申请读锁是可以的。

       如果有一个线程已经占用了写锁,则此时其他线程如果 申请读锁或写锁,则申请的线程会一直等待释放写锁。

synchronized与Lock

(1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置语言实现的;

(2)synchronized在发生异常时,会自动释放线程占有的锁;而Lock在发生异常时,如果没有主动通过unlock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

(3)Lock可以提高多个线程进行读操作的效率。


synchronized关键字实例:

package synchronizeddemo;

public class TicketRunnable implements Runnable {
	private int ticket = 5;
	
	@Override
	public void run() {
		for(int i = 0;i < 100; i++) {//故意是循环次数超过票的总数
			sell();
		}
	}

	//对于非静态方法来说,this当前对象充当了同步对象
	public synchronized void sell() {
		if(ticket > 0) {//如果还有票,则卖票
			try {
				Thread.sleep(1000);//休眠一秒,交出CPU
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			ticket--;
			System.out.println(Thread.currentThread().getName() + "卖了一张票,当前剩余票数:" + ticket);
		}
	}
}
package synchronizeddemo;

public class TestTicket {

	public static void main(String[] args) {
		TicketRunnable tr = new TicketRunnable();
		new Thread(tr,"窗口A").start();
		new Thread(tr,"窗口B").start();
		new Thread(tr,"窗口C").start();
	}

}
运行结果:

死锁问题(哲学家进餐问题)实例:

package deadlock;

//叉子类
public class Fork {
	public void forkSay() {
		System.out.println("我拿到叉子了,请给我刀子...");
	}
}
package deadlock;

//刀子类
public class Knife {
	public void knifeSay() {
		System.out.println("我拿到刀子了,请给我叉子...");
	}
}
package deadlock;

//“哲学家进餐”
public class PhilosopherRunnable implements Runnable {
	private static Fork fork = new Fork();//叉子资源 
	private static Knife knife = new Knife();//刀子资源
	private boolean flag = false;
	
	public void setFlag(boolean flag) {
		this.flag = flag;
	}
	
	@Override
	public void run() {
		if(flag) {
			synchronized(knife) {
				knife.knifeSay();
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized(fork) {
					fork.forkSay();
				}
			}
			System.out.println(Thread.currentThread().getName() + "吃完饭了!");
		}else {
			synchronized(fork) {
				fork.forkSay();
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (knife) {
					knife.knifeSay();
				}
			}
			System.out.println(Thread.currentThread().getName() + "吃完饭了!");
		}
	}
}
package deadlock;

public class Test {

	public static void main(String[] args) {
		PhilosopherRunnable pr1 = new PhilosopherRunnable();
		pr1.setFlag(true);
		PhilosopherRunnable pr2 = new PhilosopherRunnable();
		new Thread(pr1,"康德").start();
		new Thread(pr2,"尼采").start();
	}

}
运行结果:

Lock实例:

package lock;

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

public class TicketLock implements Runnable{
	private int ticket = 5;
	private Lock lock = new ReentrantLock();
	
	@Override
	public void run() {
		for(int i = 0; i < 100; i++) {
			lock.lock();//获取锁(更常用的方法)
			try {
				if(ticket > 0) {
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					ticket--;
					System.out.println(Thread.currentThread().getName() + "卖了一张票,当前剩余票数:" + ticket);
				}
			} finally {
				lock.unlock();//释放“锁”
			}
		}
		
	}
}
package lock;

public class TestLock {

	public static void main(String[] args) {
		TicketLock tl = new TicketLock();
		new Thread(tl,"线程A").start();
		new Thread(tl,"线程B").start();
		new Thread(tl,"线程C").start();
	}

}
运行结果:


ReadWriteLock实例:

package rwlock;

import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class InfoRunnable implements Runnable {
	private static int data;
	private boolean flag = false;
	//该属性对象可以获取“读锁”或“写锁”
	private static ReadWriteLock rwl = new ReentrantReadWriteLock();
	
	public void setFlag(boolean flag) {
		this.flag = flag;
	}
	
	@Override
	public void run() {
		if(flag) {
			for(int i = 0; i < 10; i++) {
				writeData();
			}
		}else{
			for(int i = 0;i < 10; i++) {
				readData();
			}
		}

	}
	
	//写数据方法
	public static void writeData() {
		try {
			rwl.writeLock().lock();//获取"锁"
			System.out.println(Thread.currentThread().getName() + "准备写数据...");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			data = new Random().nextInt(10);//模拟"写数据"
			System.out.println(Thread.currentThread().getName() + "写数据完毕!");
		} finally {
			rwl.writeLock().unlock();//释放"锁"
		}
	}

	//读数据方法
	public static void readData() {
		try {
			rwl.readLock().lock();//获取"读锁"
			System.out.println(Thread.currentThread().getName() +"准备读数据...");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "读取到的数据是:" + data);
		} finally {
			rwl.readLock().unlock();//释放"读锁"
		}
	}
}
package rwlock;

public class TestInfo {

	public static void main(String[] args) {
		InfoRunnable ir1 = new InfoRunnable();
		ir1.setFlag(true);
		new Thread(ir1,"写入线程1").start();
		new Thread(ir1,"写入线程2").start();
	
		InfoRunnable ir2 = new InfoRunnable();
		new Thread(ir2,"读取线程1").start();
		InfoRunnable ir3 = new InfoRunnable();
		new Thread(ir3,"读取线程2").start();
	}

}
运行结果:








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值