一、多线程安全与同步问题
当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();
}
}
运行结果: