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

JavaSe——线程

2015-03-03 17:49 45 查看

A.线程的概述和多线程的意义

l 多线程概述

• 进程:

• 正在运行的程序,是系统进行资源分配和调用的独立单位。

• 每一个进程都有它自己的内存空间和系统资源。

• 线程:

• 是进程中的单个顺序控制流,是一条执行路径

• 一个进程如果只有一条执行路径,则称为单线程程序。

• 一个进程如果有多条执行路径,则称为多线程程序。

1:要想了解多线程,必须先了解线程,而要想了解线程,必须先了解进程,因为线程是依赖于进程而存在。

2:什么是进程?

通过任务管理器我们就看到了进程的存在。

而通过观察,我们发现只有运行的程序才会出现进程。

进程:就是正在运行的程序。

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

3:多进程有什么意义呢?

单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。

举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)。

也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务。

并且呢,可以提高CPU的使用率。

问题:

一边玩游戏,一边听音乐是同时进行的吗?

不是。因为单CPU在某一个时间点上只能做一件事情。

而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。

4:什么是线程呢?

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

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

单线程:如果程序只有一条执行路径。

多线程:如果程序有多条执行路径。

5:多线程有什么意义呢?

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

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

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

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

6.并行和并发

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

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

B.Java程序运行原理

l Java程序运行原理

• java 命令会启动java 虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个“主线程” ,然后主线程去调用某个类的 main 方法。所以main方法运行在主线程中。在此之前的所有程序都是单线程的。

• 思考:

• jvm虚拟机的启动是单线程的还是多线程的?

多线程。原因是垃圾回收线程也要先启动,否则很容易出现内存溢出。

现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。

C.如何实现多线程及多线程方式1的思路

方式1:

step1.写一个MyThread类继承Thread类,

step2.重写MyThread里面的run方法

step3.创建MyThread对象

step4.启动线程

问题:为什么要重写run方法呢?

因为不是类中所有的代码都需要被线程执行的。

而为了区分哪些代码被执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。

package com.core.thread;

public class MyThread extends Thread {

public MyThread() {
super();
}
public MyThread(String name) {
super(name);
}

@Override
public void run() {
//耗时操作
for (int i = 0; i < 10; i++) {
//获取线程的名称
System.out.println(getName()+"::"+i);
}
}
}


package com.core.thread;

public class Test {
public static void main(String[] args) {
MyThread t1 = new MyThread();

MyThread t2 = new MyThread();

// 设置线程名称
t2.setName("线程t2");
Thread t3 = new MyThread("线程t3");

// 获得main函数的线程名称?
// 返回当前正在执行的线程对象
Thread currentThread = Thread.currentThread();
System.out.println("currentThread::" + currentThread.getName());

}
}


D.线程调度及获取和设置线程优先级

package com.core.thread;

public class Test {
public static void main(String[] args) {
MyThread t1 = new MyThread();

MyThread t2 = new MyThread();

// 设置线程名称
t2.setName("线程t2");
Thread t3 = new MyThread("线程t3");

// 获得main函数的线程名称?
// 返回当前正在执行的线程对象
Thread currentThread = Thread.currentThread();
System.out.println("currentThread::" + currentThread.getName());

// • public final int getPriority()
// • public final void setPriority(int newPriority)
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
System.out.println(t3.getPriority());
t1.setPriority(10);
t2.setPriority(1);
t3.setPriority(1);

t1.start();
t2.start();
t3.start();
}
}


E.休眠线程

• 线程休眠

• public static void sleep(long millis)

package com.core.thread.demo1;

import java.util.Date;

public class SleepThread extends Thread {

public SleepThread() {
super();
}
public SleepThread(String name) {
super(name);
}

@Override
public void run() {
//耗时操作
for (int i = 0; i < 100; i++) {
//获取线程的名称
System.out.println(getName()+"--"+i+","+new Date());
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}


测试类:
package com.core.thread.demo1;

public class Test {
public static void main(String[] args) {
SleepThread st1 = new SleepThread();
SleepThread st2 = new SleepThread("线程2");
st1.setName("线程1");

st1.start();
st2.start();
}
}


F.加入线程

• 线程加入

• public final void join()

等待线程结束
package com.core.thread.demo1;

import java.util.Date;

public class JoinThread extends Thread {

public JoinThread() {
super();
}
public JoinThread(String name) {
super(name);
}

@Override
public void run() {
//耗时操作
for (int i = 0; i < 100; i++) {
//获取线程的名称
System.out.println(getName()+"--"+i+","+new Date());
}
}
}


测试类:
package com.core.thread.demo1;

public class Test {
// 线程加入
// public final void join()

public static void main(String[] args) {
JoinThread jt1 = new JoinThread("爸爸");
JoinThread jt2 = new JoinThread("大儿子");
JoinThread jt3 = new JoinThread("小儿子");
jt1.start();
try {
jt1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
jt2.start();
jt3.start();
}
}


G.礼让线程

• 线程礼让

• public static void yield()

package com.core.thread.demo3;

import java.util.Date;

public class YieldThread extends Thread {

public YieldThread() {
super();
}

public YieldThread(String name) {
super(name);
}

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


测试类:
package com.core.thread.demo3;

public class Test {
//线程礼让,暂停当前正在执行的线程对象,并执行其他线程
//让多个线程的执行更协调,但是不能保证一人一次
public static void main(String[] args) {
YieldThread st1 = new YieldThread();
YieldThread st2 = new YieldThread("线程2");
st1.setName("线程1");

st1.start();
st2.start();
}
}


H.守护线程

public final void setDaemon(boolean on)

将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。

该方法必须在启动线程前调用。

package com.core.thread.demo4;

import java.util.Date;

public class DaemonThread extends Thread {

public DaemonThread() {
super();
}

public DaemonThread(String name) {
super(name);
}

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

}
}
}


测试类:
package com.core.thread.demo4;

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

public static void main(String[] args) {
DaemonThread dt1 = new DaemonThread();
DaemonThread dt2 = new DaemonThread("关羽");
dt1.setName("张飞");

// 设置dt1和dt2为守护线程,必须放在启动线程之前
dt1.setDaemon(true);
dt2.setDaemon(true);

dt1.start();
dt2.start();
Thread.currentThread().setName("刘备");
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "," + i);
}
}
}


I .中断线程

package com.core.thread.demo5;

import java.util.Date;

public class StopThread extends Thread {

public StopThread() {
super();
}

public StopThread(String name) {
super(name);
}

@Override
public void run() {
long currentTimeMillis = System.currentTimeMillis();
Date date = new Date(currentTimeMillis);
System.out.println("开始时间:" + date);

try {
sleep(5000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("线程被终止");
}

currentTimeMillis = System.currentTimeMillis();
date = new Date(currentTimeMillis);
System.out.println("结束时间:" + date);
}
}


测试类:
package com.core.thread.demo5;

public class Test {

public static void main(String[] args) {
StopThread st1 = new StopThread("线程1");
st1.start();

try {
Thread.currentThread().sleep(3000);
//			st1.stop();
st1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}


注意在终止线程的时候,stop和interrupt的区别。

J.线程生命周期图解



K.多线程方式2的思路及代码实现

l 实现Runnable接口

• 如何获取线程名称

• 如何给线程设置名称

l 实现接口方式的好处

• 可以避免由于Java单继承带来的局限性。

• 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。

package com.core.thread.demo6;

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.core.thread.demo6;

public class Test {
// 多线程的方式2
// a.写一个MyRunnable类继承Runnable接口
// b.重写run()方法
// c.创建MyRunnable对象
// d.创建Thread对象,并把c步骤创建的MyRunnable对象作为构造参数传递
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread t1 = new Thread(runnable, "线程1");
Thread t2 = new Thread(runnable, "线程2");
t1.start();
t2.start();
}
}


L.多线程两种方式的图解比较及区别



M.线程安全

如,某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。

用多线程来实现,两种方法。

第一种,继承Thread类,重写run方法。

第二种,实现Runnable接口。

先用第一种方法:

package com.core.thread.demo7;

public class SellTicket extends Thread {
public static int ticketNum = 100;
public SellTicket(String name){
super(name);
}
@Override
public void run() {
//		while(ticketNum>0){
//
//		}
while(true){
if(ticketNum >0){
System.out.println(Thread.currentThread().getName()+"正在出售"+ticketNum+"张票");
ticketNum--;
}
}
}
}


测试类:

package com.core.thread.demo7;

//某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票
public class Test {
public static void main(String[] args) {
SellTicket st1 = new SellTicket("窗口1");
SellTicket st2 = new SellTicket("窗口2");
st1.start();
st2.start();
}
}


通过打印可以看出,这个是存在线程安全的!暂时先放一下,稍后解决。

接下来,使用实现Runnable接口的方式。

package com.core.thread.demo8;

public class SellTicket implements Runnable {
private static int ticketNum = 100;
@Override
public void run() {
while(true){
if(ticketNum >0){
try {
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售"+ticketNum+"张票");
ticketNum--;
}
}
}

}


package com.core.thread.demo8;

//某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票
//实现Runnable接口
public class Test {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
t1.start();
t2.start();
}

}


注意:为了演示方便,我在售票的时候,睡眠了一段时间。从打印的数据来看,还是存在线程安全的问题。

N.出现了同票和负数票的原因分析

l 问题

• 相同的票出现多次

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

• 还出现了负数的票

• 随机性和延迟导致的

l 注意

• 线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

O.线程安全问题的产生原因分析

l 首先想为什么出现问题?(也是我们判断是否有问题的标准)

• 是否是多线程环境

• 是否有共享数据

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

l 如何解决多线程安全问题呢?

• 基本思想:让程序没有安全问题的环境。

• 怎么实现呢?

• 把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。

P.使用同步代码块解决线程安全问题

package com.core.thread.demo8;

public class SellTicket implements Runnable {
private static int ticketNum = 100;
//创建锁对象
private Object obj = new Object();

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

}


package com.core.thread.demo8;

//某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票
//实现Runnable接口
public class Test {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}

}


Q.同步代码块的特点和优缺点

l 同步的前提

• 多个线程

• 多个线程使用的是同一个锁对象

l 同步的好处

• 同步的出现解决了多线程的安全问题。

l 同步的弊端

• 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

R.同步方法

package com.core.thread.demo10;

public class SellTicket implements Runnable {
private static int ticketNum = 100;
@Override
public void run() {
while(true){
sellTicket();
}
}
private synchronized void sellTicket() {
if(ticketNum >0){
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售"+ticketNum+"张票");
ticketNum--;
}
}

}


S.关于使用同步方法锁的问题

回顾一下,关于我们使用的锁!

package com.core.thread.demo11;

public class SellTicket implements Runnable {
private static int ticketNum = 100;
private Object obj = new Object();
private Test t = new Test();

@Override
public void run() {
while (true) {
// 使用Object类为锁
/*			synchronized (obj) {
if (ticketNum > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售" + ticketNum + "张票");
ticketNum--;
}
}

// 使用任意类为锁
synchronized (t) {
if (ticketNum > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售" + ticketNum + "张票");
ticketNum--;
}
}
*/
// 使用字节码类为锁
synchronized (SellTicket.this) {
if (ticketNum > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售" + ticketNum + "张票");
ticketNum--;
}
}
}
}

private synchronized void sellTicket() {
if (ticketNum > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售"
+ ticketNum + "张票");
ticketNum--;
}
}

}


再回过来,看同步方法到底是使用哪一个锁呢?

package com.core.thread.demo12;

public class SellTicket implements Runnable {
private static int ticketNum = 100;
private Object obj = new Object();
private Test t = new Test();
private int x = 1;

@Override
public void run() {
while (true) {
if (x % 2 == 1) {
synchronized (this) {
// 这里注意了,如果是SellTicke.class 设为锁,则会出现售0票的现象。
// 但是如果要是this,或者SellTicket.this 设为锁,则售票正常[可以看下面的例子,sellTicket为静态方法]
if (ticketNum > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售" + ticketNum + "张票");
ticketNum--;
}
}
} else {
sellTicket();
}
x++;
}
}

private synchronized void sellTicket() {
if (ticketNum > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售"
+ ticketNum + "张票");
ticketNum--;
}
}

}


静态方法发锁:
package com.core.thread.demo12;

//某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票
//实现Runnable接口
public class Test {
//	小结:
//	A:同步代码块上的锁对象是谁?
//		任意对象。
//	B:同步方法是锁是谁?
//		this
//	C:静态方法的锁对象是谁?
//		类的字节码文件

public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}

}


小结:

A:同步代码块上的锁对象是谁?

任意对象。

B:同步方法是锁是谁?

this

C:静态方法的锁对象是谁?

类的字节码文件对象

T.回顾以前的线程安全的类

package com.core.thread.demo13;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;

public class Test {

public static void main(String[] args) {
// 回顾以前讲过的线程安全的类
StringBuffer sb = new StringBuffer();
Vector<Object> vector = new Vector<>();
Hashtable<Object, Object> hashtable = new Hashtable<>();

ArrayList<Object> list = new ArrayList<>();// 线程不安全。
// Collections类中给我提供一个方法
// synchronizedList
// public static <T> List<T> synchronizedList(List<T> list)
// 返回指定列表支持的同步(线程安全的)列表。为了保证按顺序访问,必须通过返回的列表完成所有对底层实现列表的访问。
List<Object> list2 = Collections.synchronizedList(list);//线程安全。
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: