您的位置:首页 > 职场人生

黑马程序员--Java多线程

2015-08-14 13:46 267 查看
——- android培训java培训、期待与您交流! ———-

多线程:

如果程序只有一条执行路径,那么该程序就是单线程程序。

如果程序有多条执行路径,该程序就是多线程程序。

进程:

正在运行的程序就是进程

进程是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源。

多进程意义:

单进程的计算机同一时间只能做一件事情,现在的计算机可以做很多事情。

也就是说现在的计算机支持多进程,可以在同一时间段内执行多个任务。并且提高CPU的使用率。

一边打游戏,一边听音乐,不是同时进行的,单CPU在某一时间点上只能做一件事。

CPU在做程序间的高效切换让我们感觉打游戏和听音乐是同时进行的。

一个程序只有一条执行路径,称为单线程程序。

一个程序有多条执行路径,称为多线程程序。

什么是线程:

同一进程内又可以执行多个任务,而这每一个任务就可以看成一个线程。

线程是程序的执行单元,执行路径。是程序使用CPU的最基本单位。

多线程的意义:

多线程的存在,不是为了提高程序的执行速读,是为了提高应用程序的使用率。

程序的执行其实就是在抢CPU的资源,CPU的执行权。

多个进程在抢资源的时候,其中某一个进行的执行路径比较多,就会有更高的几率抢到CPU的执行权。

但是不能保证的是哪一个线程在哪个时刻抢到执行权,所以线程的执行有随机性。

并发和并行的区别:

并发是逻辑上同时发生,指在某一时间内同时运行多个程序。

并发是物理上同时发生,指在某一时间点同时运行多个程序。

JAVA程序运行原理:

java命令会启动java虚拟机,启动JVM就等于启动了一个应用程序,也就是一个进程,

这个进行自动启动一个“主进程”,然后主进程调用某个类的main方法。所以main方法运行在主线程中。

JAVA虚拟机的启动是多线程的,除了主线程,至少还需要垃圾回收线程,否则内存很快就会溢出。

如何实现多线程?

线程是依赖进程存在的,应该先创建一个进程出来。

进程是由系统创建的,应该调用系统功能创建一个进程。

java不能直接调用系统功能,所以没有办法直接实现多线程程序

但是java可以调用C/C++写好的程序实现多线程程序。

由C/C++去调用系统能创建进程,然后由java去调用。

java再提供一些类供我们使用,这就可以实现多线程了。

方式1:继承Thread类

自定义MyThread()方法

package com.kxg_01;
public class MyThread extends Thread {
@Override
// 重写run()方法,不是类中的所有代码都需要被线程执行,这个时候,为了区分哪些代码能够被线程执行,
// java提供了Thread类中的run()方法用来包含那些被线程执行的代码。
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);//getName()方法是Thread类中获取线程名称
}
}
}


创建线程对象并启动

package com.kxg_01;

/*
* 需求:继承Thread实现多线程的程序
*
* 步骤:
*                 1.自定义MyThread类继承Threa类
*                 2.重写Thread类中的run()方法
*                 3.创建对象
*                 4.启动线程
*/
public class TreadDemo {
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();

// m1.run();
// m2.run();
// 调用run()方法不是开始线程,这样调用相当于调用了一个普通的run()方法

// 用start()方法启动线程
m1.start();
m2.start();
}
}


run()方法封装被线程执行的代码,直接调用是普通方法。

start()方法先启动了线程,然后再由JVM调用该线程的run()方法。

获取和设置线程名称:

package com.kxg_01;
public class MyThread extends Thread {

public MyThread() {
}

//        定义带参构造方法,Thread类里面有这个带参构造,把参数传递给Thread
public MyThread(String name) {
super(name);
}
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}


package com.kxg_01;
public class TreadDemo {
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();

// 通过带参构造定义线程名称
// MyThread m3 = new MyThread("广州");
// MyThread m4 = new MyThread("深圳");

// 通过Thread类中的setName()方法定义线程名称
m1.setName("北京");
m2.setName("上海");

m1.start();
m2.start();
}
}


设置线程优先级:

package com.kxg_02;

/*
* 设置线程优先级
*                 public final void setPriority(int newPriority)
*
* 注意:
*                 线程默认优先级是5
*                 线程优先级范围为1-10
*                 优先级高仅仅代表该线程获取的CPU时间片的几率高一些,多次运行才能有好的效果
*/
public class TreadDemo {
public static void main(String[] args) {
// 创建线程
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();

// 设置线程名称
m1.setName("北京");
m2.setName("上海");

// 设置线程优先级
m1.setPriority(1);
m1.setPriority(10);

// 启动线程
m1.start();
m2.start();
}
}


设置线程休眠时间:

package com.kxg_03;
public class MyThread extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
// public static void sleep(long millis):设置休眠时间,单位为毫秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}


加入线程:

package com.kxg_04;

/*
* public final void join():等待该线程终止。
*                 别的线程需要等待这个加入的线程执行结束才能执行。
*/
public class TreadDemo {
public static void main(String[] args) {
// 创建线程
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
MyThread m3 = new MyThread();

// 设置线程名称
m1.setName("北京");
m2.setName("上海");
m3.setName("广州");

// 启动线程
m1.start();
// 加入线程
try {
m1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
m2.start();
m3.start();
}
}


礼让线程:执行效果就是两个线程你一次,我一次的执行

package com.kxg_05;
public class MyThread extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);

// public static void yield():暂停当前正在执行的线程对象,并执行其他线程。也称为礼让线程
Thread.yield();
}
}
}


守护线程:

package com.kxg_06;

/*
* public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。
*                 当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。
*/

public class TreadDemo {
public static void main(String[] args) {
// 创建线程
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();

// 设置守护线程
m1.setDaemon(true);
m2.setDaemon(true);
// 设置线程名称
m1.setName("深圳");
m2.setName("上海");

// 启动线程
m1.start();
m2.start();

// 设置当前运行线程名称
Thread.currentThread().setName("北京");
for (int i = 0; i < 10; i++) {
// Thread.currentThread().getName():当前执行的线程的名称
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}


m1,m2设置称为守护线程,当前运行主线程结束的时候,m1,m2都会随之结束。但已经抢到了执行权,不可能立马结束,会稍微运行一下。

中断线程:

package com.kxg_06;

/*
* 中断线程:
*                 public void interrupt()
*/

public class TreadDemo {
public static void main(String[] args) {
// 创建线程
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();

// 设置线程名称
m1.setName("深圳");
m2.setName("上海");

// 中断线程,后面的线程还可以继续运行
m1.interrupt();
// 启动线程
m2.start();
}
}


线程生命周期:



方式2:通过实现Runnable接口的实现类

package com.kxg_07;
/*
* 实现Runnable接口
*/
public class MyRunnable implements Runnable {

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}


package com.kxg_07;

/*
* 实现Runnable接口
* 步骤:
*                 1.自定义类MyRunnable实现Runnabe接口
*                 3.创建MyRunnable对象
*                 4.创建Thread类的对象,把MyRunnable对象作为构造参数传递
*/
public class RunnableDemo {
public static void main(String[] args) {
// 创建对象
MyRunnable mr = new MyRunnable();

// 设置线程
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);

// 设置线程名称
t1.setName("深圳");
t2.setName("上海");

// 启动线程
t1.start();
t2.start();
}
}


方式2好处:

1.可以避免由于java单继承带来的局限性

2.适合多个相同程序的代码去处理同一个资源的情况,

把线程和程序的代码,数据有效分离,较好的体现了面向对象的设计思想。

判断线程是否有问题的标准

1.是否是多线程环境

2.是否有共享数据

3.是否有多条语句操作共享数据

线程的状态转换图及常见执行情况



方式3:实现Callable接口,需要和线程池结合使用

实现类:

package com.kxg_06;
import java.util.concurrent.Callable;

/*
* Callable<V>:带泛型的接口
*                 接口中只有一个方法:V call()
*                 接口中的泛型是call()方法的返回值类型
*
*/
public class MyCallable implements Callable {

@Override
public Object call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return null;
}
}


测试类:

package com.kxg_06;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyCallableDemo {
public static void main(String[] args) {
// 创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);

// 添加Callable实现类
pool.submit(new MyCallable());
pool.submit(new MyCallable());

// 结束线程池
pool.shutdown();
}
}


同步问题

先看一个模拟电影院卖票案例,为了模拟真实卖票环境,代码加入延时。

package com.kxg_09;

public class SellTicekt implements Runnable {
private int ticket = 100;

@Override
public void run() {
while (true) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (ticket--) + "张票");
}
}
}
}


package com.kxg_09;

/*
* 实现Runnable接口
*/
public class SellTicketDemo {
public static void main(String[] args) {
// 创建自定义类对象
SellTicekt st = new SellTicekt();

// 创建线程
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);

// 设置线程名称
t1.setName("窗口1");
t2.setName("窗口2");

// 启动线程
t1.start();
t2.start();
}
}


此时运行程序就会出现相同票卖多次和出现负数票的情况

1.相同的票卖了多次

CPU的一次操作必须是原子性的

2.出现负数票

线程的随机性和延迟导致的

解决方式1:同步代码块

把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行

package com.kxg_09;

/*
* synchronized(对象){
*                 代码;
* }
*
* 注意:同步代码块可以解决安全问题的根本原因在对象上,该对象如果锁一样的功能,别的线程不能进入。
*                  这个对象可以是任意对象,最好是用本身this作为这个对象。
*
*/
public class SellTicekt implements Runnable {
private int ticket = 100;

@Override
public void run() {
while (true) {
synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (ticket--) + "张票");
}
}
}
}
}


方式2:同步方法

package com.kxg_09;

/*
* synchronized关键字修饰方法
* 锁对象是this
*/
public class SellTicekt implements Runnable {
private int ticket = 100;

@Override
public synchronized void run() {
while (true) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (ticket--) + "张票");
}
}
}
}


同步特点:

前提:多个线程

解决问题注意:多个线程使用的是同一个锁对象

同步好处:

同步解决了多线程的安全问题

同步的弊端:

当线程很多时,因为每个线程都会去判断同步上的锁,很消耗资源,降低程序的运行效率

JDK5以后提供了一个新的锁对象Lock,Lock是一个接口,ReentrantLock是Lock的实现类

package com.kxg_11;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* Lock:
*                 lock():添加锁
*                 unlock():释放锁
*/
public class SellTicekt implements Runnable {
private int ticket = 100;
//        创建Lock接口的实现类ReentrantLock
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (ticket--) + "张票");
}
} finally {
lock.unlock();
}
}
}
}


死锁

两个或两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象


例如:

中国人,外国人吃饭

正常:

中国人:筷子两支

外国人:刀和叉

死锁:

中国人:筷子一支,刀一把

外国人:筷子一支,叉一把

两个人出现死锁以后,都不能正常用餐,都等着对方

package com.kxg_12;

public class DieLock extends Thread {
// 创建两个锁对象
public static Object objA = new Object();
public static Object objB = new Object();

// 定义变量
private boolean flag;

// 定义构造方法
public DieLock(boolean flag) {
this.flag = flag;
}

@Override
public void run() {
if (flag) {
// 线程A中嵌套线程B
synchronized (objA) {
System.out.println("if ObjA");
synchronized (objB) {
System.out.println("if objB");
}
}
} else {
// 线程B中嵌套线程A
synchronized (objB) {
System.out.println("else objB");
synchronized (objA) {
System.out.println("else objA");
}
}
}
}
}


package com.kxg_12;

public class DieLockDemo {
public static void main(String[] args) {
// 创建两个线程
DieLock dl1 = new DieLock(true);
DieLock dl2 = new DieLock(false);

// 启动线程
dl1.start();
dl2.start();
}
}


if中的语句抢到执行权,输入”if objA”。输出”if objB”之前被else中的语句抢到了执行权

else语句输出”else objB”,当往下执行的时候需要获取objA锁,但是objA锁在if语句线程中没有释放。

只好等着if语句中释放。此时if语句抢到执行权,往下执行的时候发现需要objB锁,但objB锁在else语句线程中没有释放。

这样就形成了死锁。

线程间通信

针对同一资源有不同的线程进行操作

例如:卖票,需要有卖票的进程,还需要有生产票的进程

通过对学生信息的设置和获取,写一个简单的线程间通信案例

测试类:

package com.kxg_03;
public class StudentDemo {
public static void main(String[] args) {
// 创建资源
Student s = new Student();

// 创建SetThread和GetThread对象
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);

// 创建线程
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);

// 开启线程
t1.start();
t2.start();
}
}


资源类:

package com.kxg_03;
/*
* 定义学生类
*/
public class Student {
String name;
int age;
boolean flag;// 用来判断是否存在资源,默认是flash,没有资源

public synchronized void set(String name, int age) {
// 生产者,如果有数据就等待
if (!this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 设置数据
this.name = name;
this.age = age;
// 修改标记
this.flag = false;
// 唤醒线程
this.notify();
}

public synchronized void get() {
if (this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.name + ":" + this.age);
// 修改标记
this.flag = true;
// 唤醒线程
this.notify();
}
}


设置类:

package com.kxg_03;

/*
* 设置学生信息的线程
*/
public class SetThread implements Runnable {

private Student s;
private int i;

public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
if (i % 2 == 0) {
s.set("小明", 5);
} else {
s.set("汪汪", 2);
}
i++;
}
}
}


获取类:

package com.kxg_03;

/*
* 设置获取学生信息的线程
*/
public class GetThread implements Runnable {

private Student s;

public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
s.get();
}
}
}


如果是生产多个,和消费多个的话,两个线程需要加上同一把锁才行。

但是这样做依然还存在着问题:

1.如果消费者先抢到CPU执行权,消费数据,这时数据如果是空,就没有意义。

应该等着数据生产出来,再去消费,这样才具有意义。

2.如果生产者先抢到CPU执行权,生产数据,但是生产完一定数量的数据以后,还继续持有执行权,

它还会继续生产数据,这还现实情况不符,需要等着消费者把数据消费以后,再生产。

正常思路:

1.生产者

先看是否有数据,有就等待,没有就生产,生产完通知消费者消费

2.消费者

先看是否有数据,有就消费,没有就等待,消费完通知生产者生产

java提供了一个等待唤醒机制来解决这个问题。



等待唤醒:

Object类中提供了三个方法:

wait():等待

notify():唤醒单个线程

为什么等待唤醒方法定义在Object类中:

这些方法都是通过锁对象进行调用的,锁对象可以是任意的

所以,这些方法必须定义在Object类中。

测试类:

package com.kxg_03;
public class StudentDemo {
public static void main(String[] args) {
// 创建资源
Student s = new Student();

// 创建SetThread和GetThread对象
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);

// 创建线程
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);

// 开启线程
t1.start();
t2.start();
}
}


资源类:

package com.kxg_03;
/*
* 定义学生类
*/
public class Student {
String name;
int age;
boolean flag;// 用来判断是否存在资源,默认是flash,没有资源
}


生产者类:

package com.kxg_03;

/*
* 设置学生信息的线程
*/
public class SetThread implements Runnable {

private Student s;
private int i;

public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
// 设置同步锁
synchronized (s) {
// 如果有数据,生产者等待
if (!s.flag) {
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

if (i % 2 == 0) {
s.name = "小明";
s.age = 5;
} else {
s.name = "汪汪";
s.age = 2;
}
i++;
// 生产完成,把变量改为有数据
s.flag = false;
// 唤醒线程
s.notify();
}
}
}
}


消费者类:

package com.kxg_03;

/*
* 设置获取学生信息的线程
*/
public class GetThread implements Runnable {

private Student s;

public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
// 设置同步锁
synchronized (s) {
// 如果没有数据,消费者就等待
if (s.flag) {
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + ":" + s.age);
// 修改标记
s.flag = true;
// 唤醒线程
s.notify();
}
}
}
}


总结

多线程中应用最多的实现方式是第二种实现方式,应该熟练掌握这种实现方式。多线程同步问题也是一个重点难点,需要掌握每种同步方法的实现方式。熟练掌握线程的生命周期,等待唤醒机制。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: