多线程--Thread、Runnable、Callable、线程池、synchronized、Lock、线程的状态

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_36634753/article/details/83794023

目录

一、创建线程 

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状态图解

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线程再结束

猜你喜欢

转载自blog.csdn.net/weixin_36634753/article/details/83794023
今日推荐