概述
线程基础
进程与线程
线程分类
多线程的优势
线程控制
线程的创建和启动
线程的状态
线程调度
线程同步
线程同步的必要性
线程同步的实现
死锁
线程间通信
线程间通信的必要性
线程间通信的实现
程序
程序是一段静态的代码,它是应用程序执行的蓝本
进程
进程是指一种正在运行的程序,有自己的地址空间
进程的特点
动态性
并发性
独立性
线程的定义
进程内部的一个执行单元,它是程序中一个单一的顺序控制流程。如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程
进程是系统资源分配的单元,可包括多个线程
线程是独立调度和分派的基本单位,共享进程资源
引入进程是为了对个线程并发执行,提高资源的利用率和系统吞吐量
引入线程是为了减少程序在并发执行时付出的时空开销
线程分类
系统级线程
由操作系统内核进行管理,使用户程序可以创建、执行、撤销线程
用户级线程
管理过程全部由用户程序完成,操作系统内核只对进程进行管理
多线程的优势
多线程使系统空转时间减少,提高CPU 利用率
进程间不能共享内存,但线程之间共享内存非常容易
使用多线程实现任务并发比多线程的效率高
Java语言内置多线程功能支持,简化了Java的多线程编程
线程的创建和启动
两种方法来创建线程
继承 Java.lang.Thread类,并覆盖run()方法
class MyThread extends Thread {
public void run( ) {
/* 覆盖该方法*/
}
}
实现Java.lang.Runnable接口,并实现run()方法
class MyThread implements Runnable{
public void run( ) {
/* 实现该方法*/
}
}
启动线程
新建的线程不会自动开始运行,必须通过start()方法启动
启动继承Thread的线程
MyThread t = new MyThread ();
t.start();
启动实现Runnable接口的线程
MyThread mt = new MyThread ();
Thread t = new Thread(mt);
t.start();
继承 Java.lang.Thread类
代码实现如下:
public class ThreadDemo1 {
public static void main(String args[]) {
MyThread1 t = new MyThread1();
t.start();
while (true) {
System.out.println("兔子领先了,别骄傲");
}
}
}
class MyThread1 extends Thread {
public void run() {
while (true) {
System.out.println("乌龟领先了,加油");
}
}
}
实现Java.lang.Runnable接口
代码实现如下:
public class ThreadDemo2 {
public static void main(String args[]) {
MyThread2 mt = new MyThread2();
Thread t = new Thread(mt);
t.start();
while (true) {
System.out.println("兔子领先了,加油");
}
}
}
class MyThread2 implements Runnable {
public void run() {
while (true) {
System.out.println("乌龟超过了,再接再厉");
}
}
}
两种线程创建方式的比较
继承Thread类方式的多线程
优势:编写简单
劣势:无法继承其它父类
实现Runnable接口方式的多线程
优势:可以继承其它类,多线程可共享同一个Thread对象
劣势:编程方式稍微复杂,如果需要访问当前线程,需要调用Thread.currentThead()方法
Thread类的常用方法
方法 | 功能 |
static Thread currentThread() | 得到当前线程 |
final String getName() | 返回线程的名称 |
final void setName(String name) | 将线程的名称设置为name指定的名称 |
void start() | 调用run()方法启动线程,开始线程的执行 |
oid run() | 存放线程体代码 |
线程的状态
新生
使用new 关键字创建一个线程后,尚未调用其start方法之前
可运行
调用线程对象的start方法之后
这个状态当中,线程对象可能正在运行,也可能等待运行
阻塞
一种“不可运行”的状态,在得到一个特定的事件之后会返回到可运行状态
死亡
线程的run方法运行完毕或者在运行中出现未捕获的异常时
生命周期:
线程调度
优先级概述
每个线程执行时都具有一定的优先级。当调度线程时,会优先考虑级别高的线程
默认情况下,一个线程继续其父线程的优先级
使用线程对象.setPriority(p)来改变线程的优先级
优先级影响CPU在线程间切换,切换原则是:
当一个线程通过显式放弃、睡眠或者阻塞、自愿释放控制权时,所有线程均接受检查而优先级高线程将会优先执行
一个线程可以被一个高优先级的线程抢占资源
同级别的线程之间,则通过控制权的释放,确保所有的线程均有机会运行
join()
阻塞指定线程等到另一个线程完成以后再继续执行
代码实现如下:
public class JoinTest extends Thread {
public JoinTest(String name) {
super(name);
}
public void run() {
for (int i = 0; i < 5; i++)
System.out.println(getName() + "" + i);
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
if (i == 5) {
JoinTest tempjt = new JoinTest("半路加入的线程");
try {
tempjt.start();
tempjt.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+""+i);
}
}
}
sleep()
使线程停止爱运行一段时间,将处于阻塞状态
阻塞的时间有指定的毫秒数决定
代码实现如下:
public class TestSleep {
public static void main(String[] args) {
System.out.println("Wait");
// 让主线程等待5秒再执行
Wait.bySec(5);
// 提示恢复执行
System.out.println("start");
}
}
class Wait {
public static void bySec(long s) {
// sleep s个1秒
for (int i = 0; i < s; i++) {
System.out.println(i + 1 + "秒");
try {
// sleep1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
yield()
让当前正在执行的线程暂停
该方法不会阻塞线程,而是将线程转入可运行状态
代码实现如下:
public class YeildTest {
public static void main(String[] args) {
TheThread mt = new TheThread();
MyNewThread mnt = new MyNewThread();
mt.start();
mnt.start();
}
}
class TheThread extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("第一个线程的第 " + (i + 1) + "次运行");
Thread.yield();
}
}
}
class MyNewThread extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("第二个线程的第 " + (i + 1) + "次运行");
Thread.yield();
}
}
}
sleep()和yiel()对比
sleep() | yiel() | |
暂停后的状态 | 进入被阻塞的状态,直到经过指定时间后,才进入可运行状态 | 直接将当前线程转入可运行状态 |
没有其他等待运行的线程 | 当前线程会继续等待指定的时间 | 当前线程会马上恢复执行 |
等待线程的优先级别 | 不考虑,机会均等 | 将优先级相同或更高的线程运行 |
setDaemon()
可以将指定的线程设置成后台线程
创建后台线程的线程结束时,后台线程也随之消亡
代码实现如下:
public class DaemonTest extends Thread {
public void run() {
while (true) {
System.out.println(getName());
}
}
public static void main(String[] args) {
DaemonTest dt = new DaemonTest();
dt.setDaemon(true);
dt.setName("后台线程");
dt.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
线程同步的必要性
当多个线程访问同一个数据时,容易出现线程安全问题。需要让线程同步,保证数据安全
线程同步
当两个或两个以上线程访问同一资源时,需要某种方式来确保资源在某一时刻只被一个线程使用
线程同步的实现方案
同步代码块
同步方法
同步代码块语法:
synchronized(obj){
//此处代码为同步代码块
}
public class TestAccount implements Runnable {
// 所有的用此TestAccount对象创建的线程共享同一个线程
private Account acct = new Account();
public void run() { … }
private void makeWithdrawal(int amt) {
synchronized (acct) {
if (acct.getBalance() >= amt) {
System.out.println(Thread.currentThread().getName()
+ " 准备取款");
try {
Thread.sleep(500);
} catch (InterruptedException ex) { }
acct.withdraw(amt); // 如果余额足够,则取款
System.out.println(Thread.currentThread().getName()
+ " 完成取款");
} else {// 余额不够给出提示
System.out.println("余额不足以支付"+ Thread.currentThread() .getName() + " 的取款,余额为 "+ acct.getBalance());
}
}
}
}
同步方法语法:
访问修饰符 synchronized 返回类型 方法名{
}
public class TestAccount implements Runnable {
// 所有的用此TestAccount对象创建的线程共享同一个线程
private Account acct = new Account();
public void run() {
for (int x = 0; x < 5; x++) {
makeWithdrawal(100); // 取款
if (acct.getBalance() < 0) System.out.println("账户透支了!");
}
}
private synchronized void makeWithdrawal(int amt) {
if (acct.getBalance() >= amt) {
System.out.println(Thread.currentThread().getName()+"准备取款");
try {
Thread.sleep(500);
} catch (InterruptedException ex) { }
acct.withdraw(amt); // 如果余额足够,则取款
System.out.println(Thread.currentThread().getName() + " 完成取款");
} else {// 余额不够给出提示
System.out.println("余额不足以支付 " + Thread.currentThread().getName()
+ " 的取款,余额为 " + acct.getBalance());
}
}
}
死锁
线程同步的好处
解决了线程安全问题
线程同步的缺点
性能下降
会带来死锁
死锁
当两个线程相互等待对方释放“锁”时就会发生死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
多线程编程时应该注意避免死锁的发生
线程间通信的必要性
在生产者和消费者问题中,仅有synchronized是不够的
synchronized可阻止并发更新同一个共享资源,实现同步
synchronized不能用来实现不同线程之间的消息传递(通信)
Java提供了3个方法解决线程之间的通信问题
方法名 | 作用 |
final void wait() | 表示线程一致等待,直到其它线程通知 |
void wait(long timeout) | 线程等待指定毫秒参数的时间 |
final void wait(long timeout,int nanos) | 线程等待指定毫秒、微秒的时间 |
final void notify() | 唤醒一个处于等待状态的线程 |
final void notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先运行 |
均是java.lang.Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常
代码实现如下:
定义产品类
class SharedData{
private char c;
private boolean isProduced = false; // 信号量
public synchronized void putShareChar(char c) {
// 如果产品还未消费,则生产者等待
if (isProduced) {
try{ System.out.println("消费者还未消费,因此生产者停止生产");
wait(); // 生产者等待
} catch (InterruptedException e) {e.printStackTrace(); }
}
this.c = c;
isProduced = true; // 标记已经生产
notify(); // 通知消费者已经生产,可以消费
System.out.println("生产了产品" + c + " 通知消费者消费...");
}
public synchronized char getShareChar() {
// 如果产品还未生产,则消费者等待
if (!isProduced){
try{ System.out.println("生产者还未生产,因此消费者停止消费");
wait(); // 消费者等待
} catch (InterruptedException e) {e.printStackTrace();}
}
isProduced = false; // 标记已经消费
notify(); // 通知需要生产
System.out.println("消费者消费了产品" + c + " 通知生产者生产...");
return this.c;
}
}
定义生产者线程类
//生产者线程
class Producer extends Thread {
private SharedData s;
Producer(SharedData s){
this.s = s;
}
public void run(){
for (char ch = 'A'; ch <= 'D'; ch++){
try{
Thread.sleep((int) (Math.random() * 3000));
} catch (InterruptedException e) {
e.printStackTrace();
}
s.putShareChar(ch); // 将产品放入仓库
}
}
}
定义消费者线程类
//消费者线程
class Consumer extends Thread {
private SharedData s;
Consumer(SharedData s){
this.s = s;
}
public void run(){
char ch;
do {
try {
Thread.sleep((int)(Math.random()*3000));
} catch (InterruptedException e) {
e.printStackTrace();
}
ch = s.getShareChar(); // 从仓库中取出产品
} while (ch != 'D');
}
}
定义测试类
//测试类
class CommunicationDemo{
public static void main(String[] args){
//共享同一个共享资源
SharedData s = new SharedData();
//消费者线程
new Consumer(s).start();
//生产者线程
new Producer(s).start();
}
}