文章目录
简介
通过《图解Java多线程设计模式》学习多线程的使用。
《图解Java多线程设计模式》资源下载
线程的启动
-
利用Thread类的子类的实例创建启动线程。
-
利用Runnable接口的实现类的实例启动线程。
线程启动(1)——利用Thread类的子类
创建PrintThread类重写run()方法
public class PrintThread extends Thread {
private String message;
public PrintThread(String message) {
this.message = message;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(message);
}
}
}
利用PrintThread类启动两个线程:
public class TestThread {
public static void main(String[] args) {
new PrintThread("Good!").start();
new PrintThread("Nice!").start();
}
}
线程启动(2)——利用Runnable接口
Runnable接口包含在java.lang包中,声明如下:
public interface Runnable{
public abstract void run();
}
Runnable接口的实现类必须要实现run方法。
创建Printer类实现Runna接口
public class Printer implements Runnable{
private String message;
public Printer(String message) {
this.message = message;
}
/**
* 当使用实现接口Runnable的对象创建线程时,
* 启动线程会导致在该单独执行的线程中调用对象的run方法。
* 方法run的一般约定是它可以采取任何行动
*/
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(message);
}
}
}
利用Printer类启动两个线程:
public class TestThread {
public static void main(String[] args) {
Runnable runnable=new Printer("Good!");
Thread thread1 =new Thread(runnable);
thread1.start();
new Thread(new Printer("Nice!")).start();
}
}
利用ThreadFactory新启动线程
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class TestThread {
public static void main(String[] args) {
ThreadFactory factory = Executors.defaultThreadFactory();
factory.newThread(new Printer("Good!")).start();
factory.newThread(new Printer("Nice!")).start();
}
}
✨小知识: 线程的终止
java程序的终止是指除守护线程(Daemon Thread)以外的线程全部终止。守护线程是执行后台作业的线程。我们可以通过setDaemon方法把线程设置为守护线程。
线程的暂停
线程Thread类中的sleep方法能够暂停线程的运行。sleep也就是**”休眠“**的意思。sleep方法是Thread类的静态方法。
Thread.sleep(1000)
执行这条语句的线程暂停约1000毫秒(约1秒)。
✨小知识:指定到纳秒(ns)单位
在sleep方法中,停止时间也可以指定到纳秒(10-9秒)单位,语法如下。
Thread.sleep(毫秒,纳秒);
不过,通常情况下Java平台运行环境无法实现这么精准的控制。具体的精度程度依Java平台运行环境而不同。
✨小知识:如何唤醒
如果中途唤醒被Thread.sleep休眠的线程,可以使用interrupt方法。————后面会有内容。
线程的互斥处理
线程A和线程B之间互相竞争而引起的与预期相反的情况称为数据竞争或竞态条件。线程互斥就是为了避免这种情况的发生。例如:如果一个线程正在执行某一部分操作,那么其他线程就不可以再执行这部分操作。
Java使用关键字synchronized
来执行线程的互斥处理。
synchronized方法(同步方法)
如果声明一个方法时,在前面加上关键字synchronized,那么这个方法就只能由一个线程运行。这种方法称为synchronized方法,也称为同步方法。
Bank(银行)类中的deposit(存款)和withdrew(取款)这两个方法就是synchronized方法。
public class Bank{
private int money;
private String name;
public Bank(String name,int money){
this.name = name;
this.money = money;
}
//存款
public synchronized void depoist(int m){
money += m;
}
//取款
public synchronized boolean withdrew(int m){
if(money >= m){
money -= m;
return true;//取款成功
} else {
return false;//取款失败
}
}
}
一个实例中的synchronized方法每次只能有一个线程运行。如果一个线程正在运行synchronized方法,其他线程需要排队等候。每个实例拥有一个独立的锁。
✨小知识:锁和监视
线程的互斥机制称为监视(monitor)。另外,获取锁有时也叫做”拥有(own)监视“或”持有(hold)锁“。
当前线程是否已获取某一个对象的锁可以通过
Thread.holdsLock
方法来确认。当前线程已获取对象obj
的锁时,可使用assert来像下面这样表示出来:
assert Thread.holdsLock(obj);
synchronized代码块
如果只是想让方法中的某一部分由一个线程运行,而非整个方法,可以使用synchronized代码块,格式如下:
synchronized (表达式){
...
}
其中**”表达式“**为获取锁的实例。synchronized代码块用于精准控制互斥处理的执行范围。
▶synchronized实例方法和synchronized代码块
synchronized实例方法是使用this的锁来执行线程的互斥处理的。
//synchronized实例方法
synchronized void method(){}
//synchronized代码块
void method(){
synchronized (this){
...
}
}
▶synchronized静态方法和synchronized代码块
synchronized静态方法是使用该类的类对象的锁来执行线程的互斥处理的。
class Something{
static synchronized void method(){
...
}
}
class Something{
static void method(){
synchronized (Something.class){
...
}
}
}
线程的协作
wait方法——将线程放入等待队列
wait(等待)方法会让线程进入等待队列。执行下面这个语句。
obj.wait()
那么,当前线程便会暂停运行,并进入实例obj的等待队列中。这叫做“线程正在obj上wait”。
若要执行wait方法,线程必须持有锁(这是规则)。但如果线程进入等待队列,便会释放其实例的锁。
✨小知识:等待队列
等待队列是一个虚拟的概念。它既不是实例中的字段,也不是用于获取正在实例上等待的线程的列表的方法。
notify方法——从等待队列中取出线程
notify(通知)方法会将等待队列中的一个线程取出。假设执行下面这条语句。
obj.notify()
那么obj的等待队列中的一个线程便会被选中唤醒,然后退出等待队列。
同wait方法一样,执行notify方法,线程必须持有要调用的实例的锁。
✨小知识:执行notify后的线程状态
notify唤醒的线程并不会在执行notify的一瞬间重新运行。因为在执行notify的那个线程还持有着锁,其他线程无法获取这个实例的锁。
notifyAll方法——从等待队列中取出所有线程
notifyAll(通知大家)方法会将等待队列中的所有线程都取出来。执行下面这条语句。
obj.notifyAll()
在obj实例的等待队列中休眠的所有线程都会被唤醒。同样,执行notifyAll方法的线程持有锁。
✨小知识:如果线程未持有锁会怎么样
如果未持有锁的线程调用
wait
、notify
和notifyAll
,会抛出异常:java.lang.IllegalMonitorStateException
java.util.concurrent包提供了便于多线程编程的可复用性高的类。