1、什么是线程安全?
我们通常会说HashMap 和HashTable的区别是一个是线程不安全的,一个是线程安全的。同样的StringBuilder和StringBuffer中前者是线程不安全的,后者是线程安全的。那么什么是线程安全呢?
所谓线程安全,即不管多少个线程同时执行,代码的结果跟单线程执行的结果总会是一致的,那么我们就说是线程安全的。反之则为线程不安全。
线程安全问题一般都是由全局变量及静态变量引起的。
2、线程安全问题示例代码(购票)
示例代码:
/**
* 购票线程,线程不安全
*/
public class Ticket implements Runnable {
private int ticketNum=100;
@Override
public void run() {
while(true){
if(ticketNum>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":购买了第"+ticketNum+"张票");
ticketNum--;
}else{
break;
}
}
}
}
public static void ticketTest(){
Ticket ticketTread = new Ticket();
for (int i = 0; i <5 ; i++) {
Thread t1=new Thread(ticketTread);
t1.setName("曹操家小兵"+i);
t1.start();
}
}
3、线程安全的解决办法
1)加同步锁synchronized
synchronized关键字为同步锁关键字,可以用于对象锁和类锁,方法锁。
2)加java锁对象Lock
创建Lock对象
示例代码:
/**
* 购票类
* 通过synchoronized 实现线程同步
*/
public class TicketSync implements Runnable {
private Integer ticketNum = 100;
Object obj=new Object();
@Override
public void run() {
while (true) {
try {
synchronized (obj) {
if (ticketNum > 0) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + ":购买了第" + (ticketNum--) + "票");
} else {
break;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class TicketLock implements Runnable {
private int ticketNum = 100;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//取得锁
lock.lock();
try {
if (ticketNum > 0) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + ":购买了第" + ticketNum-- + "张票");
} else {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
}
}
4、死锁
死锁产生的原因:线程间资源交叉使用,线程互相等待对方释放锁资源,则会产生死锁。
上述图中有产生死锁的四个原因:
1.互斥条件:一个资源每次只能被一个线程使用。图上每条路上只能让一个方向的汽车通过,故满足产生死锁的条件之一
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。可以看出,图上每个方向的汽车都在等待其他方向的汽车撤走,故满足产生死锁的条件之二
3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。这里假设没有交警,那么没有人能强行要求其他方向上的汽车撤离,故满足产生死锁的条件之三
4.循环等待条件:若干进程或线程之间形成一种头尾相接的循环等待资源关系。这个在图中很直观地表达出来了
/**
* 死锁演示类
*
*/
public class DeadLockLeft implements Runnable {
Object left;
Object right;
public DeadLockLeft(){
}
public DeadLockLeft(Object left, Object right){
this.left=left;
this.right=right;
}
@Override
public void run() {
synchronized (left){
System.out.println(Thread.currentThread().getName()+":报告!报告!我已进入狭窄路段左侧!");
System.out.println(Thread.currentThread().getName()+":前方通行缓慢,我要等待一秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":终于快过去了,我靠,前方出现一个胖子挡我道啊!我得等他让路");
synchronized (right){
System.out.println(Thread.currentThread().getName()+":那傻叉终于让路了,我可以过去啦");
}
}
}
}
public class DeadLockRight implements Runnable {
Object left;
Object right;
public DeadLockRight(){
}
public DeadLockRight(Object left, Object right){
this.left=left;
this.right=right;
}
@Override
public void run() {
synchronized (right){
System.out.println(Thread.currentThread().getName()+":报告!报告!我已进入狭窄路段右侧!");
System.out.println(Thread.currentThread().getName()+":前方通行缓慢,我要等待一秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":一秒等待结束,前方出现一个黑涩会大佬挡我道啊!我得等他让路");
synchronized (left){
System.out.println(Thread.currentThread().getName()+":那傻叉终于让路了,我可以过去啦");
}
}
}
}
/**
* 死锁测试类
*/
public class DeadLockTest {
public static void main(String[] args) {
Object left=new Object();
Object right=new Object();
Thread leftThread=new Thread(new DeadLockLeft(left,right));
Thread rightThread=new Thread(new DeadLockRight(left,right));
leftThread.setName("王雄");
rightThread.setName("孔锦平");
leftThread.start();
rightThread.start();
}
}
5、如果避免死锁
1)只在必要的最短时间内持有锁,考虑使用同步语句块代替整个同步方法;
2)尽量编写不在同一时刻需要持有多个锁的代码,如果不可避免,则确保线程持有第二个锁的时间尽量短暂;
3)创建和使用一个大锁来代替若干小锁,并把这个锁用于互斥,而不是用作单个对象的对象级别锁;
6、线程间通讯
1)共享内存机制
a)通过synchronized同步方式共享内存对象,来达到信息交换的目的
b)通过wait/notify机制来实现线程间的通讯
2)管道通信机制
使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信。
public class Producer extends Thread {
Product product;
public Producer(){}
public Producer(Product product){
this.product=product;
}
@Override
public void run() {
while (true){
synchronized (product) {
if (product.getNumber() < 5) {
System.out.println(getName() + ":生产了一个" + product.getName());
product.setNumber(product.getNumber() + 1);
} else {
product.notify();
try {
product.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// product.notify();
}
}
}
}
}
public class Consumer extends Thread {
private Product product;
public Consumer(){}
public Consumer(Product product){
this.product=product;
}
@Override
public void run() {
while (true) {
synchronized (product) {
if (product.getNumber() > 0) {
System.out.println(getName()+":来一个"+product.getName());
product.setNumber(product.getNumber()-1);
}else{
try {
product.notify();
product.wait();
// product.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
/**
* 产品类
*/
public class Product implements Serializable{
private static final long serialVersionUID = -6049994461715823463L;
private String name;
private int number;
public Product() {
}
public Product(String name, int number) {
this.name = name;
this.number = number;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public String toString() {
return "Product{" +
"name='" + name + '\'' +
", number=" + number +
'}';
}
}
网友评论