在多線程的處理之中,可以利用Runnable描述多個線程操作的資源,而Thread描述每一個線程對象,於是當多個線程訪問同一資源的時候,如果處理不當就會產生數據的錯誤操作。
同步問題的引出
範例:創建若干個線程對象實現賣票操作
class MyThread implements Runnable{
private int ticket = 10;
public void run() {
while(true) {
if(this.ticket > 0) {
try {
Thread.sleep(100); // 模擬網絡延遲操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + this.ticket--);
}
else {
System.out.println("無票");
break;
}
}
}
}
public class Main{
public static void main(String args[]) throws Exception {
MyThread mt = new MyThread();
new Thread(mt, "窗口1").start();
new Thread(mt, "窗口2").start();
new Thread(mt, "窗口3").start();
}
}
當有延遲的時候就能很輕易地看到問題所在
解決同步問題的關鍵是鎖,指的是當某一個線程執行操作的時候,其他線程外面等待。
使用synchronized關鍵字可以定義同步方法或者同步代碼塊;在同步代碼塊的操作裏面的代碼同一時間只允許一個線程執行。
1.利用同步代碼塊進行處理
synchronized(同步對象){
同步代碼操作;
}
一般要進行同步對象處理的時候可以採用當前對象this進行同步。
範例:利用同步代碼塊解決數據同步訪問問題
class MyThread implements Runnable{
private int ticket = 10;
public void run() {
while(true) {
synchronized(this) { // 同步代碼塊
if(this.ticket > 0) {
try {
Thread.sleep(100); // 模擬網絡延遲操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + this.ticket--);
}
else {
System.out.println("無票");
break;
}
}
}
}
}
public class Main{
public static void main(String args[]) throws Exception {
MyThread mt = new MyThread();
new Thread(mt, "窗口1").start();
new Thread(mt, "窗口2").start();
new Thread(mt, "窗口3").start();
}
}
加入同步之後,雖然不會再出現數據上出錯的問題,但是程序的整體行能下降了。
2.利用同步方法解決:只需要再方法定義上使用synchronized關鍵字即可
class MyThread implements Runnable{
private int ticket = 10;
public synchronized boolean sale() {
if(this.ticket > 0) {
try {
Thread.sleep(100); // 模擬網絡延遲操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + this.ticket--);
return true;
}
else {
System.out.println("無票");
return false;
}
}
@Override
public void run() {
while(true) {
if(!this.sale()) {
break;
}
}
}
}
public class Main{
public static void main(String args[]) throws Exception {
MyThread mt = new MyThread();
new Thread(mt, "窗口1").start();
new Thread(mt, "窗口2").start();
new Thread(mt, "窗口3").start();
}
}
使用同步代碼塊和同步方法並沒有多大差別,但是再Java類庫許多類上的同步處理都是用的是同步方法。
死鎖
死鎖是再進行多線程同步處理中有可能出現的問題。所謂的死鎖指的是若干個線程彼此互相等待的狀態。
範例:
package threads;
class test1{
public synchronized void func(test2 t2) {
System.out.println("我是test1,需要一個test2的對象");
t2.get();
}
public synchronized void get() {
System.out.println("我是test1,得到了一個test2的對象");
}
}
class test2{
public synchronized void func(test1 t1) {
System.out.println("我是test2, 需要一個test1的對象");
t1.get();
}
public synchronized void get() {
System.out.println("我是test2,得到了一個test1的對象");
}
}
public class DeadLock implements Runnable{
private test1 t1 = new test1();
private test2 t2 = new test2();
public DeadLock() {
new Thread(this).start(); // 新啓動一個線程
t2.func(t1);
}
@Override
public void run() {
t1.func(t2);
}
public static void main(String[] args) {
new DeadLock();
}
}
造成死鎖的原因是互相等待。
若干個線程訪問同一資源時一定要進行同步處理,而過多的同步處理會造成死鎖。
死鎖是同步引起的。