Thread实现方式一:
1.继承Thread,创建Thread的子类
2.重写子类的run方法
3.创建该子类的对象
4.启动线程(对象),对象调用 start()方法
注意事项:
1. 一个Thread类(Thread子类)对象 代表 一个线程
2. 为什么我们重写Thread类中的run方法
只有Thread run()方法中的代码,才会执行在子线程中。为了保证子线程中运行的是我们想要在子线程中运行的代码.
3. 但是,如果想要让代码在子线程中运行,代码并非一定要写在run方法方法体中。 对于定义在该Thread子类中其他方法方法体中的代码,也可以运行在子线程。换句话说,一个方法被哪个线程中的代码调用,被调用的方法,就运行在调用它的线程中。
4,启动线程,必须使用start()方法来启动,这样才能使Thread中的run方法运行在子线程中。如果通过调用run方法,来执行Thread的run方法代码,这仅仅只是普通的方法调用,无法启动线程。
5. 同一个Thread或Thread子类对象(代表同一个线程),只能被启动一次。如果,我们要启动多个线程,只能创建多个线程对象,并启动这些线程对象一次且仅一次。
public class Demo1 {
public static void main(String[] args) {
//3. 创建该子类对象(该Thread子类对象,代表一个线程)
MyFirstThread thread = new MyFirstThread();
//4. 启动线程(对象)
thread.start();
// 错误的启动线程的方式,如果直接调用run方法,这仅仅相当于,在main所在线程中,调用了一下run方法
//thread.run();
// 试图将一个线程启动多次
//thread.start(); // IllegalThreadStateException
//要启动多个线程,必须创建多个线程对象
MyFirstThread second = new MyFirstThread();
second.start();
System.out.println("second start");
}
}
//1. 定义Thread的子类
class MyFirstThread extends Thread {
//2. 在子类中重写Thread的run方法(要在线程中执行的代码,都写在run方法中)
@Override
public void run() {
// 在该run方法中,写我们自己要运行在子线程中的代码
System.out.println("hello thread");
// 在run方法中,调用的其他方法,也会运行在子线程中
testCall();
}
private void testCall() {
System.out.println("testCall");
}
}
线程信息API
1.线程名
设置或获取线程的名称
public final String getName()
public final void setName(String name)
2.static Thread currentThread()
返回对当前正在执行该方法的线程对象的引用。
对于currentThread()方法而言,该方法在哪个线程中被调用,哪个线程就是该方法的当前线程。
public class Demo1Name {
public static void main(String[] args) {
NameThread nameThread = new NameThread();
// 获取线程名称
System.out.println(nameThread.getName()); //Thread-0
//修改线程名称
nameThread.setName("NameThread");
System.out.println(nameThread.getName());
//启动线程
//nameThread.start();
// 利用Thread类中到的静态方法currentThread(),获取到了main所在线程的名称
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
class NameThread extends Thread {
@Override
public void run() {
System.out.println(this.getName() + ": hello");
}
}
3.优先级
多线程的优先级
public final int getPriority()
public final void setPriority(int priority)
注意事项:
1.多线程的优先级的取值范围 1 <= priority <=10
2.线程的默认优先级为5
3.然而, 给线程对象设置优先级,并没有什么用。我们在java语言中设置的线程优先级, 它仅仅只能被看做是一种"建议"(对操作系统的建议),实际上,操作系统本身,有它自己的一套线程优先级 (静态优先级 + 动态优先级)
4.结论,千万不要试图通过设置线程的优先级,控制线程的执行的先后顺序
线程控制API
1.public static native void sleep(long millis)
被native修饰的方法,称之为本地方法,本地方法都不是由java语言实现的。
1. 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
2. 指定的休眠时间是以毫秒为单位的休眠时间
public class Demo1Sleep {
public static void main(String[] args) {
SleepThread sleepThread = new SleepThread();
sleepThread.start();
}
}
class SleepThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello, sleep");
}
}
2. public final void join()
当前线程(谁)等待该线程(谁)执行终止。
线程对象.join()
该线程:在哪个线程对象上调用join方法,该线程指的就是哪个线程对象所表示的线程
(1). 谁等待? 当前线程等待, join方法,在哪个线程中被调用,哪个线程就等待
(2). 等待谁? 该线程,在哪个线程对象上调用join方法,等待的就是哪个线程对象的线程。
public class Demo2Join {
public static void main(String[] args) throws InterruptedException {
// 创建线程对象
JoinThread joinThread = new JoinThread();
// 启动线程对象所表示的线程
joinThread.start();
// 使用join方法
// 在main线程中被调用,main线程就是join方法本次调用的当前线程
// 该线程指的就是joinThread线程
joinThread.join();
//begin
//runnung
//end
//main end
System.out.println("main end");
}
}
class JoinThread extends Thread {
@Override
public void run() {
System.out.println("begin");
try {
Thread.sleep(5000);
System.out.println("runnung");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end");
}
}
3. public static native void yield()
暂时让当前线程放弃cpu的执行权
(1).暂停当前正在执行的线程对象(自己放弃cpu的使用权,yield方法可以实现)
( 2).并执行其他线程。(并执行其他线程, yield方法并未实现)
public class Demo3Yield {
public static void main(String[] args) {
YieldThread yieldThread1 = new YieldThread();
yieldThread1.setName("yield-1");
YieldThread yieldThread2 = new YieldThread();
yieldThread2.setName("YIELD-2");
yieldThread1.start();
yieldThread2.start();
}
}
class YieldThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ": i = " + i);
//礼让一下 cpu的使用权,但是下次谁使用cpu未确定,还是需要公平竞争
Thread.yield();
}
}
}
4.public void interrupt()
中断线程的阻塞过程。
(1).如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的
join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法,在这个过程中受阻,调用
interrupt方法会打断当前的阻塞
(2).线程将收到一个 InterruptedException。
public class Demo5Interrupt {
public static void main(String[] args) {
// 创建线程对象
InterruptThread thread = new InterruptThread();
//启动线程
thread.start();
// 打断 子线程的阻塞
thread.interrupt();
//结果:
//begin
//被打断了
//after interrupted
}
}
class InterruptThread extends Thread {
@Override
public void run() {
System.out.println("begin");
try {
Thread.sleep(5000);
System.out.println("end");
Thread.sleep(5000);
//还有一堆代码
} catch (InterruptedException e) {
System.out.println("被打断了");
}
System.out.println("after interrupted");
}
}
Thread实现方式二
1.定义实现Runnable接口的子类
2.实现Runnable接口的run方法
3.创建该子类对象
4.创建Thread对象,将创建好的Runnable子类对象作为初始化参数,传递给Thread对象
5.启动Thread对象(启动线程)
注意事项:
1. Runnable接口子类的run()方法代码,会运行在子线程当中。
2. 在线程的第二种实现方式中,我们自己定义子类,实现Runnable接口的run方法,
将要在子线程中执行的代码放在run()方法中
3. 但是,Runnable子类对象并不代表线程,它只代表要在线程中执行的任务。 把线程(Thread对象代表线程) 和在线程上执行的任务(Ruannable子类对象)分开描述。
public class Demo1Second {
public static void main(String[] args) {
//3. 创建Runnable接口子类对象
MyRunnable myRunnable = new MyRunnable();
// 4. 创建Thread类对象,并将Runnable 子类对象作为初始化参数,传递给Thread对象
Thread thread = new Thread(myRunnable);
// 5. 启动线程
thread.start();
}
}
// 1. 定义实现Runnable接口的子类
class MyRunnable implements Runnable {
// 2. 实现接口的run方法
@Override
public void run() {
System.out.println("hello, runnable");
}
}
多线程的数据安全问题
两种思路构造原子操作(一个线程对共享数据的访问一次完成):
第1种思路:阻止线程切换,就不会发生多线程数据安全问题。思路没问题,但是我们做不到,这涉及到线程调度,在抢占式线程调度中做不到,线程调度有系统决定。
第2种思路:我们无法阻止线程切换,但是我们换个思路,我们给共享变量加一把锁,利用锁来实现原子操作。 从而保证:
a. 只有加锁的线程,能够访问到共享变量
b. 而且在加锁线程没有完成对共享共享变量的一组操作之前,不会释放锁
c. 只要不释放锁.其他线程即使被调度执行也无法访问共享变量。
所以,解决多线程数据安全问题 ——> 如何在java语言层面,加锁构造出原子操作?
同步代码块
synchronized (锁对象) {
一组要作为原子操作的代码(需要访问共享数据)
}
同步代码块的细节:
a. synchronized代码块中的 锁对象,可以是java语言中的任意对象(java语言中的任意一个对象,都可以充当锁的角色,仅限于synchronized代码块中):
1)因为java中所有对象,内部都存在一个标志位,表示加锁和解锁的状态
2)所以其实锁对象,就充当着锁的角色, 所谓的加锁解锁,其实就是设置锁对象的标志位,来表示加锁解锁的状态。
b. 我们的代码都是在某一条执行路径(某一个线程中运行),当某个线程执行到synchronized代码块时,
会尝试在当前线程中,对锁对象加锁
1) 此时,如果锁对象处于未加锁状态,jvm就会设置锁对象的标志位(加锁),并在锁对象中记录,是哪个线程加的锁。 然后,让加锁成功的当前线程,执行同步代码块中的代码
2) 此时,如果锁对象已经被加锁,且加锁线程不是当前线程,系统会让当前线程处于阻塞状态(等着), 直到加锁线程,执行完了对共享变量的一组操作,并释放锁
c. 加锁线程何时释放锁?
当加锁线程,执行完了同步代码块中的代码(对共享变量的一组操作),在退出同步代码块之前, jvm自动清理锁对象的标志位,将锁对象变成未上锁状态(释放锁)。
其实,加锁是在完成线程同步。
所以,最终我们是通过线程同步,来解决多线程的数据安全问题的。
线程同步的优缺点:
优点:解决了多线程线程安全问题。
缺点:相比于异步,因为等待锁资源而引发的阻塞,降低了程序运行效率。
public class Demo1 {
public static void main(String[] args) {
// 表示售票任务
SalesTask salesTask = new SalesTask();
Thread window1 = new Thread(salesTask);
Thread window2 = new Thread(salesTask);
Thread window3 = new Thread(salesTask);
window1.start();
window2.start();
window3.start();
}
}
// 线程的第二种方式,很好的实现了多线程的数据共享
class SalesTask implements Runnable {
int tickets = 100;
// 定义一个锁对象
Object lockObj = new Object();
@Override
public void run() {
while (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 一次完整卖票过程
synchronized (lockObj) {
// synchronized代码块中的代码,是一组原子操作
if (tickets > 0) { // double check, 双检查机制
System.out.println(Thread.currentThread().getName() + "售出了第" + this.tickets-- + "张票");
}
}
}
}
}
注意,对同一个共享变量的访问,必须使用同一个锁对象。
同步方法
整个方法体是一个同步代码块,其效果等价下面的同步代码块
synchronized(锁对象对象) {
}
同步方法的锁对象是谁呢?
对于普通成员方法而言: 同步方法的锁对象是 this,当前对象(其锁对象是隐式给出的)
对象名.方法()
静态方法,可以不可以是同步方法呢? 可以
类名.静态方法() 静态方法依赖于类而存在,而jvm中一个类对应一个Class对象,所以一个静态方法锁对象就是表示静态方法所属类的Clsss对象(隐式给出)
*
public class Demo1 {
public static void main(String[] args) {
// 表示售票任务
SalesTask salesTask = new SalesTask();
Thread window1 = new Thread(salesTask);
Thread window2 = new Thread(salesTask);
Thread window3 = new Thread(salesTask);
window1.start();
window2.start();
window3.start();
}
}
class SalesTask implements Runnable {
int tickets = 100;
// 定义一个锁对象
Object lockObj = new Object();
@Override
public void run() {
while (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 有票就买一张票
this.salesingleTicket();
}
}
// 同步方法: 整个方法体是一个同步代码块
private synchronized void salesingleTicket() {
// 一次完整卖票过程
// synchronized代码块中的代码,是一组原子操作
if (tickets > 0) { // double check, 双检查机制
System.out.println(Thread.currentThread().getName() + "售出了第" + this.tickets-- + "张票");
}
}
// 定义静态的同步方法
public static synchronized void testStatic() {}
}
Lock锁对象 VS synchoronized 锁对象
区别
1. synchronized 锁对象,只提供了用来模拟锁状态的标志位(加锁和释放锁),但是加锁和释放锁的行为,都是由jvm隐式完成(和synchronized 锁对象没关系), 所以synchronized 锁对象不是一把完整的锁 。
2.一个Lock对象,就代表一把锁,而且还是一把完整的锁。 Lock对象,它如果要实现加锁和释放锁,不需要synchronized关键字配合,它自己就可以完成
Lock(接口):
lock() 加锁
unlock() 释放锁
联系:
都可以实现线程同步
1. synchronized(锁对象) {
需要同步的代码
}
2. lock.lock()
需要同步的代码
lock.unlock()
利用线程同步,解决多线程的数据安全问题,有两种方式
- synchronized + java中任意对象
- Lock锁对象
当我们要实现线程同步的时候,究竟选择哪种方式呢? 推荐使用 synchronized + java对象完成线程同步
3. 两种方式,实现的效果是相同,因为synchronize + java对象简单,易用。
4. 虽然,在jdk1.5, 1.6等早期版本中,Lock锁效率高一些,synchronized方式效率低一些,现在两种方式加锁,效率上的区别,几乎可以忽略。
Lock锁比较标准的用法
Lock lock = new ReentrantLock();
lock.lock();
try {
// access the resource protected by this lock
} finally {
// 不管异常还是正常执行,都可以正确的释放锁
lock.unlock();
}
public class Demo1 {
public static void main(String[] args) {
// 表示售票任务
SalesTask salesTask = new SalesTask();
Thread window1 = new Thread(salesTask);
Thread window2 = new Thread(salesTask);
Thread window3 = new Thread(salesTask);
window1.start();
window2.start();
window3.start();
}
}
// 线程的第二种方式,很好的实现了多线程的数据共享
class SalesTask implements Runnable {
int tickets = 100;
// 定义一个Lock锁对象
Lock lock = new ReentrantLock();
@Override
public void run() {
while (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 利用Lock锁完成线程同步
lock.lock();
try {
if (tickets > 0) { // double check, 双检查机制
System.out.println(Thread.currentThread().getName() + "售出了第" + this.tickets-- + "张票");
}
} finally {
lock.unlock();
}
}
}
}
同步另一个弊端:如果出现了嵌套锁,可能产生死锁
比如: 某个线程要同时持有两把锁lockA 和 lockB两把锁,换个说法, 该线程,成功持有lockA锁的情况下,在持有lockB锁:
synchronized (lockA) {
//当某线程的代码,执行到这里
synchronized (lockB) {
// 执行到这里,意味着当前线程在持有lockA锁的情况下,又持有了lockB这把锁,所以此时当前线程就同时持有两把锁
}
}
解决死锁问题的第一种方式:调整获取锁的顺序,让多线程获取锁的顺序相同
public class Demo1 {
public final static Object lockA = new Object();
public final static Object lockB = new Object();
public static void main(String[] args) {
ABThread abThread = new ABThread();
BAThread baThrad = new BAThread();
abThread.start();
baThrad.start();
}
}
// 向访问共享变量,在访问打印机,将对共享变量计算的结果发送到打印机
class ABThread extends Thread {
@Override
public void run() {
// 首先,尝试访问共享变量
synchronized (Demo1.lockA) {
try {
Thread.sleep(100);
System.out.println("ABThread 加 lockA锁");
} catch (InterruptedException e) {
e.printStackTrace();
}
// 访问共享变量做计算,得到一些结果
// 卡在这里
synchronized (Demo1.lockB) {
// 把共享变量计算的结果发送到打印机来打印
System.out.println("ABThread 加 lockB锁");
}
}
}
}
// 先获取打印机的访问权,在尝试访问共享变量,将共享变量计算结果发送到打印机打印
class BAThread extends Thread {
@Override
public void run() {
// 首先,尝试访问打印机
synchronized (Demo1.lockA) {
//访问共享变量, 计算出结果,把结果发送到打印机打印
try {
Thread.sleep(100);
System.out.println("BAThread 加 lockA锁");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Demo1.lockB) {
// 获取打印机的访问权
System.out.println("BAThread 加 lockB锁");
}
}
}
}
解决死锁问题的第二种方式:要么一个线程要么同时持有所有需要的多把锁,要么一把锁都不加。所以,第二种解决方案的实质,就是把加多把锁作为一个原子操作。
在定义一把新的锁,利用这把锁(synchronized + 该对象),实现将加多把锁的操作变成一个原子操作。
public class Demo1 {
public final static Object lockA = new Object();
public final static Object lockB = new Object();
public final static Object allLock = new Object();
public static void main(String[] args) {
ABThread abThread = new ABThread();
BAThread baThrad = new BAThread();
abThread.start();
baThrad.start();
}
}
// 向访问共享变量,在访问打印机,将对共享变量计算的结果发送到打印机
class ABThread extends Thread {
@Override
public void run() {
synchronized (Demo1.allLock) {
// 首先,尝试访问共享变量
synchronized (Demo1.lockA) {
try {
Thread.sleep(100);
System.out.println("ABThread 加 lockA锁");
} catch (InterruptedException e) {
e.printStackTrace();
}
// 访问共享变量做计算,得到一些结果
// 卡在这里
synchronized (Demo1.lockB) {
// 把共享变量计算的结果发送到打印机来打印
System.out.println("ABThread 加 lockB锁");
}
}
}
}
}
// 先获取打印机的访问权,在尝试访问共享变量,将共享变量计算结果发送到打印机打印
class BAThread extends Thread {
@Override
public void run() {
synchronized (Demo1.allLock) {
// 首先,尝试访问打印机
synchronized (Demo1.lockB) {
// 获取打印机的访问权
try {
Thread.sleep(100);
System.out.println("BAThread 加 lockB锁");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Demo1.lockA) {
//访问共享变量, 计算出结果,把结果发送到打印机打印
System.out.println("BAThread 加 lockA锁");
}
}
}
}
}
wait()方法
作用:
导致当前线程等待 。在哪个线程中调用wait()方法,当前线程就会处于阻塞状态。
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,才可以唤醒被wait()阻塞的线程。
调用条件:
当前线程必须拥有此对象监视器(锁对象 Monitor) ,即当前线程必须持有该对象的锁。
调用时发生了什么:
该线程发布对此对象监视器的所有权并等待,。当前线程首先放弃对锁对象的持有,阻塞自己,让自己处于等待状态。
唤醒条件:
直到其他线程通过调用 notify 方法或 notifyAll 方法 通知 在此对象的监视器上(锁对象) 等待的线程 醒来。
如果要唤醒,在某对象上阻塞的线程,必须在其他线程中,在同一(线程阻塞的)对象上,调用 notify() 方法 或 notifyAll()方法唤醒该线程
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Demo1 demo1 = new Demo1();
// 不是任意对象的wait方法都可以正常执行的
//demo1.wait();
// 正确的用法,在当前线程持有的锁对象上,调用该wait方法才能正常执行
synchronized (demo1) {
if (true) {
// 某种条件下克制自己
demo1.wait();
}
}
// 自己无法唤醒自己,只能是别人来唤醒,wait方法之后的代码,在线程被唤醒之前,不会执行
demo1.notify();
System.out.println("after wait");
}
}
notify(),notifyall()
通知别人:
对象(锁对象).notify()
public final void notify():
1.唤醒在此对象监视器上等待的单个线程。
2.如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。
3. 选择是任意性的,并在对实现做出决定时发生
对象(锁对象).notifyAll()
public final void notifyAll()
唤醒在此对象监视器上等待的所有线程.