目录
1.2.2编写RunnableTest类实现Runnable接口
1.3实现Callable接口结合FutureTask类使用
一、创建线程
创建线程的三种方式:继承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线程再结束