1.1 线程的基本概念
什么是线程?
一个线程是一个程序内部的顺序控制流
线程和进程
每个进程都有独立的代码和数据空间(进程上下文),进程之间切换开销大
线程:轻量的进程,同一类线程共享代码和数据空间,每个线程有独立运行的栈和程序计数器(PC),线程切换开销小。
多进程:在操作系统中能够同时运行多个任务(程序)。
多线程:在同一个应用程序中有多个顺序流同时执行。
1.2 Thread类详解
名称 | 说明 |
---|---|
public Thread() | 构造一个新的线程对象,默认名称是Thread-n,n是从0开始递增的整数 |
public Thread(Runnable target) | 构造一个新的线程对象,以一个实现Runnable接口的类的对象为参数。默认名称是Thread-n,n是从0开始递增的整数 |
public static Thread currentThread() | 返回当前正在运行的线程对象 |
public static void yield() | 使当前线程对象暂停,允许别的线程开始运行 |
public static void sleep(long millis) | 使当前线程睡眠指定毫秒数,但此线程并不失去已获得的锁 |
public void start() | 启动线程,JVM将调用此线程的run方法,结果是将同时运行两个线程,当前线程和执行run方法的线程 |
public void run() | Thread子类重写此方法,内容应为该线程应该执行的任务 |
public final void stop() | 停止线程运行,释放该线程占用的对象锁 |
public void interrupt() | 中断此线程 |
public final void join() | 如果此前启动了线程A,调用join方法将等待线程A死亡才能继续执行当前线程。 |
public final join(long millis) | 如果此前启动了线程A,调用join方法将等待指定毫秒数或线程A死亡才能继续执行当前线程 |
public final void setPriority(int newPriority) | 设置线程优先级 |
public final void setDaemon(Boolean on) | 设置是否为后台线程,如果当前运行线程均为后台线程则JVM停止运行。这个方法必须在start()方法前使用 |
public final void checkAccess() | 判断当前线程是否有权利修改调用此方法的线程 |
public void setName(String name) | 更改本线程的名称为指定参数 |
public final boolean isAlive() | 测试线程是否处于活动状态,如果线程启动并没有死亡则返回true |
1.3 Runnable接口
- 只有一个run()方法
- Thread类实现了Runnable接口
- 便于多个线程之间共享资源
- Java不支持多继承,如果已经继承了某个积累,实现Runnable接口来生成多线程
- 以实现Runnable的对象为参数建立新的线程
1.4 线程内部数据共享
- 用同一个实现了Runnable接口的对象作为参数创建多个线程
- 多个线程共享同一对象中的相同数据
```java
public class ShareTargetTester {
public static void main(String[] args) {
} }RunnableThread runnableThread = new RunnableThread();
System.out.println("starting threads");
new Thread(runnableThread, "thread-1").start();
new Thread(runnableThread, "thread-2").start();
new Thread(runnableThread, "thread-3").start();
System.out.println("Threads started,main ends\n");
class RunnableThread implements Runnable { private final int sleepTime;
public RunnableThread() {
sleepTime = (int) (Math.random() * 6000);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " going to sleep for " + sleepTime);
}
}
运行结果:
Starting threads Threads started,main ends
thread-1 going to sleep for 5601 thread-3 going to sleep for 5601 thread-2 going to sleep for 5601
说明:<br />因为是用一个Runnable类型对象创建的3个新线程,这三个线程就共享了这个对象的私有成员sleepTime,在本次运行中,三个线程都休眠了5601毫秒。<br />独立且同时运行的线程有时需要共享一些数据,并且考虑到彼此的状态和动作
模拟售票窗口:
```java
/**
* 模拟三个窗口同时出售200张车票
*/
public class SellTicketsTester {
public static void main(String[] args) {
SellTickets t = new SellTickets();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class SellTickets implements Runnable {
private int tickets = 200;
@Override
public void run() {
while (tickets > 0) {
System.out.println(Thread.currentThread().getName() + " is selling ticket " + tickets--);
}
}
}
运行结果(最后五行):
Thread-1 is selling ticket 5
Thread-0 is selling ticket 6
Thread-1 is selling ticket 2
Thread-2 is selling ticket 3
Thread-0 is selling ticket 1
说明:
在这个例子中,创建三个线程,每个线程调用的是同一个SellTickets对象中的run()方法,访问的是同一个对象中的变量(tickets)
如果是通过创建Thread类的子类来模拟售票过程,再创建3个新线程,则每个线程都会有各自的方法和变量,虽然方法相同,但变量却各有200张票,因而结果将会是各卖出200张票。
1.5 多线程的同步控制
问题
多线程是如何实现同步的?
多线程中如何避免死锁问题?
线程的生命周期是如何的?
每个线程之间优先级如何控制?
- 有时线程之间彼此不独立、需要同步
- 线程间的互斥
- 同时运行的几个线程需要共享一个(些)数据
- 共享的数据,在某一时刻只允许一个线程对其进行操作
- 生产者/消费者问题
- 假设有一个线程负责往数据区写数据,另一个线程从统一数据取读数据,两个线程可以并行执行
- 如果数据区已满,生产者要等消费者读取一些数据后才能写
- 当数据区空时,消费者要等生产者写入一些数据后再取
- 线程间的互斥
- 用两个线程模拟存票、售票过程
- 假定开始售票处并没有票,一个线程往里存票,另一个线程则往出卖票
- 新建一个票类对象,让存票和售票线程都访问他。本例采用两个线程共享同一个数据对象来实现对同一份数据的操作。
1.6 同步和锁的要点
- 只能同步方法,不能同步变量;
- 每个对象只有一个锁;当提到同步时,应该清楚在什么上同步,也就是说在那个对象上同步。
- 类可以拥有同步和非同步方法,非同步方法可以被多个线程自由访问而不受锁的限制
- 如果两个线程使用相同的实例来访问synchronized方法,那么一次只能有一个线程执行方法,另一个需要等待锁
- 线程休眠时,所持有的任何锁都不会释放
- 线程可以获得多个锁,比如,在一个对象的同步方法中调用另外一个对象的同步方法,获得两个对象的同步锁
- 同步损害并发性,应该尽可能的缩小同步范围,同步不仅可以同步整个方法,还可以同步方法中的代码块
- 在使用同步代码块时,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁
并发问题:
作者:LeetCode
链接:https://leetcode-cn.com/problems/print-in-order/solution/an-xu-da-yin-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
1.7 Semaphore关键字(信号量)
用来控制同时访问某个资源的线程数,使用semaphore对象,acquire()方法会减少一个许可,release()会增加一个许可
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreTest {
static Semaphore semaphore = new Semaphore(3);//限制访问资源的线程数,并不实现同步。三个打饭窗口
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Student(semaphore, "学生" + i).start();
}
}
}
class Student extends Thread {
private final Semaphore semaphore;
private final String name;
public Student(Semaphore semaphore, String name) {
this.semaphore = semaphore;
this.name = name;
}
@Override
public void run() {
try {
System.out.println(name + "进入餐厅");
semaphore.acquire(); //获得许可
System.out.println(name + "获得许可");
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(name + "释放许可");
semaphore.release();
}
}
}
Semaphore常见方法:
- acquire(int permits)
- 从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被中断。就好比是一个学生占两个窗口。这同时也对应了相应的release方法。
- release(int permits)
- 释放给定数目的许可,将其返回到信号量。这个是对应于上面的方法,一个学生占几个窗口完事之后还要释放多少
- availablePermits()
- 返回此信号量中当前可用的许可数。也就是返回当前还有多少个窗口可用。
- reducePermits(int reduction)
- 根据指定的缩减量减小可用许可的数目。
- hasQueuedThreads()
- 查询是否有线程正在等待获取资源。
- getQueueLength()
- 返回正在等待获取的线程的估计数目。该值仅是估计的数字。
- tryAcquire(int permits, long timeout, TimeUnit unit)
- 如果在给定的等待时间内此信号量有可用的所有许可,并且当前线程未被中断,则从此信号量获取给定数目的许可。
- acquireUninterruptibly(int permits)
- 从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞。
使用信号量控制线程执行顺序:
class Foo {
public Foo() {
}
Semaphore first=new Semaphore(0);
Semaphore second=new Semaphore(0);
public void first(Runnable printFirst) throws InterruptedException {
printFirst.run();
first.release();
}
public void second(Runnable printSecond) throws InterruptedException {
first.acquire();
printSecond.run();
second.release();
}
public void third(Runnable printThird) throws InterruptedException {
second.acquire();
printThird.run();
}
}
线程同步控制打印foobar
class FooBar {
private final int n;
private boolean isFoo;
public FooBar(int n) {
this.n = n;
}
public synchronized void foo(Runnable printFoo) throws InterruptedException {
/**
* 先执行 printFoo
* 然后让这个线程阻塞,另一个线程调用 printBar
*
*/
for (int i = 0; i < n; i++) {
printFoo.run();
isFoo = true;
this.notify();
if (i < n - 1) {//n=3
this.wait();
}
}
}
public synchronized void bar(Runnable printBar) throws InterruptedException {
if (!isFoo) {
this.wait();
}
for (int i = 0; i < n; i++) {
printBar.run();
//执行完毕,让这个线程等待
this.notify();
if (i < n - 1) {//否则最后一次线程仍然再阻塞
this.wait();
}
}
}
}