线程同步指多个线程同时访问某资源时,采用一系列的机制以保证同时最多只能一个线程访问该资源。
为什么需要线程同步呢?
我们举一个最简单的例子来说明为什么需要线程同步。
比如有一本书(有且只有一本),交给多个售货员同时去卖;
如果其中任何一个售货员把这本书给卖了,其他售货员就不能再卖这本书了。
现实生活中,如果要保证该书不会被多个售货员同时卖掉,必须要有一种机制来保证:
比如,售货员应该拿到该书之后才能开始卖书,暂时拿不到的话就只能等该书被退回柜台。
售书的完整的例子可以参考
范例解说Java里的线程概念与线程同步技术
一文
这里,每一个售货员售书可以看作一个线程。欲售的书便是各线程需要共享的资源。
开始售书之前,需要取得该书(资源),取不到情况下等待:
资源取得
开始售书之后,则需要取得对该书的独享控制(不让他人拿到该书):
资源加锁
售完书时,需要通知柜台该书已售出;或者未售出时,把书退回柜台(通知他人可以拿到该书):
资源解锁
synchronized控制线程同步的概念跟此完全一样。
Java里可以使用synchronized来同步代码块或者方法。
同步代码块例:
- synchronized(欲同步的对象obj) {
- 需要同步的代码块
- }
synchronized(欲同步的对象obj) {
需要同步的代码块
}
可以同步代码块。
synchronized (obj)
表示若多个线程同时访问时,只让其中一个线程最先取得obj对象并对其加锁,其它线程则阻塞直到取得obj对象的线程执行完代码块,此时被加锁的obj对象得到释放(解锁),其它线程得到通知取得该book对象继续执行。
很多情况下,可以使用synchronized
(this){...}来同步代码块。但需要注意的是,使用this作为同步对象的话,如果同一个类中存在多个synchronized
(this){...}代码块,其中任何一个synchronized(this)代码块处于被执行状态,则其它线程对其他synchronized(this)代码块的访问也会受到阻塞。
为了说明这个问题,我们举例说明:
HelloSynchronized.java
- publicclass HelloSynchronized {
- publicstaticvoid main(String[] args) {
- //
- HelloSynchronized helloSynchronized = new HelloSynchronized();
- //创建2个线程t1, t2,分别调用HelloSynchronized helloSynchronized的2个方法method1,与method2
- Thread t1 = new Thread(new HelloSynchronizedRunnalbe(helloSynchronized, "method1"), "t1");
- Thread t2 = new Thread(new HelloSynchronizedRunnalbe(helloSynchronized, "method2"), "t2");
- t1.start();
- t2.start();
- }
- //synchronized public void method1() { //同步方法
- publicvoid method1() {
- synchronized (this) { //同步块
- System.out.println(Thread.currentThread().getName()
- + " enter method1");
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- // do nothing
- }
- System.out.println(Thread.currentThread().getName()
- + " exit method1");
- }
- }
- //synchronized public void method2() { //同步方法
- publicvoid method2() {
- synchronized (this) { //同步块
- System.out.println(Thread.currentThread().getName()
- + " enter method2");
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- // do nothing
- }
- System.out.println(Thread.currentThread().getName()
- + " exit method2");
- }
- }
- }
- class HelloSynchronizedRunnalbe implements Runnable {
- private HelloSynchronized helloSynchronized;
- private String methodName;
- public HelloSynchronizedRunnalbe(HelloSynchronized helloSynchronized, String methodName) {
- this.helloSynchronized = helloSynchronized;
- this.methodName = methodName;
- }
- publicvoid run() {
- if (methodName.equals("method1")) {
- helloSynchronized.method1();
- } elseif (methodName.equals("method2")) {
- helloSynchronized.method2();
- }
- }
- }
public class HelloSynchronized {
public static void main(String[] args) {
//
HelloSynchronized helloSynchronized = new HelloSynchronized();
//创建2个线程t1, t2,分别调用HelloSynchronized helloSynchronized的2个方法method1,与method2
Thread t1 = new Thread(new HelloSynchronizedRunnalbe(helloSynchronized, "method1"), "t1");
Thread t2 = new Thread(new HelloSynchronizedRunnalbe(helloSynchronized, "method2"), "t2");
t1.start();
t2.start();
}
//synchronized public void method1() { //同步方法
public void method1() {
synchronized (this) { //同步块
System.out.println(Thread.currentThread().getName()
+ " enter method1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// do nothing
}
System.out.println(Thread.currentThread().getName()
+ " exit method1");
}
}
//synchronized public void method2() { //同步方法
public void method2() {
synchronized (this) { //同步块
System.out.println(Thread.currentThread().getName()
+ " enter method2");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// do nothing
}
System.out.println(Thread.currentThread().getName()
+ " exit method2");
}
}
}
class HelloSynchronizedRunnalbe implements Runnable {
private HelloSynchronized helloSynchronized;
private String methodName;
public HelloSynchronizedRunnalbe(HelloSynchronized helloSynchronized, String methodName) {
this.helloSynchronized = helloSynchronized;
this.methodName = methodName;
}
public void run() {
if (methodName.equals("method1")) {
helloSynchronized.method1();
} else if (methodName.equals("method2")) {
helloSynchronized.method2();
}
}
}
运行结果为:
t1 enter method1
t1 exit
method1
t2 enter method2
t2 exit
method2
等到线程t1结束后,t2才开始运行(t2受到阻塞)
再把synchronized
(this)去掉,运行结果为:
t1 enter method1
t2 enter
method2
t1 exit method1
t2 exit
method2
线程t1,t2同时运行
同步方法例:
- synchronizedprivatevoid sellBook(Book book) {
- ...
- }
synchronized private void sellBook(Book book) {
...
}
这种方法其实相当于
- privatevoid sellBook(Book book) {
- synchronized(this) {
- ...
- }
- }
private void sellBook(Book book) {
synchronized(this) {
...
}
}
由于默认采用this作为同步对象,所以当一个类中有多个synchronized方法时,同样会存在以上问题:即如果有一个线程访问其中某个synchronized方法时,直到该方法执行完毕,其它线程对其它synchronized方法的访问也将受到阻塞。
大家可以把上面的例子稍加改造,去掉代码中的synchronized
(this),改为synchronized public void method1(),synchronized public void
method2()同步形式,运行后会得到同样结果。
多同步代码块synchronized(this){...}的多线程阻塞问题(包括synchronized同步方法),在并发处理的系统中(比如WEB服务器)会严重影响性能,建议慎重使用。可以使用synchronized(obj){...}缩小同步资源对象的范围来解决这个问题。
线程 是一段完成某个特定功能的代码,程序中的执行线程。Java
虚拟机允许应用程序并发地运行多个执行线程。
每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。
进程不同的是,由同名类生成的多个线程共享相同的内存空间和系统资源。
线程与进程的区别:
一个线程是一个程序内部的顺序控制流。
1.
进程:每个进程都有独立的代码和数据空间(进程上下文)
,进程切换的开销大。线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。
2.
一个进程中可以包含多个线程。
本文将介绍以下线程方面的知识:
1,线程的创建
2,线程的状态
3,线程同步
4,线程组
理解线程的最有效的方法是通过实例来理解。下面我们将通过
售货员售书 为例,由浅入深地介绍线程的创建,通信,锁机制等概念。
售货员售书
我们假设一下售货员售书的操作流程:
1,我们假设有20本书,交给2个售货员去卖。
2,售货员可以卖掉任何一本尚未卖出去的书。换句话说,同一本书若被其中一位售出去了,则不能被另外一位再售出了。
清单1:
文件名 | 说明 |
Book.java |
书籍类 |
SellBookRunnable.java |
售书类,线程的创建方法之一,该类实现了Runnable 接口,并实现了 run 方法。 |
SellBookThread.java |
售书类,线程的创建方法之一,该类声明为 Thread 的子类,并重写 Thread 类的 run 方法。 |
CallSellBook.java |
调用类。该类分别介绍了2种不同线程创建的调用方法。 |
Book.java
- publicclass Book {
- private String name;
- privateboolean sold = false;
- public Book(String name) {
- this.name = name;
- }
- public String getName() {
- return name;
- }
- publicvoid setName(String name) {
- this.name = name;
- }
- publicboolean isSold() {
- return sold;
- }
- publicvoid setSold(boolean sold) {
- this.sold = sold;
- }
- }
public class Book {
private String name;
private boolean sold = false;
public Book(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isSold() {
return sold;
}
public void setSold(boolean sold) {
this.sold = sold;
}
}
SellBookRunnable.java
til.List;
- publicclass SellBookRunnable implements Runnable {
- private String saleMan;
- private List<Book> bookList;
- public SellBookRunnable(String saleMan, List<Book> bookList) {
- this.saleMan = saleMan;
- this.bookList = bookList;
- }
- publicvoid run() {
- for (int i = 0; i < bookList.size(); i++) {
- Book book = bookList.get(i);
- sellBook(book);
- }
- }
- /**
- * 售货员卖书。我们这样描述售货员的卖书过程。
- *
- * @param book Book
- */
- privatevoid sellBook(Book book) {
- //从开始售书-到售书完成,使用synchronized (book)保证book不被其他售货员售出
- synchronized (book) {
- if (book.isSold()) {
- return;
- } else {
- try {
- //为了让各线程有执行机会,设置平均售书时间为0.5秒
- Thread.sleep(500);
- } catch (Exception e) {
- }
- //设置已售标志
- book.setSold(true);
- //打印该书已售信息
- System.out.println("[" + saleMan + "]" + book.getName() + " sold out:"
- + book.isSold() + ". by "
- + Thread.currentThread().getName());
- }
- }
- }
- }
import java.util.List;
public class SellBookRunnable implements Runnable {
private String saleMan;
private List<Book> bookList;
public SellBookRunnable(String saleMan, List<Book> bookList) {
this.saleMan = saleMan;
this.bookList = bookList;
}
public void run() {
for (int i = 0; i < bookList.size(); i++) {
Book book = bookList.get(i);
sellBook(book);
}
}
/**
* 售货员卖书。我们这样描述售货员的卖书过程。
*
* @param book Book
*/
private void sellBook(Book book) {
//从开始售书-到售书完成,使用synchronized (book)保证book不被其他售货员售出
synchronized (book) {
if (book.isSold()) {
return;
} else {
try {
//为了让各线程有执行机会,设置平均售书时间为0.5秒
Thread.sleep(500);
} catch (Exception e) {
}
//设置已售标志
book.setSold(true);
//打印该书已售信息
System.out.println("[" + saleMan + "]" + book.getName() + " sold out:"
+ book.isSold() + ". by "
+ Thread.currentThread().getName());
}
}
}
}
SellBookThread.java
- import java.util.List;
- publicclass SellBookThread extends Thread {
- private String saleMan;
- private List<Book> bookList;
- public SellBookThread(String saleMan, List<Book> bookList) {
- this.saleMan = saleMan;
- this.bookList = bookList;
- }
- publicvoid run() {
- for (int i = 0; i < bookList.size(); i++) {
- Book book = bookList.get(i);
- sellBook(book);
- }
- }
- /**
- * 售货员卖书。我们这样描述售货员的卖书过程。
- *
- * @param book Book
- */
- privatevoid sellBook(Book book) {
- //从开始售书-到售书完成,使用synchronized (book)保证book不被其他售货员售出
- synchronized (book) {
- if (book.isSold()) {
- return;
- } else {
- try {
- //为了让各线程有执行机会,设置平均售书时间为0.5秒
- Thread.sleep(500);
- } catch (Exception e) {
- }
- //设置已售标志
- book.setSold(true);
- //打印该书已售信息
- System.out.println("[" + saleMan + "]" + book.getName() + " sold out:"
- + book.isSold() + ". by "
- + Thread.currentThread().getName());
- }
- }
- }
- }
import java.util.List;
public class SellBookThread extends Thread {
private String saleMan;
private List<Book> bookList;
public SellBookThread(String saleMan, List<Book> bookList) {
this.saleMan = saleMan;
this.bookList = bookList;
}
public void run() {
for (int i = 0; i < bookList.size(); i++) {
Book book = bookList.get(i);
sellBook(book);
}
}
/**
* 售货员卖书。我们这样描述售货员的卖书过程。
*
* @param book Book
*/
private void sellBook(Book book) {
//从开始售书-到售书完成,使用synchronized (book)保证book不被其他售货员售出
synchronized (book) {
if (book.isSold()) {
return;
} else {
try {
//为了让各线程有执行机会,设置平均售书时间为0.5秒
Thread.sleep(500);
} catch (Exception e) {
}
//设置已售标志
book.setSold(true);
//打印该书已售信息
System.out.println("[" + saleMan + "]" + book.getName() + " sold out:"
+ book.isSold() + ". by "
+ Thread.currentThread().getName());
}
}
}
}
CallSellBook.java
- import java.util.ArrayList;
- import java.util.List;
- //该类调用SellBookXxx类
- publicclass CallSellBook {
- /**
- * 用线程模拟这个售书的过程
- */
- publicstaticvoid main(String[] args) {
- //方法1:
- callSellBookThread();
- //or
- //方法2:
- //callSellBookRunnable();
- }
- //调用SellBookRunnable(Runnable接口实现类)模拟售书过程
- publicstaticvoid callSellBookThread() {
- List <Book>bookList = getBookListForSale();
- //将预售书籍清单交给售货员SaleMan1
- Thread t1 = new SellBookThread("SaleMan1", bookList);
- //将预售书籍清单交给售货员SaleMan2
- Thread t2 = new SellBookThread("SaleMan2", bookList);
- //售货员SaleMan1开始售书
- t1.start();
- //售货员SaleMan2开始售书
- t2.start();
- }
- //调用SellBookRunnable(Runnable接口实现类)模拟售书过程
- publicstaticvoid callSellBookRunnable() {
- List <Book>bookList = getBookListForSale();
- //将预售书籍清单交给售货员SaleMan1
- Thread t1 = new Thread(new SellBookRunnable("SaleMan1", bookList));
- //将预售书籍清单交给售货员SaleMan2
- Thread t2 = new Thread(new SellBookRunnable("SaleMan2", bookList));
- //售货员SaleMan1开始售书
- t1.start();
- //售货员SaleMan2开始售书
- t2.start();
- }
- //准备预售书籍
- publicstatic List<Book> getBookListForSale() {
- List <Book>bookList = new ArrayList();
- for (int i = 0; i < 20; i++) {
- Book book = new Book("Book" + i);
- bookList.add(book);
- }
- return bookList;
- }
- }
import java.util.ArrayList;
import java.util.List;
//该类调用SellBookXxx类
public class CallSellBook {
/**
* 用线程模拟这个售书的过程
*/
public static void main(String[] args) {
//方法1:
callSellBookThread();
//or
//方法2:
//callSellBookRunnable();
}
//调用SellBookRunnable(Runnable接口实现类)模拟售书过程
public static void callSellBookThread() {
List <Book>bookList = getBookListForSale();
//将预售书籍清单交给售货员SaleMan1
Thread t1 = new SellBookThread("SaleMan1", bookList);
//将预售书籍清单交给售货员SaleMan2
Thread t2 = new SellBookThread("SaleMan2", bookList);
//售货员SaleMan1开始售书
t1.start();
//售货员SaleMan2开始售书
t2.start();
}
//调用SellBookRunnable(Runnable接口实现类)模拟售书过程
public static void callSellBookRunnable() {
List <Book>bookList = getBookListForSale();
//将预售书籍清单交给售货员SaleMan1
Thread t1 = new Thread(new SellBookRunnable("SaleMan1", bookList));
//将预售书籍清单交给售货员SaleMan2
Thread t2 = new Thread(new SellBookRunnable("SaleMan2", bookList));
//售货员SaleMan1开始售书
t1.start();
//售货员SaleMan2开始售书
t2.start();
}
//准备预售书籍
public static List<Book> getBookListForSale() {
List <Book>bookList = new ArrayList();
for (int i = 0; i < 20; i++) {
Book book = new Book("Book" + i);
bookList.add(book);
}
return bookList;
}
}
执行CallSellBook
[SaleMan1]Book0 sold
out:true. by Thread-0
[SaleMan2]Book1 sold out:true. by
Thread-1
[SaleMan2]Book2 sold out:true. by Thread-1
[SaleMan2]Book3 sold
out:true. by Thread-1
[SaleMan2]Book4 sold out:true. by
Thread-1
[SaleMan2]Book5 sold out:true. by Thread-1
[SaleMan1]Book6 sold
out:true. by Thread-0
[SaleMan1]Book7 sold out:true. by
Thread-0
[SaleMan1]Book8 sold out:true. by Thread-0
[SaleMan1]Book9 sold
out:true. by Thread-0
[SaleMan1]Book10 sold out:true. by
Thread-0
[SaleMan1]Book11 sold out:true. by Thread-0
[SaleMan2]Book12 sold
out:true. by Thread-1
[SaleMan2]Book13 sold out:true. by
Thread-1
[SaleMan2]Book14 sold out:true. by Thread-1
[SaleMan2]Book15 sold
out:true. by Thread-1
[SaleMan2]Book16 sold out:true. by
Thread-1
[SaleMan2]Book17 sold out:true. by Thread-1
[SaleMan1]Book18 sold
out:true. by Thread-0
[SaleMan1]Book19 sold out:true. by Thread-0
线程的创建
创建新执行线程有两种方法。
方法一种方法是将类声明为 Thread
的子类。该子类应重写 Thread 类的 run
方法。事实上类Thread本身也实现了接口Runnable,所以我们可以同过继承Thread类实现线程体。
参考:
SellBookThread.java 与
CallSellBook.java
另一种方法是声明实现 Runnable
接口的类。该类然后实现 run 方法。
参考:
SellBookRunnable.java 与
CallSellBook.java
线程的状态
线程有四种状态:创建状态(New),可运行状态(Runnable),阻塞状态(Blocked),死亡状态(Dead)。
创建状态(New):
当执行完
Thread
t1 = new SellBookThread("SaleMan1",
bookList);
语句之后,则t1处于创建状态(New)。此时t1并未真正运行。
可运行状态(Runnable):
当Thread
t1被创建,并执行完
t1.start();
语句之后,t1就处于可运行状态(Runnable)。此时,系统为线程t1分配其所需的系统资源。并对t1加以调用(或者根据任务调度情况准备调用)。
阻塞状态(Blocked):
由于以下原因:
1)
调用了sleep()方法;
2) 调用了suspend()方法(该方法已不推荐使用);
3) 为等待条件锁,调用wait()方法等;
4)
输入输出,或消息发生阻塞;
使得线程处于阻塞状态(Blocked)。处于该状态的线程即使处理器空闲,也不会得到执行。
死亡状态(Dead):
死亡状态(Dead)可以为自然死亡(线程运行完毕),或者调用了stop()方法(该方法已不推荐使用)。
线程的优先级:
可以通过Thread类的
void setPriority(int
newPriority)
方法为线程设置优先级。但是不能保证高优先级的线程就会被先运行。
线程组:
可以通过
ThreadGroup group = new
ThreadGroup(groupName);
Thread t1 = new Thread(ThreadGroup g, Runnable
r1);
Thread t1 = new Thread(ThreadGroup g, Runnable
r2);
等方法把多个线程加到一个线程组里去,这样可以通过ThreadGroup对这些线程进行某些统一操作,
例如:group.interrupt();中断该组所有线程。
线程unchecked异常处理器:
可以通过:
public void static
Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)
方法为所有线程指定一个unchecked异常处理器,该处理器必须实现UncaughtExceptionHandler接口。
线程同步:
线程同步指多个线程同时访问某资源时,采用一系列的机制以保证同时最多只能一个线程访问该资源。
线程同步是多线程中必须考虑和解决的问题,因为很可能发生多个线程同时访问(主要是写操作)同一资源,如果不进行线程同步,很可能会引起数据混乱,造成线程死锁等问题。
使用synchronized同步线程。
在J2SE5.0之前,只能使用synchronized来同步线程。可以使用synchronized来同步代码块或者方法。
同步代码块例:
synchronized(欲同步的对象obj)
{需要同步的代码块}可以同步代码块。
参考:
SellBookThread.java
- privatevoid sellBook(Book book) {
- synchronized (book) {
- ...
- }
- }
private void sellBook(Book book) {
synchronized (book) {
...
}
}
该例synchronized (book)
表示若多个线程同时访问时,只让其中一个线程最先取得book对象,其它线程则阻塞直到代码块执行完毕book对象被释放后,其它线程才能取得该book对象继续执行。
很多情况下,可以使用synchronized
(this){...}来同步代码块。但需要注意的是,使用this作为同步对象的话,如果同一个类中存在多个synchronized
(this){...}代码块,其中任何一个synchronized(this)代码块处于被执行状态,则其它线程对其他synchronized(this)代码块的访问也会受到阻塞。
同步方法例:
- synchronizedprivatevoid sellBook(Book book) {
- ...
- }
synchronized private void sellBook(Book book) {
...
}
这种方法其实相当于
- privatevoid sellBook(Book book) {
- synchronized(this) {
- ...
- }
- }
private void sellBook(Book book) {
synchronized(this) {
...
}
}
由于默认采用this作为同步对象,所以当一个类中有多个synchronized方法时,同样会存在以上问题:即如果有一个线程访问其中某个synchronized方法时,直到该方法执行完毕,其它线程对其它synchronized方法的访问也将受到阻塞。
有关synchronized详细说明我们将在其它文章中加以说明。
使用java.util.concurrent.locks.ReentrantLock和java.util.concurrent.locks.ReentrantReadWriteLock类同步线程。
J2SE5.0加入了ReentrantLock和ReentrantReadWriteLock可以对线程进行同步,这里举一个最简单的例子对其加以说明:
- class X {
- privatefinal ReentrantLock lock = new ReentrantLock();
- // ...
- publicvoid m() {
- lock.lock(); // block until condition holds
- try {
- // ... method body
- } finally {
- lock.unlock()
- }
- }
- }
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
其它J2SE5.0新导入的有关线程的相关接口/类:
java.util.concurrent.Future
Future接口可以保持/取得异步执行的结果值
java.util.concurrent.Callable
类似于Runnable接口。但Runnable不能返回值,也不能抛出checked异常
java.util.concurrent.ExecutorService
该接口继承了Executor接口。可以通过submit方法把Runnable,Callable对象转换为Future
形式。
java.util.concurrent.FutureTask
该类实现了Runnable和Future接口。提供异步执行的取消以及异步执行结果的取得等功能。
java.util.concurrent.Executor
执行指定的Runnable对象
java.util.concurrent.Executors
工具类。提供静态方法可以创建Executor,ExecutorService,Callable等对象。可以通过newCachedThreadPool()等方法简单创建线程池。