图解Java多线程设计模式——Java多线程基础


简介

通过《图解Java多线程设计模式》学习多线程的使用。
《图解Java多线程设计模式》资源下载

线程的启动

  1. 利用Thread类的子类的实例创建启动线程。

  2. 利用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方法的线程持有锁。

小知识:如果线程未持有锁会怎么样

​ 如果未持有锁的线程调用waitnotifynotifyAll,会抛出异常:java.lang.IllegalMonitorStateException

java.util.concurrent包提供了便于多线程编程的可复用性高的类。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值