04 Java SE 多线程
By Kevin Song
- 04-01 多线程概述
- 04-02 JVM中的多线程
- 04-03 多线程创建
- 04-04 多线程同步
- 04-05 多线程中的单例模式
- 04-06 多线程死锁
- 04-07 多线程通信
- 04-08 多线程停止
04-01 多线程概述
进程:正在进行中的程序
线程:进程中的一个负责程序执行的控制单元(执行路径)
- 一个进程中可以有多个线程
- 一个进程中至少有一个线程
多线程作用:开启多个线程是为了同时运行多部分代码
多线程的优缺点:
- 优点:可以同时运行多个程序
- 缺点:内存处理到程序频率变低,运行速度变慢
04-02 JVM中的多线程
JVM启动时就启动了多个线程,至少有两个线程
- 执行主方法的线程:该线程的任务代码都定义在主方法中
- 负责垃圾回收的线程
Object 类中的 finalize() 方法:
当垃圾回收器确定不存在该对象的更多引用时,由对象的垃圾回收器调用此方法
System类中的 System.gc() 方法:运行垃圾回收器,垃圾回收器运行时间随机
class Demo extends Object {
public void finalize() {
System.out.println("Recycle Complete");
}
}
class ThreadDemo {
public static void main(String[] args) {
new Demo();
new Demo();
System.gc(); //
new Demo();
System.out.println("Hello World!");
}
}
/*
输出
Hello World!
Recycle Complete
Recycle Complete
因为是两个线程,先运行主线程,JVM关闭前运行垃圾回收线程
*/
04-03 多线程创建
主方法单线程运行
不创建多线程
class Demo {
private String name;
Demo (Srting name) {
this.name = name;
}
public void show() {
for(int x = 0; x < 10; x++) {
//y的for循环让每次输出都有一定的延迟,但是必须d1都输出完才输出d2,这时就需要多线程来让他们同时输出
for(int y =-9999999; y < 999999999; y++) {}
System.out.println(name+"...x="+x);
}
}
}
class ThreadDemo {
public static void main(String[] args) {
Demo d1 = new Demo("Sequence Initializing");
Demo d2 = new Demo("序列初始化中");
d1.show();
d2.show();
}
}
创建多线程
目的:开启一条执行路径,使得指定的代码和其他代码实现同时运行,而此执行路径运行的指定代码就是这个执行路径的任务
任务存放位置:
- JVM创建的主线程的任务都定义在了主方法中
- 自定义线程的任务定义在Thread类的run方法中
创建多线程有两种方法
- 继承Thread类
- 实现Runnable接口
创建线程方式一
继承Thread类
步骤:
- 定义一个类,继承Thread类
- 重写Thread类中的 run(); 方法
- 直接创建Thread类的子类对象(创建线程)
- 调用 start(); 方法开启线程,并调用线程的任务run(); 方法
class Demo extends Thread{
private String name;
Demo (Srting name) {
this.name = name;
}
public void run() {
for(int x = 0; x < 10; x++) {
//currentThread()获取当前运行中线程的引用
System.out.println(name+"...x="+x+"...Thread Name="+Thread.currentThread().getname());
}
}
}
class ThreadDemo {
public static void main(String[] args) {
Demo d1 = new Demo("Sequence Initializing");
Demo d2 = new Demo("序列初始化中");
d1.start();//开启线程,调用run方法
d2.start();//开启线程,调用run方法
//CPU在主线程,d1,d2之间随机高速切换
}
}
Thread类中的方法&线程名称
getName()方法
获取线程的名称Thread-编号(从0开始)
主线程的名字main
currentThread()方法
获取当前运行中线程的引用,该方法为静态,可以直接被类名调用
多线程异常问题
有异常的线程结束运行,run() 方法出栈,线程之间互不影响。
线程的状态
- new被创建
- start() 运行(具备执行资格,具备执行权)
- 冻结(释放执行权,释放执行资格)
- sleep(time) 冻结 时间到自动唤醒
- wait() 冻结 notify 唤醒
- 阻塞(具备执行资格,不具备执行权,正在等待执行权)
- 消亡
- run() 方法结束
- stop() 停止线程
- 冻结(释放执行权,释放执行资格)
- start() 运行(具备执行资格,具备执行权)
CPU的执行资格:可以被CPU处理,在处理队列中排队
CPU的执行权:正在被CPU处理
创建线程方式二
实现Runnable接口
步骤:
- 定义一个类,实现Runnable接口
- 重写接口中的run方法,将线程的任务代码封装到run方法中
- 通过Thread类创建线程对象,将Runnable接口的子类对象作为构造方法的参数进行传递
- 调用线程对象的start(); 方法开启线程
class Demo extends Fu implements Runable{ //无法继承Thread但是需要多线程,通过接口形式完成
public void run() {
show();
}
public void show() {
for(int x = 0; x < 20; x++) {
System.out.println(Thread.currentThread().getName()+"..."+x);
}
}
}
class ThreadDemo {
public static void main(String[] args) {
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start;
t2.start;
}
}
第二种方法的好处:
- 将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象
- 避免了java单继承的局限性
04-04 多线程同步
卖票示例
四个线程,每个线程都卖100张票
class Ticket extends Thread{
private int num = 100;
public void run() {
sale();
}
public void sale() {
while(true) {
if(num > 0) {
System.out.println(Thread.currentThread().getName()+“...sale...”num--);
}
}
}
}
class TicketDemo {
public static void main(String[] args) {
//四个对象,每个对象都有100张票
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
四个线程一起卖100张票
class Ticket implements Runnable {
private int num = 100;
public void run() {
sale();
}
public void sale() {
while(true) {
if(num > 0) {
System.out.println(Thread.currentThread().getName()+“...sale...”num--);
}
}
}
}
class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket(); //一个对象,所有一共只有100张票
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();
}
}
线程安全问题的现象
会出现0,-1,-2张票的情况,因为线程1还没运行到num–的时候,线程2,线程3有可能就已经加载进来,当num–运行完之后,线程还会继续运行,这就导致0,-1,-2的出现。
安全问题产生的原因
- 多个线程在操作共享的数据
- 操作共享数据的线程代码有多条
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算
多线程同步
将多条操作共享的数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。
同步代码块
格式
synchronized(对象) {
需要被同步的代码;
}
class Ticket implements Runnable {
private int num = 100;
Object obj = new Object; //此对象为了传给synchronized作为锁
public void run() {
sale();
}
public void sale() {
while(true) {
synchronized(obj) { //synchronized修饰的代码块只能同一时间被一个线程调用
if(num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName()+“...sale...”num--);
}
}
}
}
}
class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket(); //一个对象,所有一共只有100张票
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
同步的前提:
- 同步中必须有多个线程并使用同一个锁
- 当作锁的对象必须在 run() 方法外创建
同步的优缺点:
- 优点:解决了线程的安全问题
- 缺点:相对降低了效率,因为同步外的线程都会判断同步锁
同步方法
/*
两个客户每次去银行存100,存三次
*/
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 Bank b = new Bank();
public void run() {
for(int x = 0; x < 3; x++) {
b.add(100);
}
}
}
class BankDemo {
public static void main(String[] args) {
Cus c = new Cus;
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
/*
输出:
sum=100
sum=200
sum=300
sum=400
sum=500
sum=600
*/
同步代码块和同步方法同时使用
class Ticket implements Runnable {
private int num = 100;
Object obj = new Object; //此对象为了传给synchronized
boolean flag = true;
public void run() {
if(flag)//flag为true则运行同步代码快
while(true) {
synchronized(this) {
if(num>0) {
try {
Thread.sleep(10);
} catch(InterruptedException e) {}
System.out.println(Thread.currentThread().getName()+"...obj..."+num--);
}
}
}
else //flag为false则运行同步方法
while(true)
this.show();
}
public synchronized void show() {
if(num > 0) {
try {
Thread.sleep(10);
} catch ()
System.out.println(Thread.currentThread().getName()+“...syn...”num--);
}
}
}
class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket(); //一个对象,所有一共只有100张票
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
t1.start();
try {
Thread.sleep(10);
} catch(InterruptedException e) {}
t.flag = false;
t2.start();
}
}
同步方法和同步代码快的区别:
- 同步方法的锁是固定的this(当前对象)
- 同步代码块的锁是任意的对象
静态同步方法使用的锁:
该方法所属字节码文件对象,可以用如下方式获取
- this.getClass() 仅限非静态方法中使用
- Ticket.class
04-05 多线程中的单例模式
使用同步虽然可以避免安全问题,但是会导致程序运行效率变低,因为每个线程都需要判断是否可以拿锁
//饿汉式(单例设计模式)
class Single {
private static final Single s = new Single();
private Single() {}
public static Single getInstance() {
return s;
}
}
//懒汉式(延迟加载单例设计模式)
class Single {
private static Single s = null;
private Single() {}
public static Single getInstance() {
if(s == null) {//解决效率问题:多加一句判断,后续线程不会判断是否可以拿锁
//不用this.getClass()是因为getClass()是非静态方法
synchronized(Single.class) {//解决安全问题
if(s == null)
s = new Single();
}
}
return s;
}
}
class SingleDemo {
public static void main(String[] args) {
System.out.println();
}
}
04-06 多线程死锁
同步代码块嵌套
class Test implements Runnable{
private boolean flag;
Test(boolean flag) {
this.flag = flag;
}
public void run() {
if(flag) {
synchronized(MyLock.locka) {
System.out.println("if...locka...");
synchronized(MyLock.lockb) {
System.out.println("if...lockb...");
}
}
} else {
synchronized(MyLock.lockb) {
System.out.println("if...lockb...");
synchronized (MyLock.locka) {
System.out.println("if...locka...");
}
}
}
}
}
class MyLock {
public static final Object locka = new Object();
public static final Object lockb = new Object();
}
class DeadLockTest {
public static void main(String[] args) {
Test a = new Test(true);
Test b = new Test(false);
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start;
t2.start;
}
}
04-07 多线程通信
定义:多个线程在处理同一资源,任务却不同
等待唤醒机制
涉及的方法:
- wait(); 让线程处于冻结状态,释放执行权和执行资格,存储到线程池中
- notify(); 唤醒线程池中一个线程
- notifyAll(); 唤醒线程池中所有线程,防止多生产多消费的死锁问题
这些方法都必须定义在同步中,因为这些方法都是用于线程状态的方法,必须要明确到底操作的是哪个锁上的线程。
等待唤醒机制中的方法(wait(); notify(); notifyAll();)定义在Object类中,因为这些方法是监视器的方法,监视器其实就是锁
//资源
class Resource {
private String name;
private String sex;
privateboolean flag = false; //用来判断是否可以输入输出
public synchronized void set(String name) {
if(flag) //如果flag为false,则不wait
try {
this.wait();
} catch (InterruptException e) {}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
public synchronized void out() {
if(!flag)
try {
this.wait();
} catch (InterruptException e) {}
System.out.println(name+"..."+sex);
flag = false;
notify();
}
}
//输入
class Input implements Runnable {
Resource r;
Input(Resource r) {
this.r = r;
}
public void run() {
int x = 0;
while(true) {
if(x == 0) { //控制两种数据输入
r.set("Kevin","Male")
} else {
r.set("Lily","Female");
}
x = (x+1)%2;
}
}
}
//输出
class Output implements Runnable {
Resource r;
Output(Resource r) {
this.r = r;
}
public void run() {
while(true) {
r.out();
}
}
}
}
class ResourceDemo {
public static void mian(String[] args) {
//创建资源
Resource r = new Resource();
//创建任务
Input in = new Input(r);
Output out = new Output(r);
//创建线程
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
//开启线程
t1.start();
t2.start();
}
}
多生产者多消费者问题
多生产多消费与单生产单消费的区别
flag判断语句区别
- if只判断标记一次,会导致不该运行的线程开始运行,出现数据错误
- while可以循环判断标记,解决了线程获取执行权后,是否要运行的问题
唤醒语句区别
- notify()只能唤醒一个线程,如果唤醒了本方,没有意义,并且会导致死锁,所有线程都进线程池
- notifyAll()解决了,本方线程一定会唤醒对方线程
//定义资源
class Resource {
private String name;//资源名称
private int count = 1;//计数器
private boolean flag = false;
public synchronized void set(String name) {
while(flag)//flag为false时不wait,直接运行下面内容。
try {
this.wait();//当flag为true时进入wait状态
} catch (InterruptException e) {}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true; //flag改成true
notify(); //随机唤醒一个线程,如果是多生产多消费者则用notifyAll();
}
public synchronized void out() {
while(!flag)
try {
this.wait();
} catch (InterruptException e) {}
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag = false;
notify();//随机唤醒一个线程,如果是多生产多消费者则用notifyAll();
}
}
class Producer implements Runnable {
private Resource r;
Producer(Resource r) {
this.r = r;
}
public void run() {
while(true) {
r.set("烤鸭");
}
}
}
class Producer implements Runnable {
private Resource r;
Producer(Resource r) {
this.r = r;
}
public void run() {
while(true) {
r.out();
}
}
}
class ProducerConsumerDemo {
public static void main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(con);
t0.start();
t1.start();
}
}
JDK1.5新特性
Lock
Lock接口:代替了同步代码快或者同步方法,将同步的隐式锁操作变成显式锁操作,更为灵活,可以一个锁上加多组监视器。
- lock(); 获取锁
- unlock(); 释放锁,通常需要定义在finally代码块中
Lock lock = new ReentrantLock();
public void run() {
lock.lock();
try {
code;
} finally {
lock.unlock();
}
}
Condition
Condition接口:代替了Object中的wait notify notifyAll方法。这些监视器被封装,变成Condition监视器对象,可以任意锁进行组合
- await(); 功能同wait()
- signal(); 同notify()
- signalAll(); 同notifyAll()
1.5版本的多生产者多消费者代码
//定义资源
class Resource {
private String name;//资源名称
private int count = 1;//计数器
private boolean flag = false;
//创建一个锁对象
Lock lock = new ReentrantLock();
//通过已有的锁获取该锁上的监视器对象
//Condition con = lock.newCondition();
Condition Producer_con = lock.newCondition();
Condition Consumer_con = lock.newCondition();
public void set(String name) {
lock.lock();
try {
while(flag)//flag为false时不wait,直接运行下面内容。
try {
Producer_con.await();//当flag为true时进入wait状态
} catch (InterruptException e) {}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true; //flag改成true
//notifyAll();
//con.signalAll();
consumer_con.signal();
} finally {
lock.unlock();
}
}
public synchronized void out() {
lock.lock();
try {
while(!flag)
try {
consumer_con.await();
} catch (InterruptException e) {}
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag = false;
//notifyAll();
//con.signalAll();
//Producer_Con.signal();
} finally {
lock.unlock();
}
}
}
class Producer implements Runnable {
private Resource r;
Producer(Resource r) {
this.r = r;
}
public void run() {
while(true) {
r.set("烤鸭");
}
}
}
class Consumer implements Runnable {
private Resource r;
Producer(Resource r) {
this.r = r;
}
public void run() {
while(true) {
r.out();
}
}
}
class ProducerConsumerDemo {
public static void main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(con);
t0.start();
t1.start();
}
}
wait和sleep区别
- 时间指定不同
- wait可以指定时间也可以不指定
- sleep**必须**指定时间
- 在同步中时,对CPU的执行权和锁的处理不同
- wait:释放执行权,释放锁
- sleep:释放执行权,不释放锁
04-08 多线程停止
停止线程
- stop方法,已经过时
- 定义标记,使run方法结束
定义标记
定义一个标记flag,当flag为true时线程可以运行,当flag为负时线程停止运行。
局限性:当有wait() 的时候会导致线程无法继续判断flag而进程冻结
class StopThread implements Runnable {
boolean flag = true; //定义flag标记
public void run() {
while(flag) { //flag标记为true时运行run方法
System.out.println(Thread.currentThread().getName()+"...");
}
}
public void setFlag() { //定义设置flag为false的方法
flag = false;
}
}
class StopThreadDemo {
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");
}
}
interrupt()方法
Interrput() 方法可以把线程从冻结状态强制恢复到运行状态中来
class StopThread implements Runnable {
boolean flag = true; //定义flag标记
public synchronized void run() {
while(flag) { //flag标记为true时运行run方法
try {
wait();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getNmae()+"..."+e);
flag = false;
}
System.out.println(Thread.currentThread().getName()+"...");
}
}
public void setFlag() { //定义设置flag为false的方法
flag = false;
}
}
class StopThreadDemo {
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");
}
}
setDaemon()方法
使线程变成守护线程(后台线程),前台线程结束之后后台线程自动结束
join()方法
执行权让给t1
t1.join();
setPriority()方法
设置线程优先级
t1.setPriority(Thread.MAX_PRIORITY);
- MAX_PRIORITY
- MIN_PRIORITY
- NORM_PRIORITY