您的位置:首页 > 编程语言 > Java开发

< 笔记 > Java SE - 04 Java SE 多线程

2017-10-05 17:33 489 查看

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-
4000
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() 停止线程

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);
//        }
}
}
cl
1172e
ass 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: