Java基础复习 —— 多线程基础
多线程
程序、进程和线程
-
程序 就是一系列有序执行的指令集合
-
进程 是程序在某个数据集合上的一次运行活动,也是操作系统进行资源分配和保护的基本单位。
-
进程就是程序的一次执行过程,程序是静态的,它作为系统中的一种资源是永远存在的。而进程是动态的,它是动态的产生,变化和消亡的,拥有其自己的生命周期。
-
线程由进程创建,是进程的一个实体。
-
一个进程中可以有多个线程,它们共享这个进程的资源。
单线程和多线程
-
单线程:同一时刻,只允许执行一个线程
-
多线程:同一时刻,可以执行多个线程
并行和并发
-
并发(concurreny):并发是指一个处理器同时处理多个任务。
指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。
-
并行(parallel)是指多个处理器或者是多核的处理器同时处理多个不同的任务
并行:指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。
举例:是一个人同时吃三个馒头,而并行是三个人同时吃三个馒头。
线程基本使用
创建线程的两种方式
-
继承 Thread 类,重写 run 方法
-
实现 Runnable 接口,重写 run 方法
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
// 创建Cat对象, 可以当成一个线程使用
Cat cat = new Cat();
// cat.run(); // 当成普通方法执行,执行完后才会接着后面的for, 串行执行
cat.start(); // 启动线程——>最终会执行cat的run()方法
/*
public synchronized void start() {
start0(); // native (本地)方法,由JVM调用, 底层是c/c++实现
// 真正实现多线程的效果,是start0(),而不是run方法
}
*/
for (int i = 0; i < 60; i++) {
System.out.println("主线程 i=" + i);
Thread.sleep(1000);
}
}
}
class Cat extends Thread {
int times = 0;
@Override
public void run() {
while (true) {
System.out.println("小猫喵喵叫 " + times++ + " 线程名:" + Thread.currentThread().getName() );
// 让该线程休眠一秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times == 80) {
break;
}
}
}
}
//******************************************************************************
public class Thread02 {
public static void main(String[] args) {
Dog dog = new Dog();
// dog.start() // 不能调用start() Runnable只有一个run() 方法
Thread thread = new Thread(dog);
thread.start();
}
}
class Dog implements Runnable { // 通过实现Runnable接口,开发线程
int count = 0;
@Override
public void run() {
while(true) {
System.out.println("小狗汪汪叫。。" + ++count + " 线程名:" + Thread.currentThread().getName());
try {
//休眠一秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}
-
java 是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能了
-
从 Java 的设计来看,通过继承 Thread 或者实现 Runnable 接口来创建线程本质上没有区别,Thread 类本身就实现了 Runnable 接口
-
实现 Runnable 接口的方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制。
/**
* 模拟线程,使用了设计模式:静态代理
*/
package com.learning.javase.thread.thread01;
public class Thread03 {
public static void main(String[] args) {
Tiger tiger = new Tiger();
// dog.start() // 不能调用start() Runnable只有一个run() 方法,没有start方法
ThreadProxy threadProxy = new ThreadProxy(tiger); // ThreadProxy 有start方法
threadProxy.start();
}
}
// 线程代理类,模拟了一个极简的Thread类
class ThreadProxy implements Runnable{
private Runnable target = null;
public ThreadProxy(Runnable target) {
this.target = target;
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
public void start() {
start0(); // 真正实现多线程的方法
}
public void start0() {
run();
}
}
class Animal {}
class Tiger extends Animal implements Runnable { // 通过实现Runnable接口,开发线程
int count = 0;
@Override
public void run() {
while(true) {
System.out.println("老虎嗷嗷叫。。" + ++count + " 线程名:" + Thread.currentThread().getName());
try {
//休眠一秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}
线程的如何理解
线程退出
- 当线程完成任务后,会自动退出
- 还可以通过使用变量来控制 run 方法退出的方式停止线程,即通知方式
public class ThreadExit {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
// 如果希望main线程去控制t1线程的终止,必须可以修改loop
// 让t1 退出 run 方法,从而终止 t1线程---》 通知方式
// 让主线程休眠10秒再通知
System.out.println("主线程休眠10s");
Thread.sleep(10000);
t.setLoop(false);
}
}
class T extends Thread {
private boolean loop = true;
public void setLoop(boolean loop) {
this.loop = loop;
}
@Override
public void run() {
int count = 0;
// 设置一个控制变量
while (loop) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A Thread 运行中。。。" + count++);
}
}
}
线程常用方法线程常用方法
static Thread currentThread()
Returns a reference to the currently executing thread object.
String getName()
Returns this thread's name.
void setName(String name)
Changes the name of this thread to be equal to the argument name.
void run()
If this thread was constructed using a separate Runnable run object, then that Runnable object's run method is called; otherwise, this method does nothing and returns.
void start()
Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread.
// 底层会创建新的线程,
int getPriority()
Returns this thread's priority.
void setPriority(int newPriority)
Changes the priority of this thread.
void interrupt()
Interrupts this thread.
static void sleep(long millis)
Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers.
void join()
Waits for this thread to die.
// 线程插队,插队的线程一旦插队成功,则肯定先执行完插入的现成的所有的任务
static void yield()
A hint to the scheduler that the current thread is willing to yield its current use of a processor.
// 线程的礼让,让出CPU,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
用户线程和守护线程
-
用户线程:也叫工作线程,当线程的任务执行完或者通知方式结束
-
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
常见的守护线程:垃圾回收机制
public class Daemon01 { public static void main(String[] args) throws InterruptedException { MyDaemon myDaemon = new MyDaemon(); // 如果希望主线程结束后,子线程自动结束 // 只需要将子线程设置为守护线程 myDaemon.setDaemon(true); // 设置好守护线程,在启动 myDaemon.start(); for (int i = 0; i < 10; i++) { System.out.println("work......."); Thread.sleep(1000); } } } class MyDaemon extends Thread { @Override public void run() { for (;;) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("hahahahahah........"); } } }
线程的生命周期
JDK 中用 Thread.State 枚举表示了线程的几种状态
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
线程状态转化图
线程的同步
-
在多线程编程,一些敏感数据不允许被多个线程同时访问,此时使用同步访问技术,保证数据在任何同一时刻,最多由一个线程访问,以保证数据的完整性
-
线程同步也可以理解为:当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
同步的具体方法 - synchronized
- 同步代码块
synchronized(对象){ // 得到对象的锁,才能操作同步代码 // 需要被同步的代码 }
- synchronized 关键字还可以放在方法声明中,表示整个方法为同步方法
public synchronized void m() { // 需要被同步的代码 }
举例
// 这时synchronized 的是 this 对象,
public synchronized void sell() { // 同一时刻只能有一个线程执行sell方法
if (tickets <= 0) {
System.out.println("售票结束");
loop = false;
return;
}
// 同步方法(静态)的锁为当前类本身
// 加在 SellTicked1.class
public synchronized static void m1() {
}
// 如果在静态方法中实现一个同步代码块
public static void m2() {
synchronized (SellTicket1.class){
System.out.println("hello");
}
}
互斥锁
-
在 Java 中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
-
每个对象都对应于一个可以称为 “互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
-
关键字 synchronized 来与对象的互斥锁联系。当某个对象用 synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问
-
同步的局限性:导致程序的执行效率降低
-
同步方法(非静态的)的锁可以是 this,也可以是其他对象(要求是同一个对象)
-
同步方法(非静态)的锁为当前类本身
注意:
- 同步方法如果没有 static 修饰,默认锁对象为 this
- 如果方法使用 static 修饰,默认锁对象:当前类.class
线程死锁
案例
妈妈:你先写完作业,才让你玩手机
小明:你先让我玩手机,我才完成作业
应用案例,模拟线程死锁
package com.learning.javase.thread.synch;
public class DeadLock01 {
public static void main(String[] args) {
// 模拟死锁现象
DeadLockDemo A = new DeadLockDemo(true);
A.setName("线程A");
DeadLockDemo B = new DeadLockDemo(false);
B.setName("线程B");
A.start();
B.start();
}
}
class DeadLockDemo extends Thread {
static Object o1 = new Object();
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
// 下面业务逻辑分析
// 1. 如果flag为true, 线程A就会先得到/持有 o1 对象锁,尝试 去获取 o2 对象锁、
// 如果线程A 得不到 o2 对象所,就会 blocked
// 2. 如果 flag 为 false, 线程B就会先得到/持有o2 对象锁,尝试 去获取 o1 对象锁
// 如果线程B 得不到 o1 对象所,就会 blocked
if (flag){
synchronized (o1) { // 对象互斥锁,下面是同步代码
System.out.println(Thread.currentThread().getName() + " 进入1");
synchronized (o2) { // 这里获得li对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入3");
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + " 进入4");
}
}
}
}
}
释放锁
下面的操作不会释放锁
-
线程执行同步代码块或同步方法时,程序调用了
Thread.sleep()、Thread.yield()
方法展厅当前线程的执行,不会释放锁 -
线程执行同步代码块时,其他线程调用了该线程的
suspend()
方法将线程挂起,该线程不会释放锁应该尽量避免使用
suspend()
和resume()
来控制线程,方法不在推荐使用