1 同步问题引出
在多线程的处理之中,可以利用Runnable描述多个线程操作的资源,而Thread描述每一个线程对象,于是当多个线程访问同一资源的时候如果处理不当就会产生数据的错误操作。
下面编写一个简单的卖票程序,将创建若干个线程对象实现卖票的处理操作。
范例:实现卖票操作
package cn.victor.demo;
class MyThread implements Runnable{
private int tickets = 10;
@Override
public void run() {
while(true) {
if(this.tickets > 0) {
System.out.println(Thread.currentThread().getName() + "sale ticket : " + this.tickets-- );
}else {
System.out.println("********* no tickets **********");
break;
}
}
}
}
public class ThreadDemo {
public static void main(String[] main)throws Exception {
Runnable mt = new MyThread();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
}
}
/*
Thread-0sale ticket : 10
Thread-2sale ticket : 9
Thread-1sale ticket : 10
Thread-2sale ticket : 7
Thread-0sale ticket : 8
Thread-2sale ticket : 5
Thread-1sale ticket : 6
Thread-2sale ticket : 3
Thread-0sale ticket : 4
Thread-2sale ticket : 1
********* no tickets **********
Thread-1sale ticket : 2
********* no tickets **********
********* no tickets **********
*/
此时的程序将创建三个线程对象,并且这三个线程对象将进行10张票的出售。那么此时的程序在进行卖票处理的时候并没有任何问题(假象),下面可以模拟一下买票操作中的延迟操作。
package cn.victor.demo;
class MyThread implements Runnable{
private int tickets = 10;
@Override
public void run() {
while(true) {
try {
Thread.sleep(100);
if(this.tickets > 0) {
System.out.println(Thread.currentThread().getName() + "sale ticket : " + this.tickets-- );
}else {
System.out.println("********* no tickets **********");
break;
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class ThreadDemo {
public static void main(String[] main)throws Exception {
Runnable mt = new MyThread();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
}
}
/*
Thread-1sale ticket : 8
Thread-0sale ticket : 9
Thread-2sale ticket : 10
Thread-1sale ticket : 7
Thread-2sale ticket : 6
Thread-0sale ticket : 6
Thread-1sale ticket : 5
Thread-0sale ticket : 4
Thread-2sale ticket : 3
Thread-1sale ticket : 2
Thread-0sale ticket : 1
Thread-2sale ticket : 0
********* no tickets **********
********* no tickets **********
********* no tickets **********
*/
这个时候追加了延迟问题就暴露出来了,而实际上这个问题一直存在。
2 线程同步处理
经过分析之后已经可以确认同步问题所产生的主要原因了,那么下面就需要进行同步问题的解决,但是解决同步问题的关键是锁,指的是当某一个线程执行操作的时候,其它线程外面等待。
如果要想在程序之中实现这把锁功能,就可以使用synchronized关键字来实现,利用此关键字可以定义同步方法或同步代码块,在同步代码块的操作代码的里面只允许一个线程执行。
1、利用同步代码块进行处理:
synchronized(同步对象){
同步代码操作;
}
一般要进行同步对象处理的时候可以采用当前对象this进行同步。
范例:利用同步代码块解决数据同步访问问题
package cn.victor.demo;
class MyThread implements Runnable{
private int tickets = 10;
@Override
public void run() {
while(true) {
synchronized(this) {
try {
Thread.sleep(100);
if(this.tickets > 0) {
System.out.println(Thread.currentThread().getName() + "sale ticket : " + this.tickets-- );
}else {
System.out.println("********* no tickets **********");
break;
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public class ThreadDemo {
public static void main(String[] main)throws Exception {
Runnable mt = new MyThread();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
}
}
/*
Thread-0sale ticket : 10
Thread-0sale ticket : 9
Thread-0sale ticket : 8
Thread-2sale ticket : 7
Thread-1sale ticket : 6
Thread-1sale ticket : 5
Thread-1sale ticket : 4
Thread-1sale ticket : 3
Thread-1sale ticket : 2
Thread-1sale ticket : 1
********* no tickets **********
********* no tickets **********
********* no tickets **********
*/
加入同步处理之后,程序的整体执行性能下降了。同步实际上会造成性能的降低。
2、利用同步方法解决:只需要在方法定义上使用synchronized关键子即可
class MyThread implements Runnable{
private int tickets = 10;
public synchronized boolean sale() {
try {
Thread.sleep(100);
if(this.tickets > 0) {
System.out.println(Thread.currentThread().getName() + "sale ticket : " + this.tickets-- );
}else {
System.out.println("********* no tickets **********");
return false;
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return true;
}
@Override
public void run() {
while(this.sale()) {
;
}
}
}
在日后学习Java类库的时候会发现,系统中许多的类上使用的同步处理采用的都是同步方法。
3 线程死锁
死锁在进行多线程同步的处理之中有可能产生的一种问题,所谓的死锁指的是若干个线程彼此互相等待的状态。下面通过一个简单的代码来观察一下死锁的表现形式。
范例:死锁的展示
package cn.victor.demo;
class hhy{
public synchronized void say(lks l) {
System.out.println("nothing");
l.get();
}
public synchronized void get() {
System.out.println("love");
}
}
class lks{
public synchronized void say(hhy h) {
System.out.println("what");
h.get();
}
public synchronized void get() {
System.out.println("think");
}
}
public class DeadDemo implements Runnable{
private hhy h = new hhy();
private lks l = new lks();
public DeadDemo() {
new Thread(this).start();;
l.say(h);
}
@Override
public void run() {
h.say(l);
}
public static void main(String[] main)throws Exception {
new DeadDemo();
}
}
现在死锁造成的主要原因是因为彼此都在互相等待着,等待着对方先让出资源。死锁实际上是一种开发中出现的不确定的状态,有的时候代码如果处理不当则会不定期出现死锁,这是属于正常开发中的调试问题。
若干个线程访问同一资源时一定要进行同步处理,而过多的同步会造成死锁。