程序与进程
程序:一段静态的代码,是一个普通的文件。
进程:程序一次动态执行过程,程序被加载到内存条中进行执行,最后释放,加载/执行/释放也是进程的生命周期。
任务:进程也可以被称为任务,基于操作系统运行的。
支持多任务的系统被称为多任务系统(Windows)。
多线程与进程
一个进程中也可以有多个任务一起执行,比如QQ,视频的同时也可以语音,打字,还可以发表情。每个任务都被称为一个线程。
多线程奥义:
并发:任务同时发生,同时进行,被称为并发(并行),也就是多线程。
线程不能独立纯在,必须依赖于进程,在进程中运行。
每一个进程至少有一个线程,被称为主线程。
程序被分为
多线程:不只一个线程。
单线程:只有一个线程。
生活例子:
看电视的同时嗑瓜子,吃苹果,每件事都是一个任务/线程。
线程与CPU
电脑有
双核四线程:CPU有两个核心,同一时间可以处理四个线程
四核八线程:CPU有4个核心,同一时间可以处理8个线程
为什么系统里的任务(线程)有很多(比如20个线程),而四核八线程的CPU可以处理20个线程呢?
CPU每回最多处理8个线程,CPU会为每个线程分配一个时间片,且在单位时间内每个线程只能执行一小段,就这样循环切换线程执行,因此我们就看到流程的操作系统。
线程可以设置优先级,同一时间优先级高的优先被执行。
线程优缺点
优点
提高软件界面的响应熟读
提高后台程序加载速度
更好的利用多线程CPU资源,提高效率。
缺点
如果线程过多,系统需要花费大量时间处理线程切换,因此降低了CPU的处理速度,CPU支持的线程数越多,多线程越有优势。
因此要合理利用多线程,不要乱用。
线程随机性:同一时间,哪些线程被执行你是不知道的,如果我们想要控制线程的执行顺序,需要自己编写代码。
创建一个无用的Thread
Java多线程代码编写有三种方式
0.直接new Thread,没有任何意义
1.继承Thread,重写run方法
2.实现Runnable,new Thread();
3.Timer和TimerTask
启动一个线程后,里面默认空的,start之后,立刻就执行完成。
继承Thread,重写run方法
Java程序中默认会有一个main线程
同一个线程不可以启动多次,否则会抛异常
同一个线程类可以创建多条线程分支
只有当程序中所有线程都结束之后,程序才会结束
多线程示例图
创建线程程序示例
public class TVThread extends Thread{
@Override
public void run() {
for(int i = 1; i < 50 ;i++) {
System.out.println(this.getName() + "---看一眼电视 " + i);
try {
//等待0.1s
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class EatThread extends Thread{
public void run() {
for(int i = 0; i<30;i++) {
System.out.println(this.getName() + "====吃一口苹果 "+ i);
try {
//间隔0.2s吃一口苹果
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException { //main线程
//创建线程
TVThread a = new TVThread();
a.setName("a线程");
EatThread b = new EatThread();
b.setName("b线程");
EatThread c = new EatThread();
c.setName("c线程");
//启动线程
a.start();
b.start();
System.out.println("a和b线程已经启动");
//间隔2s后启动c线程
Thread.sleep(2000);
c.start();
System.out.println("---------------c线程已经启动");
System.out.println("main线程结束");
}//main线程结束
}
运行结果:
线程的生命周期
线程的生命周期
1.诞生(new Thread)
当创建一个Thread实例的时候,未启动状态
2.就绪
当start之后,线程就处于就绪状态,时刻准备着被cpu执行
3.运行
线程得到了cpu资源进行运算,但是只有一小段时间片,时间到了之后,cpu就切换到其他线程,当前线程又变为就绪状态。
4.死亡
当run执行结束,或者中途被其他线程杀死,
自然终止:正常运行run()方法结束后终止
异常终止:调用stop()等方法杀死
5.堵塞/阻塞
睡眠:sleep(long ms);
等待:wait
队列:join
sleep方法
一旦调用了sleep方法,该线程就由运行状态进入了阻塞状态。
利用sleep方法对线程的控制是非常不精确的。
join方法
join方法可以精确控制线程
join方法也会导致线程阻塞
特点:如果当前线程中调用了另外一个线程的 join方法,当前线程会立即阻塞,直到另外一个线程运行完成
join方法程序示例
public class ThreadTest {
public static void main(String[] args) throws InterruptedException { //main线程
//创建线程
TVThread a = new TVThread();
a.setName("a线程");
EatThread b = new EatThread();
b.setName("b线程");
//启动线程
a.start();
b.start();
System.out.println("a和b线程已经启动");
//在main线程中写一个join,代表main线程结束后,才会接着执行main线程
a.join();
System.out.println("main线程结束");
}//main线程结束
}
程序运行结果
多线程同步问题
当多个线程同时访问同一个对象时就可能发生不同步问题,不同步问题发生的原因是因为运算流程被破坏。
有个账户类,里面有属性余额
有两个人,同时拥有同一个账户,A人取钱,B人存钱,这两个人同时在操作同一个账户,这就涉及到一个线程同步的问题。
多线程并发问题程序示例
public class Account {
//账户余额
private int n;
public int getN() {
return n;
}
public void setN(int n) {
this.n = n;
}
}
public class AccTest {
public static void main(String[] args) {
Account a = new Account();
a.setN(100);
//匿名内部类
new Thread("张三") { //连续存款50次,每次100
@Override
public void run() {
for(int i = 0;i < 50;i++) {
try {
//获取账户金额
int n = a.getN();
n += 100;
//延时1ms
sleep(1);
a.setN(n);
System.out.println(this.getName() + "存入100元后的余额: " + a.getN());
} catch (Exception e) {
// TODO: handle exception
}
}
}
}.start();
new Thread("小明") {//连续取款存款50次,每次100
@Override
public void run() {
for(int i = 0;i < 50;i++) {
try {
//获取账户金额
int n = a.getN();
n -= 100;
//延时1ms
sleep(1);
a.setN(n);
System.out.println(this.getName() + "取出存入100元后的余额: " + a.getN());
} catch (Exception e) {
// TODO: handle exception
}
}
}
}.start();
}
}
程序运行的结果
显然结果是不对的,因为没有进行线程同步,请看下一节线程同步。
结果不对的最终原因:
当李四存钱时,取出账户余额100,然后存钱100+100 = 200,存完的钱还没有存入到内存中,这时小明开始存钱,取出账户余额100,然后取钱100-100=0,小明这个线程比李四线程执行的快一些(有可能是网络延时问题/CPU执行线程的随机性),导致小明取出后的钱被优先存入到内存中,这时呢?李四线程执行完,把200元存入到内存中,此时账户的余额是200元,这肯定不对。
synchronized线程同步
账户问题的解决方案:
当一个用户操作账户时,就应该将当前用户锁死,其他用户不可以操作该账户,也就是排队问题。
synchronized:线程同步,让线程排队,解决不同步问题
锁:队伍中的信物,使用不同的锁就有不同的队伍
可以把Account,Data,Object等等类的对象当做锁。
当一个用户操作账户类时,我用账户作为锁,如果这时另一个进程识别到这个锁时,就不会执行锁里的程序块。
只有当锁里的程序块被执行完时,才把锁打开,其它线程可以重新使用这个锁,并且执行锁里的程序块。
程序示例
public class Account {
//账户余额
private int n;
public int getN() {
return n;
}
public void setN(int n) {
this.n = n;
}
}
public class AccTest {
public static void main(String[] args) {
Account a = new Account();
a.setN(100);
//匿名内部类
new Thread("张三") { //连续存款50次,每次100
@Override
public void run() {
for(int i = 0;i < 50;i++) {
try {
synchronized(a) {
//获取账户金额
int n = a.getN();
n += 100;
//延时1ms
sleep(1);
a.setN(n);
System.out.println(this.getName() + "存入100元后的余额: " + a.getN());
}
System.out.println(this.getName() + "-----OVER------"+a.getN());
} catch (Exception e) {
// TODO: handle exception
}
}
}
}.start();
new Thread("小明") {//连续取款存款50次,每次100
@Override
public void run() {
for(int i = 0;i < 50;i++) {
try {
synchronized (a) {
//获取账户金额
int n = a.getN();
n -= 100;
//延时1ms
sleep(1);
a.setN(n);
System.out.println(this.getName() + "取出100元后的余额: " + a.getN());
}
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(this.getName() + "-----OVER------"+a.getN());
}
}
}.start();
}
}
核心重点:
1.什么是多线程
2.多线程怎么用
3.线程不同步问题
4.不同步问题的解决方案
创建线程的5种方法
直接new Thread
//创建一个线程对象
Thread t1 = new Thread();
//因为最原始的线程里面没有内容,start之后线程就会立马消失
t1.start();
继承Thread类,重写run方法
这种方法比较常用
public class Eat extends Thread{
@Override
public void run() {
try {
System.out.println("开始吃-----"+this.getName());
for(int i = 1;i < 5;i++) {
System.out.println("吃一口------"+i);
sleep(200);
}
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(this.getName() + "吃完了");
}
}
public class Test {
public static void main(String[] args) {
//创建吃线程
Eat e = new Eat();
//为当前线程起个名称
e.setName("张三");
//启动线程
e.start();
System.out.println("主线程结束了");
}
}
程序运行结果
实现Runnable接口
TV类不是一个线程,然而这个TV类中的run方法有可能被需要线程调用。
//Eat不是一个线程,Eat中的run方法可以被多个线程调用
public class Eat extends Object implements Runnable{
@Override
public void run() {
try {
//获取当前正在执行这段代码的线程
Thread now = Thread.currentThread();
String str = now.getName();
System.out.println("开始吃-----" + str);
for(int i = 1;i < 5;i++) {
System.out.println(str + "吃一口------"+i);
Thread.sleep(200);
}
System.out.println(str + "吃完了");
} catch (Exception e) {
// TODO: handle exception
}
}
}
public class Test {
public static void main(String[] args) {
//该类实现了Runnable接口
Eat e = new Eat();
//将Runnable接口的实现传入线程中
Thread t1 = new Thread(e, "小明");
t1.start();
Thread t2 = new Thread(e, "小天");
t2.start();
System.out.println("主线程结束了");
}
}
程序运行的结果
匿名内部类
匿名内部类,适合局部少量使用
public class Test {
public static void main(String[] args) {
//匿名内部类
new Thread("小李") {
@Override
public void run() {
try {
for(int i = 0;i < 5;i++) {
System.out.println(getName() + "看一眼 ----" + i);
sleep(200);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
System.out.println("主线程结束了");
}
}
程序运行结果
Runnable匿名内部类
public class Test {
public static void main(String[] args) {
//Runnable匿名内部类
new Thread(new Runnable() {
@Override
//run可以被不同的线程调用
public void run() {
try {
for(char i = 'A';i < 'Z';i++) {
//获取当前进程
Thread now = Thread.currentThread();
System.out.println(now.getName() + "看一眼 ----" + i);
Thread.sleep(200);
}
} catch (Exception e) {
// TODO: handle exception
}
}
}, "张三").start();
System.out.println("主线程结束了");
}
}
程序运行结果
多线程经典——生产者和消费者
notify与wait
notify解锁线程,wait锁住线程。
使用notify和wait方法必须对线程进行同步,否则会报错。
notify和wait是依赖于同步锁,因此synchronized中的锁,必须填写notify和wait的线程。
接下来说说利用wait()和notify()来实现生产者和消费者并发问题:
显然要保证生产者和消费者并发运行不出乱,主要要解决:当生产者线程的缓存区为满的时候,就应该调用wait()来停止生产者继续生产,而当生产者满的缓冲区被消费者消费掉一块时,则应该调用notify()唤醒生产者,通知他可以继续生产;同样,对于消费者,当消费者线程的缓存区为空的时候,就应该调用wait()停掉消费者线程继续消费,而当生产者又生产了一个时就应该调用notify()来唤醒消费者线程通知他可以继续消费了。
这里需要强调的是,wait方法和notify方法,并不是Thread线程上的方法,它们是Object上的方法。
因为所有的Object都可以被用来作为同步对象,所以准确的讲,wait和notify是同步对象上的方法。
wait()的意思是: 让占用了这个同步对象的线程,临时释放当前的占用,并且等待。 所以调用wait是有前提条件的,一定是在synchronized块里,否则就会出错。
notify() 的意思是,通知一个等待在这个同步对象上的线程,你可以苏醒过来了,有机会重新占用当前对象了。
调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor(锁)的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;
1个线程被唤醒不代表立即获取了对象的monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行。
notify和wait的程序演示
public class Test {
// 在多线程间共享的对象上使用wait
private static String shareObj = "true";
public static void main(String[] args) throws IOException, ClassNotFoundException {
Thread1 a = new Thread1();
a.setName("a");
/* 启动线程a */
a.start();
Thread2 b = new Thread2();
b.setName("b");
/* 启动线程b */
b.start();
}
static class Thread1 extends Thread{
@Override
public void run() {
try {
synchronized(shareObj) {
System.out.println("开始等待线程获取锁");
/* 等待获取锁,需要其他线程将对象锁进行释放 */
shareObj.wait();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.getName() + "线程,获取到锁");
}
}
static class Thread2 extends Thread{
@Override
public void run() {
try {
synchronized (shareObj) {
shareObj.notify();
System.out.println("线程"+Thread.currentThread().getName() + "调用shareObj.notify()");
/* 只要当前线程等待2s后,线程Thread1才会被执行
* 因为只要执行完synchronized语句块,对象锁才会被释放
* 当对象锁被释放的时候,线程Thread1才会执行,因为获得到锁
*/
sleep(2000);
}
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(this.getName() + "释放了锁");
}
}
}
生产者与消费者的程序
package demo;
import java.util.Vector;
public class Test {
/* 鞋店 */
public static Vector<Xie> shop = new Vector<Xie>();
public static void main(String[] args) {
//开始生产
Producer a = new Producer();
a.start();
//开始消费
Custormer b = new Custormer();
b.start();
}
/**
* 生产者/工厂
* 鞋店里最多放10个,工厂生产数量达到30个就解散
*/
static class Producer extends Thread{
@Override
public void run() {
for(int i = 1;i <= 30;i++) {
try {
/*
* 生产一双鞋,同时将这双鞋添加到鞋店里
* 为了模拟现实,生产一双鞋是需要时间的,因此
* 使用sleep延时1s。
*/
Xie x = new Xie();
sleep(1000);
shop.add(x);
synchronized(shop) {
/*
*已经生产一双鞋,消费者可以购买
*首先要为商店对象解锁,以便消费者线程可以获取到shop锁
*接着执行。
*/
System.out.println("工厂生产一双鞋了");
System.out.println("当前鞋的数量为:" + shop.size());
shop.notify();
/*
* 判断商店的鞋是否大于10双
* 如果大于10双,就让该线程停在这块,等待其他线程
* 释放shop对象锁。
*/
while(shop.size() >= 10) {
System.out.println("鞋店,库存达到10个,停止进货");
shop.wait();
}
}
} catch (Exception e) {
// TODO: handle exception
}
}
System.out.println("工厂生产的鞋的数量达到上限,工厂倒闭了");
}
}
/**
* 消费者
* @author 27823
*
*/
static class Custormer extends Thread{
@Override
public void run() {
//消费者买50双鞋
for(int i = 0;i < 50;i++) {
System.out.println("消费者来了....");
try {
synchronized(shop) {
/*
*判断鞋店是否有鞋,可供消费者进行购买
*如果没有鞋,就使用wait方法使消费者线程处于等待的状态,需要生产者
*使用 notify()释放锁,然后消费者就可以获取到锁,即线程可以接着执行。
*/
while(shop.size() <= 0) {
System.out.println("仓库空荡荡,消费者在等待着!!!");
shop.wait();
}
/* 消费者买鞋,取走第一双鞋 */
shop.removeElementAt(0);;
System.out.println("消费者买了一双鞋,商店还剩" + shop.size() + "双鞋");
/* 唤醒工厂线程 */
shop.notify();
}
System.out.println("客户:全五星好评,2秒后再买");
/*
* 这个延时时间是模拟人购买的时间间隔,在这个时间间隔里,工厂可以接着生产鞋
* 注意:该sleep必须放在synchronized语句外边,因为notify()方法只是唤醒工厂线程
* ,然工厂线程准备执行,但是程序只要没有执行完synchronized语句块,shop对象锁
* 就不会被释放,只有出了synchronized语句块,对象锁才会被释放,因此必须将延时的
* 时间放在外边,当延时2000ms时,工厂线程在执行中(生产鞋子)。
*/
sleep(2000);
} catch (Exception e) {
// TODO: handle exception
e.getStackTrace();
}
}
}
}
//鞋
static class Xie{
}
}