多线程
视频:毕向东-Java基础
进程
正在进行中的程序(直译)
线程
-
进程中的一个负责程序执行的一个控制单元(执行路径)
-
一个进程中可以有多个执行路径(多线程)
-
一个进程中至少要有一个线程
多线程
**开启多线程的作用:**为了同时运行多部分的代码。每一个线程都有自己运行的内容。这个内容可以称为线程要执 行的任务
好处: 解决了多部分同时运行的问题
弊端: 线程太多会导致效率降低
注意: 其实应用程序的执行都是CPU在做着快速的切换完成的,这个切换是随机的
JVM在启动时就启动了多个线程,至少有两个线程可以分析出来
-
执行main函数的线程
- 该线程的任务代码都定义在main函数中
-
负责垃圾回收的线程
- 当垃圾收集确定不再有对该对象的引用时,垃圾收集器在对象上调用==finalize()==方法。
public class Test {
public static void main(String[] args) {
new Demo();
new Demo();
System.gc(); //启动垃圾回收器(在另一个线程中执行)
new Demo();
System.out.println("hello world");
}
}
class Demo{
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("Demo 垃圾回收");
}
}
//运行结果(每次运行顺序会不一样)
Demo 垃圾回收
Demo 垃圾回收
hello world
创建线程
方式一
- 继承Thread类
步骤:
1、定义一个类继承Thread类
2、重写Thread类中的run方法
3、直接创建Thread的子类对象创建线程
4、调用start方法开启线程并调用线程的任务run方法执行
public class Demo02_Thread {
public static void main(String[] args) {
/*
* 创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行
* 而运行的指定代码就是这个执行路径的任务
* jvm创建的主线程的任务都定义在了主函数中
* 而自定义的线程任务定义在哪儿呢?
* Thread类用于描述线程,线程是需要任务的,所以Thread类也有对任务的描述
* 这个任务就通过Thread类中的run方法来体现,也就是说
* run方法就是封装自定义线程的运行任务的函数
*
* run方法中定义的就是线程要运行的任务代码
*
* 开启线程是为了运行指定代码,所以只有继承Thread类并复写run方法,
* 将运行的代码定义在run方法中即可
*/
Demo2 d1 = new Demo2("张三");
Demo2 d2 = new Demo2("lisi");
d1.start(); //开启线程,调用run方法
d2.start();
}
}
class Demo2 extends Thread{
private String name;
Demo2(String name){
this.name = name;
}
@Override
public void run() {
for(int x = 0;x < 10;x++) {
System.out.println(name+"...x="+x+"...name="+getName());
}
}
}
- 可以通过Thread中的==getName()==方法获取线程的名称
- 名称格式:Thread-编号(从0开始)
获取当前运行时的线程名称
Thread.currentThread().getName();
注意:
1、主线程的名称叫main
2、当一个线程中出现异常的时候,该线程就停止,而其他线程继续执行
多线程运行图解
方式二
实现Runnable接口
1、定义类实现Runnable接口
2、覆盖接口中的run方法,将线程的任务代码封装到run方法中
3、通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。因为线程的任务都封装在Runnable接口的子类对象的run方法中,所以要在线程对象创建的时候就必须明确要运行的任务
4、调用线程对象的Start方法开启线程
public class Demo01_Thread {
public static void main(String[] args) {
Demo1 d = new Demo1();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
class Demo1 implements Runnable{
public void show() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "..." + i);
}
}
@Override
public void run() {
show();
}
}
好处:
1、将线程的任务从线程的子类中分离出来进行了单独的封装
按照面向对象的思想将任务封装成对象
2、避免了Java单继承的局限性
线程的状态
CPU的执行资格:
- 可以被cpu处理,在处理队列中排队
CPU的执行权:
- 正在被cpu处理
线程安全问题
线程安全问题产生的原因
1、多个线程在操作共享的数据
2、操作共享数据的线程代码有多条
- 当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生
解决思路:
将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程是不可以参与运算的,必须要当前线程把这些代码都执行完毕之后,其他线程才可以参与运算
在Java中,用同步代码块就可以解决这个问题
同步
同步的好处:
解决了线程的安全问题
同步的弊端:
相对降低了效率,因为同步外的线程都会判断同步锁
同步的前提:
同步中必须有多个线程并使用同一个锁
同步代码块
同步代码格式
synchronized(对象){
需要被同步的代码;
}
卖票示例:
public class Test1 {
public static void main(String[] args) {
Ticket t = new Ticket(); // 创建一个线程任务对象
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket implements Runnable {
private int num = 100;
Object obj = new Object();
public void sale() {
while (true) {
synchronized(obj) {
if (num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "..." + num--);
}
}
}
}
@Override
public void run() {
sale();
}
}
同步函数
/*
* 需求:储户,两个,每个都到银行存钱,每次存100,共存三次
*/
public class Test2 {
public static void main(String[] args) {
Cus c1 = new Cus();
Cus c2 = new Cus();
Thread t1 = new Thread(c1);
Thread t2 = new Thread(c2);
t1.start();
t2.start();
}
}
class Bank {
private int sum;
// private Object obj = new Object();
public synchronized void add(int num) { //同步函数
// synchronized (obj) {
sum = sum + num;
System.out.println("sum=" + sum);
// }
}
}
class Cus implements Runnable {
private static Bank b = new Bank();
@Override
public void run() {
for(int x = 0;x < 3;x++) {
b.add(100);
}
}
}
同步函数的锁: this
同步函数和同步代码块的区别:
同步函数的锁是固定的this
同步代码块的锁是任意的对象
- 建议使用同步代码块
静态的同步函数使用的锁:
- 该函数所属的字节码文件对象,可以用getClass方法获取,也可以用当前类名 . class形式表示
单列设计模式涉及的多线程问题
/*
懒汉式单例设计模式
*/
class Single{
private static Single s = null;
private Single() {
}
public static Single getInstance(){
if (s == null) {
synchronized (Single.class) {
if (s == null)
s = new Single();
}
}
return s;
}
}
死锁
**发生死锁常见情况之一:**同步嵌套
public class Demo03_DeadLock {
public static void main(String[] args) {
Test3 a = new Test3(false);
Test3 b = new Test3(true);
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start();
t2.start();
}
}
class Test3 implements Runnable{
private boolean flag;
Test3(boolean flag){
this.flag = flag;
}
@Override
public void run() {
if(flag) {
synchronized (MyLock.locka) {
loacka
System.out.println(Thread.currentThread().getName() + "...if locka");
synchronized (MyLock.lockb) {
System.out.println(Thread.currentThread().getName() + "...if lockb");
}
}
}else {
synchronized (MyLock.lockb) {
loackb
System.out.println(Thread.currentThread().getName() + "...else lockb");
synchronized (MyLock.locka) {
System.out.println(Thread.currentThread().getName() + "...else locka");
}
}
}
}
}
class MyLock{
public static final Object locka = new Object();
public static final Object lockb = new Object();
}
线程间通讯
等待&唤醒机制
涉及的方法:
wait() throws InterruptedException:被wait的线程会被存储在线程池中
notify():唤醒线程池中的一个线程(任意)
notifyall():唤醒线程池中的所有线程
注意:
这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法,必须要明确到底操作的是哪个锁上的线程
**问题:**为什么这些方法定义在Object类中?
**原因:**因为这些方法是监视器的方法,监视器其实就是锁,锁可以是任意对象,任意的对象调用的方法一定定义在Object类中
示例代码:
class Resource {
private String name;
private String sex;
private boolean flag = false;
public synchronized void set(String name,String sex) {
if(flag == true) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
public synchronized void out() {
if(flag == false) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name + "..." + sex);
flag = false;
this.notify();
}
}
class Input implements Runnable {
private Resource r;
public Input(Resourc3 r) {
this.r = r;
}
@Override
public void run() {
int x = 0;
while(true) {
if(x == 0) {
r.set("mike", "male");
}else {
r.set("丽丽","女");
}
x = (x + 1)%2;
}
}
}
class Output implements Runnable {
private Resource r;
public Output(Resource r) {
this.r = r;
}
@Override
public void run() {
while(true) {
r.out();
}
}
}
生产者消费者问题
if判断标记,只有一次,会导致不该运行的线程运行了,出现了数据错误的情况
while判断标记,解决了线程获取执行权后,是否要运行
notify:只能唤醒一个线程,如果本方唤醒本方,没有意义,而且while判断标记和 notify会导致死锁
notifyAll解决了本方线程一定会唤醒对方线程
public class Demo04_ProducerConsumer {
public static void main(String[] args) {
Resource4 r = new Resource4();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
class Resource4{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name) {
//判断有无烤鸭,有就等待
while(flag == true) {
try {
this.wait(); t0
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + count; //烤鸭1
count++; //2
System.out.println(Thread.currentThread().getName() + "...生产者..." + this.name); //生产烤鸭1
//有烤鸭
flag = true;
//唤醒消费者
notifyAll();
}
public synchronized void out() {
//判断有无烤鸭,没有就等待
while(flag == false) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "————消费者————" + this.name);
//无烤鸭
flag = false;
//唤醒生产者
notifyAll();
}
}
/*
* 生产者
*/
class Producer implements Runnable{
private Resource4 r;
Producer(Resource4 r){
this.r = r;
}
@Override
public void run() {
while(true) {
r.set("烤鸭");
}
}
}
/*
* 消费者
*/
class Consumer implements Runnable{
private Resource4 r;
Consumer(Resource4 r){
this.r = r;
}
@Override
public void run() {
while(true) {
r.out();
}
}
}
Lock接口
- 同步代码块中,对于锁的操作是隐式的
- jdk1.5之后,将同步和锁封装成了对象,并将操作锁的隐式方式定义到该对象中,将隐式的动作变成显示的
特点:
它的出现替代了同步代码块或同步函数,更为灵活,可以一个锁上加上多个监视器(包括wait()、notify()、notifyAll()这些方法)
lock() //获取锁
unlock() //释放锁,通常需要定义在finally代码块中
用法
Lock lock = new ReentrantLock();
public void method(){
lock.lock();
try{
......;
}finally{
lock.unlock();
}
}
Condition接口
- 它的出现替代了Object中的wait、notify、notifyAll方法,将这些监视器方法单独进行了封装,变成了Condition监视器对象,可以任意进行组合
//通过已有的锁获取两组监视器对象,一组监视生产者,一组监视消费者
Condition producer_con = lock.newCondition();
Condition Consumer_con = lock.newCondition();
await() //冻结
signal() //唤醒
signalAll() //唤醒全部
范例
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition(); //监视烤鸭是否装满
final Condition notEmpty = lock.newCondition(); //监视烤鸭是否吃完
final Object[] items = new Object[100]; //可以存100只烤鸭
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length) //烤鸭满了
notFull.await(); //冻结添加烤鸭的线程,在notFull中等待
//烤鸭没满
items[putptr] = x;
if (++putptr == items.length) putptr = 0; //判断添加完这只烤鸭后是否装满了
++count; //烤鸭数量加1
notEmpty.signal(); //唤醒notEmpty中等待的消费烤鸭的线程
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0) //没有烤鸭
notEmpty.await(); //冻结消费烤鸭的线程,在notEmpty中等待
//有烤鸭
Object x = items[takeptr]; //从索引0开始获取烤鸭
if (++takeptr == items.length) takeptr = 0; //表示获取完烤鸭
--count; //烤鸭数量减一
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
wait和sleep的区别
wait:可以指定时间,也可以不指定
sleep:必须指定时间
在同步时,对cpu的执行权和锁的处理不同
wait:释放执行权,释放锁
sleep:释放执行权,不释放锁
停止线程
- 1、stop方法(不安全)
- 2、run方法结束
怎么控制线程的任务结束?
任务中都会有循环结构,只要控制住循环,就可以结束任务
控制循环通常就用定义标记来完成
public class Demo04_StopThread {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 1;
for(;;) {
if(++num == 50) {
st.setFlag();
break;
}
System.out.println("main..." + num);
}
System.out.println("over");
}
}
class StopThread implements Runnable{
private boolean flag = true;
@Override
public void run() {
while(flag) { //定义标记
System.out.println(Thread.currentThread().getName() + "...run");
}
}
public void setFlag() {
flag = false;
}
}
如果线程处于冻结状态就无法读取标记,如何结束?
@Override
public synchronized void run() {
while(flag) {
try {
wait(); //t0和t1都这等待着
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println(Thread.currentThread().getName() + "..." + e);
}
System.out.println(Thread.currentThread().getName() + "...run");
}
}
Interrupt方法
interrupt():
可以将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格
但是强制动作会发生异常,记得要处理
例:
public class Demo04_StopThread {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 1;
for(;;) {
if(++num == 50) {
// st.setFlag();
t1.interrupt();
t2.interrupt();
break;
}
System.out.println("main..." + num);
}
System.out.println("over");
}
}
class StopThread implements Runnable{
private boolean flag = true;
@Override
public synchronized void run() {
while(flag) {
try {
wait(); //t0和t1都这等待着
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println(Thread.currentThread().getName() + "..." + e);
flag = false;
}
System.out.println(Thread.currentThread().getName() + "...run");
}
}
public void setFlag() {
flag = false;
}
}
其他方法
setDaemon(boolean on)
-
将此线程标记为守护线程或用户线程,传入true表示该线程为守护线程
-
当正在运行的线程都是守护线程时,Java虚拟器退出
-
该方法必须在启动线程前调用 可以理解为(守护线程)后台线程,前台线程结束时必须手动结束, 当所有前台线程都结束时,后台进程自动结束
join()
- 临时加入一个线程运算时可以使用join方法
- 当前线程中若使用了join方法将另一个线程加入进来,那么当前线程就会失去cpu执行资格,当前线程必须得等加入进来的线程执行完毕之后才能重写具备执行资格
例:
public class Demo06_Join {
public static void main(String[] args) throws InterruptedException {
Demo6 d = new Demo6();
Thread t0 = new Thread(d);
Thread t1 = new Thread(d);
t0.start();
// t0.join(); //t1线程要申请加入进来运行,主线程等待t0结束之后才会继续开启
t1.start();
t1.join();
for(int i = 0;i < 50;i++) {
System.out.println(Thread.currentThread().getName() + "...");
}
}
}
class Demo6 implements Runnable{
@Override
public void run() {
for(int i = 0;i < 50;i++) {
System.out.println(Thread.currentThread().getName() + "...");
}
}
}
setPriority(int newPriority)
- 设置优先级,优先级越高,被CPU执行到的几率越大
- 只能设置为1~10
MAX_PRIORITY: //最高优先级
MIN_PRIORITY: //最低优先级
NORM_PRIORITY: //默认优先级
例:
Thread t0 = new Thread(d);
t0.setPriority(Thread.MAX_PRIORITY);
面试题:
public class Test2 {
public static void main(String[] args) {
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("runnable run");
}
}) {
@Override
public void run() {
System.out.println("subThread run");
}
}.start(); //subThread run
}
}
t1.start();
t1.join();
for(int i = 0;i < 50;i++) {
System.out.println(Thread.currentThread().getName() + "...");
}
}
}
class Demo6 implements Runnable{
@Override
public void run() {
for(int i = 0;i < 50;i++) {
System.out.println(Thread.currentThread().getName() + "...");
}
}
}
#### setPriority(int newPriority)
- 设置优先级,优先级越高,被CPU执行到的几率越大
- 只能设置为==1~10==
```java
MAX_PRIORITY: //最高优先级
MIN_PRIORITY: //最低优先级
NORM_PRIORITY: //默认优先级
例:
Thread t0 = new Thread(d);
t0.setPriority(Thread.MAX_PRIORITY);
面试题:
public class Test2 {
public static void main(String[] args) {
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("runnable run");
}
}) {
@Override
public void run() {
System.out.println("subThread run");
}
}.start(); //subThread run
}
}