天天看點

多線程--Thread、Runnable、Callable、線程池、synchronized、Lock、線程的狀态一、建立線程 二、synchronized、lock 三、線程的狀态

目錄

一、建立線程 

1.1繼承Thread類

1.1.1建立ThreadTest繼承Thread類

1.1.2編寫測試類

1.2實作Runnable接口

1.2.1編寫Tickets類

1.2.2編寫RunnableTest類實作Runnable接口

1.2.3編寫測試類MainTest

1.3實作Callable接口結合FutureTask類使用

1.3.1編寫Tickets類

1.3.2編寫CallableTest類

1.3.3編寫測試類

注:線程的執行過程 

注:call()方法傳回值的擷取

1.4線程池建立線程使用

二、synchronized、lock

2.1 synchronized 三種用法

2.1.1 synchronized 修飾執行個體方法

2.1.2 synchronized 修飾靜态方法

2.1.3 synchronized 修飾代碼塊

 注:synchronized 總結

注:synchronized的實作原理請參考下面部落格

2.2 Lock

2.2.1 synchronized缺陷以及lock介紹

2.2.2 lock使用

三、線程的狀态

3.1狀态圖解

3.2線程的常用方法

一、建立線程 

建立線程的三種方式:繼承Thread類、實作Runnable接口、實作Callable接口結合FutureTask類使用

四種方式實作多線程賣票;

話不多說,直接幹

1.1繼承Thread類

1.1.1建立ThreadTest繼承Thread類

package thread;

/**
 * @author ppc
 *
 */
public class ThreadTest extends Thread {
	
	//總票數20
	static Integer count = 20;
	
	@Override
	public void run() {
		while(count>0) {
			synchronized (count) {//對餘票加鎖
				if(count>0) {
					System.out.println(Thread.currentThread().getName()+"賣了一張票,還剩"+--count+"票。");
				}
				
			}
			try {
				sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
        if(count==0) {
					System.out.println("票已賣完");

				}

	}


	public ThreadTest() {
		super();
		// TODO Auto-generated constructor stub
	}


	public ThreadTest(String name) {
		super(name);
		// TODO Auto-generated constructor stub
	}

	
	
}
           

1.1.2編寫測試類

package thread;

/**
 * @author ppc
 *
 */
public class MainTest {

	public static void main(String[] args) {
		//三個視窗同時賣票
		ThreadTest tt1 = new ThreadTest("視窗一");
		ThreadTest tt2 = new ThreadTest("視窗二");
		ThreadTest tt3 = new ThreadTest("視窗三");
		//啟動線程
		tt1.start();
		tt2.start();
		tt3.start();
	
	}
}
           

執行結果:

視窗一賣了一張票,還剩19票。

視窗三賣了一張票,還剩18票。

視窗二賣了一張票,還剩17票。

視窗一賣了一張票,還剩16票。

視窗二賣了一張票,還剩15票。

視窗三賣了一張票,還剩14票。

視窗一賣了一張票,還剩13票。

視窗二賣了一張票,還剩12票。

視窗三賣了一張票,還剩11票。

視窗一賣了一張票,還剩10票。

視窗二賣了一張票,還剩9票。

視窗三賣了一張票,還剩8票。

視窗一賣了一張票,還剩7票。

視窗二賣了一張票,還剩6票。

視窗三賣了一張票,還剩5票。

視窗一賣了一張票,還剩4票。

視窗二賣了一張票,還剩3票。

視窗三賣了一張票,還剩2票。

視窗一賣了一張票,還剩1票。

視窗二賣了一張票,還剩0票。

票已賣完

票已賣完

票已賣完

1.2實作Runnable接口

1.2.1編寫Tickets類

package thread;

/**
 * @author ppc
 *
 */
public class Tickets {

	//總票數20張
	int count = 20;

	public int getCount() {
		return count;
	}

	public void setCount(int count) {
		this.count = count;
	}

	public Tickets(int count) {
		super();
		this.count = count;
	}

	public Tickets() {
		super();
		// TODO Auto-generated constructor stub
	}
	
	
}
           

1.2.2編寫RunnableTest類實作Runnable接口

package thread;

/**
 * @author ppc
 *
 */
public class RunnableTest implements Runnable {

	//Tickets tickets;//票數對象
	
	Integer count = 20;
	
	synchronized void sale() {
		if(count>0) {
			System.out.println(Thread.currentThread().getName()+"賣了一張票,還剩"+count+"票。");
		}
	}
	
	@Override
	public void run() {
		while(count>0) {
			synchronized (count) {

				if(count>0) {
					
					System.out.println(Thread.currentThread().getName()+"賣了一張票,還剩"+ --count+"票。");
				}

			}
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(count==0){
			System.out.println(Thread.currentThread().getName()+"票已賣完。");

		}

	}

	

	public RunnableTest() {
		super();
		// TODO Auto-generated constructor stub
	}
	
	

}
           

1.2.3編寫測試類MainTest

package thread;

/**
 * @author ppc
 *
 */
public class MainTest {

	
	
	public static void main(String[] args) {
		RunnableTest rt = new RunnableTest();
		new Thread(rt,"視窗4").start();
		new Thread(rt,"視窗5").start();
		new Thread(rt,"視窗6").start();

	}

}
           

執行結果:

視窗4賣了一張票,還剩19票。

視窗5賣了一張票,還剩18票。

視窗6賣了一張票,還剩17票。

視窗4賣了一張票,還剩16票。

視窗5賣了一張票,還剩15票。

視窗6賣了一張票,還剩14票。

視窗6賣了一張票,還剩13票。

視窗4賣了一張票,還剩12票。

視窗5賣了一張票,還剩11票。

視窗4賣了一張票,還剩10票。

視窗5賣了一張票,還剩9票。

視窗6賣了一張票,還剩8票。

視窗6賣了一張票,還剩7票。

視窗4賣了一張票,還剩6票。

視窗5賣了一張票,還剩5票。

視窗5賣了一張票,還剩4票。

視窗4賣了一張票,還剩3票。

視窗6賣了一張票,還剩2票。

視窗4賣了一張票,還剩1票。

視窗5賣了一張票,還剩0票。

視窗4票已賣完。

視窗6票已賣完。

視窗5票已賣完。

1.3實作Callable接口結合FutureTask類使用

1.3.1編寫Tickets類

參考1.2.1中的Tickets類

1.3.2編寫CallableTest類

package thread;

import java.util.concurrent.Callable;

/**
 * @author ppc
 *
 */
public class CallableTest implements Callable<Object> {

	Integer count = 20;//票數對象

	@Override
	public Object call() throws Exception {
		while(count>0) {
			synchronized (count) {
			
				if(count>0) {
					System.out.println(Thread.currentThread()+"賣了一張票,還剩"+ --count+"票。");
				}
			}
			Thread.sleep(100);
		}
		if(count == 0) {
			System.out.println(Thread.currentThread()+"票已賣完");
		}
		//可以有傳回值
		return "tickets";
	}

	public CallableTest() {
		super();
		// TODO Auto-generated constructor stub
	}

	
}
           

1.3.3編寫測試類

public static void main(String[] args) {
		CallableTest ct = new CallableTest();
		FutureTask<Object> ft1 = new FutureTask<>(ct);
		FutureTask<Object> ft2 = new FutureTask<>(ct);
		FutureTask<Object> ft3 = new FutureTask<>(ct);
		
		new Thread(ft1,"視窗7").start();
		new Thread(ft2,"視窗8").start();
		new Thread(ft3,"視窗9").start();
//		FutureTask<Object> ft1 = new FutureTask<>(ct);
//		
//		new Thread(ft1,"視窗7").start();不能寫成這樣,如果寫成這樣隻有一個線程執行因為FutureTask中的run()方法執行的時候會判斷FutureTask的屬性state的值,隻有是0的時候才會往下執行調callable的call方法
//		new Thread(ft1,"視窗8").start();
//		new Thread(ft1,"視窗9").start();
		try {
			System.out.println(	ft1.get());//可以接受call()的傳回參數
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
           

執行結果:

Thread[視窗7,5,main]賣了一張票,還剩19票。

Thread[視窗9,5,main]賣了一張票,還剩18票。

Thread[視窗8,5,main]賣了一張票,還剩17票。

Thread[視窗9,5,main]賣了一張票,還剩16票。

Thread[視窗8,5,main]賣了一張票,還剩15票。

Thread[視窗7,5,main]賣了一張票,還剩14票。

Thread[視窗9,5,main]賣了一張票,還剩13票。

Thread[視窗7,5,main]賣了一張票,還剩12票。

Thread[視窗8,5,main]賣了一張票,還剩11票。

Thread[視窗8,5,main]賣了一張票,還剩10票。

Thread[視窗7,5,main]賣了一張票,還剩9票。

Thread[視窗9,5,main]賣了一張票,還剩8票。

Thread[視窗8,5,main]賣了一張票,還剩7票。

Thread[視窗9,5,main]賣了一張票,還剩6票。

Thread[視窗7,5,main]賣了一張票,還剩5票。

Thread[視窗8,5,main]賣了一張票,還剩4票。

Thread[視窗7,5,main]賣了一張票,還剩3票。

Thread[視窗9,5,main]賣了一張票,還剩2票。

Thread[視窗8,5,main]賣了一張票,還剩1票。

Thread[視窗9,5,main]賣了一張票,還剩0票。

Thread[視窗7,5,main]票已賣完

tickets

Thread[視窗8,5,main]票已賣完

Thread[視窗9,5,main]票已賣完

注:線程的執行過程 

start()——> start0()(本地方法)——> run() 

參考源碼(截選了部分源碼)

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

    /**
     * If this thread was constructed using a separate
     * <code>Runnable</code> run object, then that
     * <code>Runnable</code> object's <code>run</code> method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * Subclasses of <code>Thread</code> should override this method.
     *
     * @see     #start()
     * @see     #stop()
     * @see     #Thread(ThreadGroup, Runnable, String)
     */
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
           

注:call()方法傳回值的擷取

執行run()方法,調用call()方法,設定outcome參數 

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;//構造方法傳進的Callable對象
            if (c != null && state == NEW) {//1.3.3的原因
                V result;
                boolean ran;
                try {
                    result = c.call();//調用call()方法
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);//設定outcome參數
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
           

 設定outcome的set()方法

/**
     * Sets the result of this future to the given value unless
     * this future has already been set or has been cancelled.
     *
     * <p>This method is invoked internally by the {@link #run} method
     * upon successful completion of the computation.
     *
     * @param v the value
     */
    protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }
           

FutureTask類的report()方法

/**
     * Returns result or throws exception for completed task.
     *
     * @param s completed state value
     */
    @SuppressWarnings("unchecked")
    private V report(int s) throws ExecutionException {
        Object x = outcome;//将call()方法的傳回值賦給x
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }
           

FutureTask類的get()方法

/**
     * @throws CancellationException {@inheritDoc}
     */
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);//傳回report()方法中的傳回值
    }
           

1.4線程池建立線程使用

package thread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

/**
 * @author ppc
 *
 */
public class MainTest {

	//線程池使用
	public static void main(String[] args) {
		ExecutorService es = Executors.newCachedThreadPool();
		Tickets t = new Tickets();
		List<Future> lf = new ArrayList<Future>();//用來存放call方法的傳回值
		for(int i=0;i<5;i++) {
			//es.execute(new RunnableTest(t)); //實作runnable接口的參數,用execute方法執行
			Future f = es.submit(new CallableTest(t));//實作Callable接口的參數,用execute方法執行
			lf.add(f);
		}
		es.shutdown();
		
		try {
			for (Future future : lf) {
				System.out.println(future.get());
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

}
           

執行結果:

Thread[pool-1-thread-1,5,main]賣了一張票,還剩19票。

Thread[pool-1-thread-5,5,main]賣了一張票,還剩18票。

Thread[pool-1-thread-4,5,main]賣了一張票,還剩17票。

Thread[pool-1-thread-3,5,main]賣了一張票,還剩16票。

Thread[pool-1-thread-2,5,main]賣了一張票,還剩15票。

Thread[pool-1-thread-1,5,main]賣了一張票,還剩14票。

Thread[pool-1-thread-5,5,main]賣了一張票,還剩13票。

Thread[pool-1-thread-4,5,main]賣了一張票,還剩12票。

Thread[pool-1-thread-3,5,main]賣了一張票,還剩11票。

Thread[pool-1-thread-2,5,main]賣了一張票,還剩10票。

Thread[pool-1-thread-4,5,main]賣了一張票,還剩9票。

Thread[pool-1-thread-5,5,main]賣了一張票,還剩8票。

Thread[pool-1-thread-1,5,main]賣了一張票,還剩7票。

Thread[pool-1-thread-3,5,main]賣了一張票,還剩6票。

Thread[pool-1-thread-2,5,main]賣了一張票,還剩5票。

Thread[pool-1-thread-5,5,main]賣了一張票,還剩4票。

Thread[pool-1-thread-1,5,main]賣了一張票,還剩3票。

Thread[pool-1-thread-4,5,main]賣了一張票,還剩2票。

Thread[pool-1-thread-3,5,main]賣了一張票,還剩1票。

Thread[pool-1-thread-2,5,main]賣了一張票,還剩0票。

Thread[pool-1-thread-5,5,main]票已賣完

Thread[pool-1-thread-4,5,main]票已賣完

Thread[pool-1-thread-1,5,main]票已賣完

tickets

Thread[pool-1-thread-3,5,main]票已賣完

Thread[pool-1-thread-2,5,main]票已賣完

tickets

tickets

tickets

tickets

二、synchronized、lock

2.1 synchronized 三種用法

2.1.1 synchronized 修飾執行個體方法

package thread;

/**
 * @author ppc
 *
 */
public class RunnableTest implements Runnable {

	
	Integer count = 20;
	
	synchronized void sale() {
		if(count>0) {
			System.out.println(Thread.currentThread().getName()+"賣了一張票,還剩"+--count+"票。");
		}
	}
	
	@Override
	public void run() {
		while(count>0) {
			sale();//鎖住的是目前對象
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(count==0){
			System.out.println(Thread.currentThread().getName()+"票已賣完。");

		}

	}

	

	public RunnableTest() {
		super();
		// TODO Auto-generated constructor stub
	}
	
	

}
           

測試類:

package thread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

/**
 * @author ppc
 *
 */
public class MainTest {


	//RunnableTest
	public static void main(String[] args) {
		RunnableTest rt = new RunnableTest();//線程執行的時候,鎖住的是rt對象,一條線程擷取rt對象的鎖,執行完sale方法,釋放鎖,其他線程才可以擷取此對象的鎖執行sale方法
		new Thread(rt,"視窗4").start();
		new Thread(rt,"視窗5").start();
		new Thread(rt,"視窗6").start();

	}
}
           

 執行結果:

視窗4賣了一張票,還剩19票。

視窗6賣了一張票,還剩18票。

視窗5賣了一張票,還剩17票。

視窗5賣了一張票,還剩16票。

視窗4賣了一張票,還剩15票。

視窗6賣了一張票,還剩14票。

視窗4賣了一張票,還剩13票。

視窗5賣了一張票,還剩12票。

視窗6賣了一張票,還剩11票。

視窗5賣了一張票,還剩10票。

視窗4賣了一張票,還剩9票。

視窗6賣了一張票,還剩8票。

視窗4賣了一張票,還剩7票。

視窗6賣了一張票,還剩6票。

視窗5賣了一張票,還剩5票。

視窗6賣了一張票,還剩4票。

視窗5賣了一張票,還剩3票。

視窗4賣了一張票,還剩2票。

視窗5賣了一張票,還剩1票。

視窗6賣了一張票,還剩0票。

視窗6票已賣完。

視窗5票已賣完。

視窗4票已賣完。

2.1.2 synchronized 修飾靜态方法

編寫線程類

package thread;

/**
 * @author ppc
 *
 */
public class ThreadTest extends Thread {
	
	 //總票數20
	static Integer count = 20;

	synchronized static void sale() {//鎖住的是目前類 
		
		if(count>0) {
			System.out.println(Thread.currentThread().getName()+"賣了一張票,還剩"+--count+"票。");
		}
	}
	
	@Override
	public void run() {
		while(count>0) {
			sale();	
			try {
				sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
        if(count==0) {
			System.out.println("票已賣完");

		}

	}


	public ThreadTest() {
		super();
		// TODO Auto-generated constructor stub
	}


	public ThreadTest(String name) {
		super(name);
		// TODO Auto-generated constructor stub
	}

	
	
}
           

測試方法參考:

package thread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

/**
 * @author ppc
 *
 */
public class MainTest {


	//ThreadTest
	public static void main(String[] args) {
		//三個視窗同時賣票
		ThreadTest tt1 = new ThreadTest("視窗一");
		ThreadTest tt2 = new ThreadTest("視窗二");
		ThreadTest tt3 = new ThreadTest("視窗三");
		//啟動線程
		tt1.start();
		tt2.start();
		tt3.start();
	
	}
}
           

2.1.3 synchronized 修飾代碼塊

編寫線程類

package thread;

/**
 * @author ppc
 *
 */
public class ThreadTest extends Thread {
	
	//總票數20
	static Integer count = 20;
	
	@Override
	public void run() {
		while(count>0) {
			synchronized (count) {//對餘票加鎖
							//這個鎖住的是靜态變量count,當線程操作count時必須取得count鎖,線程操作完此代碼塊會釋放count鎖
							//也可以用this(synchronized (this) {...}) 鎖住的是目前對象(本案例就是ThreadTest類的目前對象)
				if(count>0) {
					System.out.println(Thread.currentThread().getName()+"賣了一張票,還剩"+--count+"票。");
				}
				
			}
			try {
				sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
        if(count==0) {
			System.out.println("票已賣完");

		}

	}


	public ThreadTest() {
		super();
		// TODO Auto-generated constructor stub
	}


	public ThreadTest(String name) {
		super(name);
		// TODO Auto-generated constructor stub
	}

	
	
}
           

測試方法參考:1.1.2

 注:synchronized 總結

1.當synchronized 注釋執行個體方法時,一個線程通路一個對象其中一個執行個體synchronized方法,其他線程不得通路該對象的

synchronized的執行個體方法,可以通路該對象的靜态synchronized 的方法或者非synchronized方法;

2.多個線程通路同一類多個執行個體對象的同一synchronized靜态方法,是互斥的,因為synchronized修飾靜态方法時鎖的是目前類的class對象鎖,必須一個一個線程執行該synchronized靜态方法(參考2.1.2)

3.synchronized修飾代碼塊時,synchronized(object){....} 當線程通路此代碼塊時必須持有object對象鎖,否則等待,

   synchronized(this){....} 鎖的是本身對象,如果線程通路此代碼塊必須持有本對象的對象鎖,否則等待,synchronized(Object.class) {...}鎖的是Object類,如果線程通路此代碼塊必須持有Object類的對象鎖

注:synchronized的實作原理請參考下面部落格

https://blog.csdn.net/javazejian/article/details/72828483

2.2 Lock

2.2.1 synchronized缺陷以及lock介紹

1.synchronized的缺陷

  synchronized是java中的一個關鍵字,也就是說是Java語言内置的特性。那麼為什麼會出現Lock呢?

  在上面一篇文章中,我們了解到如果一個代碼塊被synchronized修飾了,當一個線程擷取了對應的鎖,并執行該代碼塊時,其他線程便隻能一直等待,等待擷取鎖的線程釋放鎖,而這裡擷取鎖的線程釋放鎖隻會有兩種情況:

  1)擷取鎖的線程執行完了該代碼塊,然後線程釋放對鎖的占有;

  2)線程執行發生異常,此時JVM會讓線程自動釋放鎖。

  那麼如果這個擷取鎖的線程由于要等待IO或者其他原因(比如調用sleep方法)被阻塞了,但是又沒有釋放鎖,其他線程便隻能幹巴巴地等待,試想一下,這多麼影響程式執行效率。

  是以就需要有一種機制可以不讓等待的線程一直無期限地等待下去(比如隻等待一定的時間或者能夠響應中斷),通過Lock就可以辦到。

  再舉個例子:當有多個線程讀寫檔案時,讀操作和寫操作會發生沖突現象,寫操作和寫操作會發生沖突現象,但是讀操作和讀操作不會發生沖突現象。

  但是采用synchronized關鍵字來實作同步的話,就會導緻一個問題:

  如果多個線程都隻是進行讀操作,是以當一個線程在進行讀操作時,其他線程隻能等待無法進行讀操作。

  是以就需要一種機制來使得多個線程都隻是進行讀操作時,線程之間不會發生沖突,通過Lock就可以辦到。

  另外,通過Lock可以知道線程有沒有成功擷取到鎖。這個是synchronized無法辦到的。

  總結一下,也就是說Lock提供了比synchronized更多的功能。但是要注意以下幾點:

  1)Lock不是Java語言内置的,synchronized是Java語言的關鍵字,是以是内置特性。Lock是一個類,通過這個類可以實作同步通路;

  2)Lock和synchronized有一點非常大的不同,采用synchronized不需要使用者去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完之後,系統會自動讓線程釋放對鎖的占用;而Lock則必須要使用者去手動釋放鎖,如果沒有主動釋放鎖,就有可能導緻出現死鎖現象。

2.lock方法介紹

下面我們就來探讨一下java.util.concurrent.locks包中常用的類和接口。

  1.Lock

  首先要說明的就是Lock,通過檢視Lock的源碼可知,Lock是一個接口:

package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}
           

下面來逐個講述Lock接口中每個方法的使用,lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用來擷取鎖的。unLock()方法是用來釋放鎖的。newCondition()這個方法暫且不在此講述,會在後面的線程協作一文中講述。

  在Lock中聲明了四個方法來擷取鎖,那麼這四個方法有何差別呢?

1)lock() 

首先lock()方法是平常使用得最多的一個方法,就是用來擷取鎖。如果鎖已被其他線程擷取,則進行等待。

  由于在前面講到如果采用Lock,必須主動去釋放鎖,并且在發生異常時,不會自動釋放鎖。是以一般來說,使用Lock必須在try{}catch{}塊中進行,并且将釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生。通常使用Lock來進行同步的話,是以下面這種形式去使用的:

Lock lock = ...;
lock.lock();
try{
    //處理任務
}catch(Exception ex){
     
}finally{
    lock.unlock();   //釋放鎖
}
           

2)tryLock() 

tryLock()方法是有傳回值的,它表示用來嘗試擷取鎖,如果擷取成功,則傳回true,如果擷取失敗(即鎖已被其他線程擷取),則傳回false,也就說這個方法無論如何都會立即傳回。在拿不到鎖時不會一直在那等待。

3)tryLock(long time, TimeUnit unit) 

tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的,隻不過差別在于這個方法在拿不到鎖時會等待一定的時間,在時間期限之内如果還拿不到鎖,就傳回false。如果如果一開始拿到鎖或者在等待期間内拿到了鎖,則傳回true。

  是以,一般情況下通過tryLock來擷取鎖時是這樣使用的:

Lock lock = ...;
if(lock.tryLock()) {
     try{
         //處理任務
     }catch(Exception ex){
         
     }finally{
         lock.unlock();   //釋放鎖
     } 
}else {
    //如果不能擷取鎖,則直接做其他事情
}
           

  lockInterruptibly()方法比較特殊,當通過這個方法去擷取鎖時,如果線程正在等待擷取鎖,則這個線程能夠響應中斷,即中斷線程的等待狀态。也就使說,當兩個線程同時通過lock.lockInterruptibly()想擷取某個鎖時,假若此時線程A擷取到了鎖,而線程B隻有在等待,那麼對線程B調用threadB.interrupt()方法能夠中斷線程B的等待過程。

  由于lockInterruptibly()的聲明中抛出了異常,是以lock.lockInterruptibly()必須放在try塊中或者在調用lockInterruptibly()的方法外聲明抛出InterruptedException。

  是以lockInterruptibly()一般的使用形式如下:

public void method() throws InterruptedException {
    lock.lockInterruptibly();
    try {  
     //.....
    }
    finally {
        lock.unlock();
    }  
}
           

 注意,當一個線程擷取了鎖之後,是不會被interrupt()方法中斷的。因為本身在前面的文章中講過單獨調用interrupt()方法不能中斷正在運作過程中的線程,隻能中斷阻塞過程中的線程。

  是以當通過lockInterruptibly()方法擷取某個鎖時,如果不能擷取到,隻有進行等待的情況下,是可以響應中斷的。

  而用synchronized修飾的話,當一個線程處于等待某個鎖的狀态,是無法被中斷的,隻有一直等待下去。

  2.ReentrantLock

  ReentrantLock,意思是“可重入鎖”,關于可重入鎖的概念在下一節講述。ReentrantLock是唯一實作了Lock接口的類,并且ReentrantLock提供了更多的方法。下面通過一些執行個體看具體看一下如何使用ReentrantLock。

2.2.2 lock使用

加個鎖沒有解鎖

package thread;

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

public class LockTest extends Thread{

	private Lock lock = new ReentrantLock(); 
	
	 //總票數20
	static Integer count = 20;


	
	@Override
	public void run() {
		while(count>0) {
			lock.lock();
			try {
				System.out.println(Thread.currentThread()+"獲得對象鎖");
			if(count>0) {
				System.out.println(Thread.currentThread().getName()+"賣了一張票,還剩"+--count+"票。");
			}
				sleep(100);
			} catch (InterruptedException e) {
				System.out.println("報錯了");
			}finally {
				//lock.unlock();
				System.out.println(Thread.currentThread()+"釋放對象鎖");

			}
		}
       if(count==0) {
			System.out.println("票已賣完");

		}

	}


	public LockTest() {
		super();
		// TODO Auto-generated constructor stub
	}


	public LockTest(String name) {
		super(name);
		// TODO Auto-generated constructor stub
	}

	
	
}
           

測試類 

package thread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

/**
 * @author ppc
 *
 */
public class MainTest {

	public static void main(String[] args) {
		LockTest lt1 =new LockTest();
		new Thread(lt1).start();
		new Thread(lt1).start();
		new Thread(lt1).start();

		
	}

}
           

執行結果:

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩19票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩18票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩17票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩16票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩15票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩14票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩13票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩12票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩11票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩10票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩9票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩8票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩7票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩6票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩5票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩4票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩3票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩2票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩1票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩0票。

Thread[Thread-1,5,main]釋放對象鎖

票已賣完

從結果可以看出目前線程獲得了鎖,沒有解鎖,是以隻有這個線程執行,其他線程沒有獲得鎖,無法執行

加上解鎖

package thread;

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

public class LockTest extends Thread{

	private Lock lock = new ReentrantLock(); 
	
	 //總票數20
	 Integer count = 20;


	
	@Override
	public void run() {
		while(count>0) {
			lock.lock();
			try {
				System.out.println(Thread.currentThread()+"獲得對象鎖");
			if(count>0) {
				System.out.println(Thread.currentThread().getName()+"賣了一張票,還剩"+--count+"票。");
			}
				sleep(100);
			} catch (InterruptedException e) {
				System.out.println("報錯了");
			}finally {
				lock.unlock();
				System.out.println(Thread.currentThread()+"釋放對象鎖");

			}
		}
       if(count==0) {
			System.out.println("票已賣完");

		}

	}


	public LockTest() {
		super();
		// TODO Auto-generated constructor stub
	}


	public LockTest(String name) {
		super(name);
		// TODO Auto-generated constructor stub
	}

	
	
}
           

測試類如上 

執行結果:

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩19票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-3,5,main]獲得對象鎖

Thread-3賣了一張票,還剩18票。

Thread[Thread-3,5,main]釋放對象鎖

Thread[Thread-2,5,main]獲得對象鎖

Thread-2賣了一張票,還剩17票。

Thread[Thread-2,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩16票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-3,5,main]獲得對象鎖

Thread-3賣了一張票,還剩15票。

Thread[Thread-3,5,main]釋放對象鎖

Thread[Thread-2,5,main]獲得對象鎖

Thread-2賣了一張票,還剩14票。

Thread[Thread-2,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩13票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-3,5,main]獲得對象鎖

Thread-3賣了一張票,還剩12票。

Thread[Thread-3,5,main]釋放對象鎖

Thread[Thread-2,5,main]獲得對象鎖

Thread-2賣了一張票,還剩11票。

Thread[Thread-2,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩10票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-3,5,main]獲得對象鎖

Thread-3賣了一張票,還剩9票。

Thread[Thread-3,5,main]釋放對象鎖

Thread[Thread-2,5,main]獲得對象鎖

Thread-2賣了一張票,還剩8票。

Thread[Thread-2,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩7票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-3,5,main]獲得對象鎖

Thread-3賣了一張票,還剩6票。

Thread[Thread-3,5,main]釋放對象鎖

Thread[Thread-2,5,main]獲得對象鎖

Thread-2賣了一張票,還剩5票。

Thread[Thread-2,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩4票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-3,5,main]獲得對象鎖

Thread-3賣了一張票,還剩3票。

Thread[Thread-3,5,main]釋放對象鎖

Thread[Thread-2,5,main]獲得對象鎖

Thread-2賣了一張票,還剩2票。

Thread[Thread-2,5,main]釋放對象鎖

Thread[Thread-1,5,main]獲得對象鎖

Thread-1賣了一張票,還剩1票。

Thread[Thread-1,5,main]釋放對象鎖

Thread[Thread-3,5,main]獲得對象鎖

Thread-3賣了一張票,還剩0票。

Thread[Thread-3,5,main]釋放對象鎖

Thread[Thread-2,5,main]獲得對象鎖

票已賣完

Thread[Thread-2,5,main]釋放對象鎖

票已賣完

Thread[Thread-1,5,main]獲得對象鎖

Thread[Thread-1,5,main]釋放對象鎖

票已賣完

以上看出一個線程得到鎖,執行代碼,解鎖,另一個線程獲得鎖,執行...,這個是正确使用

 參考文獻:https://www.cnblogs.com/baizhanshi/p/6419268.html

三、線程的狀态

3.1狀态圖解

多線程--Thread、Runnable、Callable、線程池、synchronized、Lock、線程的狀态一、建立線程 二、synchronized、lock 三、線程的狀态

1.建立(new):當線程被建立的時候隻會短暫的處于這種狀态。

2.就緒(runnable):在這種狀态下,隻要排程器把時間碎片配置設定給線程,線程就可以運作,進入運作狀态

3.運作(running):線程得到cpu時間碎片,執行任務,此時除非此線程自動放棄CPU資源或者有優先級更高的線程進入,線程将一直運作到結束。

4.阻塞(blocked):線程能夠運作,但是有某個條件阻止它的運作,當線程處于阻塞狀态的時候,排程器将忽略線程,不會配置設定給線程任何CPU時間碎片,直到線程重新進入就緒狀态,等待CPU配置設定時間碎片

5.死亡(dead)處于死亡或終止狀态下的線程是不可以排程的,并且不會再得到時間碎片,它的任務已結束,或不再是可運作的

自然終止:正常運作run()方法後終止

異常終止:調用stop()方法讓一個線程終止運作

3.2線程的常用方法

void run()   建立該類的子類時必須實作的方法,在這個方法裡添加任務的實作

void start() 開啟線程的方法,調用此方法,線程開始運作

static void sleep(long t) 釋放CPU的執行權,不釋放鎖,等待時間t以後重新進入就緒狀态,其他線程與目前線程存在資源競争關系不會獲得CPU的時間排程,那些線程依然處在阻塞狀态

static void sleep(long millis,int nanos)

final void wait() 釋放CPU的執行權,釋放鎖,進入等待池,等待喚醒進入阻塞狀态

final void notify() 喚醒等待池中的線程,線程進入阻塞狀态,等待其他任務執行完,擷取CPU時間碎片

final void  notifyAll

()

喚醒正在等待對象螢幕的所有線程。

static void yied()可以對目前線程進行臨時暫停(釋放CPU的執行權),直接進入就緒狀态,其他線程與目前線程存在資源競争關系不會獲得CPU的時間排程,那些線程依然處在阻塞狀态

void join() 等待這個線程死亡,在a線程中調用線程b.join() 要等線程b死亡 線程a才能繼續執行

sleep()方法 之前用了,這裡不再示範了,yied()方法和sleep()差不多也不做示範

示範一下wait(),notifyAll(),join()

wait(),notifyAll()測試

package thread;

import java.util.LinkedList;
import java.util.Queue;

public class WaitTest {

	public static void main(String[] args) {
		Queue queue = new LinkedList<>();
		Producer p1 = new Producer(queue, 2, "Pro 1");
		Producer p2 = new Producer(queue, 2, "Pro 2");
		Consumer c1 = new Consumer(queue, 2, "Con 1");
		Consumer c2 = new Consumer(queue, 2, "Con 2");
		p1.start();
		p2.start();
		c1.start();
		c2.start();
	}
}

class Producer extends Thread{

	private Queue queue;
	private int maxSize;

	@Override
	public void run() {
		while(true) {
			synchronized(queue) {
				try 
				{
					Thread.sleep(200);
					if(queue.size() == maxSize) {
						System.out.println(Thread.currentThread().getName()+"queue已滿");
							queue.notifyAll();
							queue.wait();//釋放對象鎖,其他線程可以擷取對象鎖,操作該對象
					}else {
						double d = Math.random();
						queue.offer(d);
						System.out.println(Thread.currentThread().getName()+"queue添加隊員:"+d);
					}
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	public Producer(Queue queue, int maxSize,String name) {
		super();
		this.queue = queue;
		this.maxSize = maxSize;
		this.setName(name);
	}
	
	
	
}

class Consumer extends Thread{

	private Queue queue;
	private int maxSize;
	
	@Override
	public void run() {
		while(true) {
			synchronized(queue) {
				try 
				{
					Thread.sleep(200);
					if(queue.isEmpty()) {
						System.out.println(Thread.currentThread().getName()+"queue已空");
							queue.notifyAll();
							queue.wait();//釋放對象鎖,其他線程可以擷取對象鎖,操作該對象
					}else {
						Object b = queue.poll();
						System.out.println(Thread.currentThread().getName()+"queue删除隊員:"+b);
					}
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	public Consumer(Queue queue, int maxSize,String name) {
		super();
		this.queue = queue;
		this.maxSize = maxSize;		
		this.setName(name);

	}
	
	
}
           

測試結果:

Pro 1queue添加隊員:0.23488318726417456

Pro 1queue添加隊員:0.15521115372976324

Pro 1queue已滿

Con 2queue删除隊員:0.23488318726417456

Con 2queue删除隊員:0.15521115372976324

Con 2queue已空

Con 1queue已空

Pro 2queue添加隊員:0.9193677478296202

Pro 2queue添加隊員:0.5024437264449279

Pro 2queue已滿

Con 2queue删除隊員:0.9193677478296202

Con 2queue删除隊員:0.5024437264449279

Con 2queue已空

Pro 1queue添加隊員:0.5026723249672198

Pro 1queue添加隊員:0.6084867830243549

Pro 1queue已滿

Pro 2queue已滿

Con 1queue删除隊員:0.5026723249672198

Con 1queue删除隊員:0.6084867830243549

Con 1queue已空

Pro 1queue添加隊員:0.7631470001577331

Pro 1queue添加隊員:0.07019169173384032

Pro 1queue已滿

Con 2queue删除隊員:0.7631470001577331

Con 2queue删除隊員:0.07019169173384032

Con 2queue已空

Con 1queue已空

Pro 2queue添加隊員:0.1655827464569286

Pro 2queue添加隊員:0.6605956773527866

Pro 2queue已滿

Con 2queue删除隊員:0.1655827464569286

Con 2queue删除隊員:0.6605956773527866

Con 2queue已空

.......

join 測試:

LockTest類 :參考2.2.2中加上解鎖的LockTest類

lt1未join到線程main中

public static void main(String[] args) {
		LockTest lt1 =new LockTest();
		lt1.start();
		
		System.out.println("main end");
		
	}
           

測試結果:

main end

Thread[Thread-0,5,main]獲得對象鎖

Thread-0賣了一張票,還剩4票。

Thread[Thread-0,5,main]釋放對象鎖

Thread[Thread-0,5,main]獲得對象鎖

Thread-0賣了一張票,還剩3票。

Thread[Thread-0,5,main]釋放對象鎖

Thread[Thread-0,5,main]獲得對象鎖

Thread-0賣了一張票,還剩2票。

Thread[Thread-0,5,main]釋放對象鎖

Thread[Thread-0,5,main]獲得對象鎖

Thread-0賣了一張票,還剩1票。

Thread[Thread-0,5,main]釋放對象鎖

Thread[Thread-0,5,main]獲得對象鎖

Thread-0賣了一張票,還剩0票。

Thread[Thread-0,5,main]釋放對象鎖

票已賣完

 由結果可以看出main 方法先結束 

lt1線程join main線程中

public static void main(String[] args) {
		LockTest lt1 =new LockTest();
		lt1.start();
		try {
			lt1.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("main end");
		
	}
           

測試結果:

Thread[Thread-0,5,main]獲得對象鎖

Thread-0賣了一張票,還剩4票。

Thread[Thread-0,5,main]釋放對象鎖

Thread[Thread-0,5,main]獲得對象鎖

Thread-0賣了一張票,還剩3票。

Thread[Thread-0,5,main]釋放對象鎖

Thread[Thread-0,5,main]獲得對象鎖

Thread-0賣了一張票,還剩2票。

Thread[Thread-0,5,main]釋放對象鎖

Thread[Thread-0,5,main]獲得對象鎖

Thread-0賣了一張票,還剩1票。

Thread[Thread-0,5,main]釋放對象鎖

Thread[Thread-0,5,main]獲得對象鎖

Thread-0賣了一張票,還剩0票。

Thread[Thread-0,5,main]釋放對象鎖

票已賣完

main end

線程lt1 先結束 main線程再結束