Java线程和多线程
1.Java线程
1.1 进程
进程:进程是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
1.2 什么是线程
线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。
如果每个请求都创建一个线程去处理,那么服务器的资源很快就会被耗尽,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
线程分类:
线程:进程的执行单元,执行路径
单线程:一个应用程序只有一条执行路径
多线程:一个应用程序有多条执行路径
多进程的意义?—— 提高CPU的使用率
多线程的意义? —— 提高应用程序的使用率
1.3 进程和线程的区别是什么?
进程是执行着的应用程序,而线程是进程内部的一个执行序列。
一个进程可以有多个线程。线程又叫做轻量级进程。
线程与进程的区别归纳:
a.地址空间和其它资源:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
b.通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
c.调度和切换:线程上下文切换比进程上下文切换要快得多。
d.在多线程OS中,进程不是一个可执行的实体。
1.3 什么是多线程
多线程:在同一个时间段内可以执行多个任务,提高了CPU的使用率。
1.3 线程的创建-继承Thread类
一:Thread类
1、创建一个Thread类,或者一个Thread子类的对象
2、Thread是一个线程类,位于java.lang包下
3、Thread类的常用方法
二:创建线程
1、通过继承Thread类的方式创建线程类,重写run()方法。
2、线程的运行是随机的。
3、代码如下
package com.imooc.thread1;
class MyThread extends Thread{
public MyThread(String name){
super(name);
}
@Override
public void run(){
for (int i=1;i<=20;i++){
System.out.println(getName()+"正在运行:"+i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//两个线程演示,多线程效果需要创建多个对象而不是一个对象多次调用start()方法
MyThread tm1 = new MyThread("主线程1");
MyThread tm2 = new MyThread("主线程2");
//启动线程,线程不能多次启动
tm1.start();
//启动线程,线程不能多次启动
tm2.start();
}
}
1.4 线程的创建-实现Runnable接口(该方式应用更广泛)
一:创建一个实现Runnable接口的类的对象
1、只有一个方法run();
2、Runnable是Java中用以实现线程的接口
3、任何实现线程功能的类都必须实现接口
二:代码测试
1、Runnable的run方法可以被多个线程共享,就Thread类的实例
2、代码
package com.imooc.runnable;
class PrintRunnable implements Runnable{
//测试2:
//int i = 1;
//Runnable的run方法可以被多个线程共享,就Thread类的实例
@Override
public void run() {
//获取当前正在运行的线程名称
String name = Thread.currentThread().getName();
int i = 1;
while (i<=20){
System.out.println(name+"子线程正在运行:"+(i++));
}
}
}
public class Test {
public static void main(String[] args) {
//定义runnable实现类的对象
PrintRunnable pr1 = new PrintRunnable();
//创建线程类对象
Thread thread1 = new Thread(pr1);
//启动线程
thread1.start();
PrintRunnable pr2 = new PrintRunnable();
//测试2:替换为pr1进行测试
Thread thread2 = new Thread(pr2);
thread2.start();
}
}
1.5 线程的状态和生命周期
**一:线程的5个状态 **
1、新建( new ):新创建了一个线程对象。
2、可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu的使用权 。
- 线程什么时候运行是由CPU决定的,只有当该线程获取CPU的使用权的使用,它才能运行。
3、正在运行( running ):可运行状态( runnable )的线程获得了cpu时间片( timeslice ) ,执行程序代码。
4、阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu 时间片轮转 到运行( running )状态。阻塞的情况分三种: - 等待阻塞:运行( running )的线程执行 o . wait ()方法, JVM 会把该线程放 入等待队列( waitting queue )中。
- 同步阻塞:运行( running )的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。
- 其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t . join ()方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。 当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。
5、死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。
二:线程的生命周期 - 时间片轮转方式让线程轮流执行
人的生命:出生-成长(生病导致死亡/生病医治成功重新正常成长)-结束
注意:stop()方法不建议使用了。
三:sleep方法的使用
1、Thread类的方法–线程休眠
public static void sleep(long millis)
2、作用:在指定的毫秒数内让正在执行的线程休眠(暂停执行)
3、参数为休眠的时间,单位是毫秒
4、代码
package com.imooc.runnable;
class MyThread1 implements Runnable{
@Override
public void run() {
for (int i=1;i<=30;i++){
System.out.println(Thread.currentThread().getName()+"执行第"+i+"次!");
try{
//休眠这么长的时间,重新变成可运行的状态,等待获取CPU的使用权
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
public class SleepDemo {
public static void main(String[] args) {
//定义runnable实现类的对象
MyThread1 mt = new MyThread1();
//创建线程类对象
Thread thread = new Thread(mt);
//启动线程
thread.start();
Thread thread1 = new Thread(mt);
thread1.start();
}
}
四:join方法的使用
1、Thread类的方法
public final void join();//该方法不能被重写
作用:等待调用该方法的线程结束后才能执行(其实是一种抢占资源的方式)
public final void join(long millis);//该方法不能被重写
作用:等待该线程终止的最长时间为millis毫秒
//join()方法抢占资源
//join()方法不指定时间,其它线程会一直等待调用join方法的线程执行完毕以后,其它线程才能执行
//thread.join();
//join()方法指定时间,在join方法的时间有效期过来以后,不管调用join方法的线程是否执行完毕,其它线程都会来执行
2、 代码
package com.imooc.runnable;
class MyThread implements Runnable{
@Override
public void run() {
for (int i=1;i<=500;i++){
System.out.println(Thread.currentThread().getName()+"子线程正在执行第"+i+"次");
}
}
}
public class JoinDemo {
public static void main(String[] args) {
MyThread mt = new MyThread();
Thread thread = new Thread(mt);
thread.start();
//测试:
// 没有join方法,主线程和子线程随机执行,谁获取到CPU使用权就谁先执行
try {
//join()方法抢占资源
//join()方法不指定时间,其它线程会一直等待调用join方法的线程执行完毕以后,其它线程才能执行
//thread.join();
//join()方法指定时间,在join方法的时间有效期过来以后,不管调用join方法的线程是否执行完毕,其它线程都会来执行
thread.join(1);
}catch (InterruptedException e){
e.printStackTrace();
}
for (int i=1;i<=15;i++){
System.out.println("主线程运行第"+i+"次");
}
System.out.println("主线程运行结束");
}
}
1.6 线程的优先级
1、Java为线程类提供了10个优先级
2、优先级可以用整数1-10表示,超过范围会抛出异常
3、主线程默认优先级为5
4、优先级常量
- MAX_PRIORITY:线程的最高优先级10
- MIN_PRIORITY:线程的最低优先级1
- NORM_PRIORITY:线程的默认优先级5
5、优先级相关的方法
public int getPriority():获取线程优先级的方法
public void setPriority(int newProirity):设置线程优先级的方法
6、优先级的结论:线程的优先级的设置与操作系统的环境,以及CPU的调用方式都是有关系的,不能完全保证优先级高的线程一定先执行
7、代码
package com.imooc.runnable;
class MyThread2 extends Thread{
private String name;
public MyThread2(String name){
this.name = name;
}
@Override
public void run() {
for (int i=1;i<=50;i++){
System.out.println("线程"+name+"正则运行"+i);
}
}
}
public class ProirityDemo {
public static void main(String[] args) {
//获取主线程的优先级
int mainProirity = Thread.currentThread().getPriority();
//System.out.println("主线程的优先级为:"+mainProirity);
MyThread2 mt1 = new MyThread2("子线程1");
MyThread2 mt2 = new MyThread2("子线程2");
mt1.setPriority(Thread.MAX_PRIORITY);
mt2.setPriority(Thread.MIN_PRIORITY);
mt2.start();
mt1.start();
//System.out.println("子线程1的优先级为:"+mt1.getPriority());
}
}
1.7 线程同步(线程互斥)
一:多线程运行的问题
1、各个线程是通过竞争CPU时间而获得运行机会的
2、各个线程什么时候得到CPU时间,占用多久,是不可预测的
3、一个正在运行着的线程在什么地方被暂停是不确定的
二:银行存取款的问题
1、为什么会出现存/取款线程不一致的原因?
就是因为代码在执行到一半的时候被打断,就会造成数据没有进行及时的更新。
存款线程在执行过程是随机进行终止的,如为更改账户余额成功就终止;存款线程执行sleep方法,线程终止
取款操作线程就执行,发现余额还是1000,并没有被修改(+100);取款线程执行sleep方法,线程终止
存款线程获得CPU使用权继续执行,修改账户余额=1100;存款线程执行完成后
取款线程继续修改账户余额,把之前获取的余额(800)写入账户。
三:同步 - synchronized
1、为了保证在存款或取款的时候,不允许其它线程对账户余额进行操作
2、需要将Bank对象进行锁定
3、使用同步关键字synchronized实现
同步关键字可以确保共享对象(银行类对象/账户),共享对象在同一时刻只能被一个线程访问
4、synchronized关键字用在
- 成员方法
- 静态方法
- 语句块
public synchronized void saveAccount(){}
public static synchronized void saveAccount(){}
synchronized(obj){......}
四:代码实现-可以对不加synchronized和加synchronized的代码进行测试
1、Bank类
package com.imooc.bank;
public class Bank {
private String account;//账户
private int balance;//账号余额
public Bank(String account, int balance) {
this.account = account;
this.balance = balance;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Bank{" +
"account='" + account + '\'' +
", balance=" + balance +
'}';
}
//存款
//存款线程在执行过程是随机进行终止的,如为更改账户余额成功就终止
public synchronized void saveAccount(){
//可以在不同的位置处添加sleep方法
//获取当前账户余额
int balance = getBalance();
// 线程终止
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
//修改余额,存100元
balance +=100;
//修改账户余额
setBalance(balance);
//输出存款后的账户余额
System.out.println("存款后的账户余额为:"+balance);
}
//取款
public void drawAccount(){
//this-->Bank类
synchronized(this){
//可以在不同的位置处添加sleep方法
int balance = getBalance();
balance = balance - 200;
//存款线程终止,取款操作线程就执行,发现余额还是1000,并没有被修改(+100)
// 线程终止
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
//修改账户余额
setBalance(balance);
System.out.println("取款后的账户余额:"+balance);
}
}
}
2、存款-SaveAccount
package com.imooc.bank;
//存款
public class SaveAccount implements Runnable{
Bank bank;
public SaveAccount(Bank bank){
this.bank = bank;
}
@Override
public void run() {
bank.saveAccount();
}
}
3、取款-DrawAccount
package com.imooc.bank;
//取款
public class DrawAccount implements Runnable {
Bank bank;
public DrawAccount(Bank bank){
this.bank = bank;
}
@Override
public void run() {
bank.drawAccount();
}
}
4、测试-Test
package com.imooc.bank;
/**
* 银行存取款操作
*/
public class Test {
public static void main(String[] args) {
//创建账户,给定余额为1000
Bank bank = new Bank("1001", 1000);
//创建线程对象
SaveAccount sa = new SaveAccount(bank);
DrawAccount da = new DrawAccount(bank);
Thread save = new Thread(sa);
Thread draw = new Thread(da);
save.start();
draw.start();
try{
draw.join();
save.join();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(bank);
}
}
四:问题:账户余额不够了怎么办?
解决:等待存入足够的钱之后进行取款操作
1.8 线程间的通信(同步与死锁)
一:问题:账户余额不够了怎么办?
解决:等待存入足够的钱之后进行取款操作
二:线程间的通信
1、wait()方法:中断方法的执行,使线程等待
- 等待其实是处于线程阻塞状态
- 死锁状态:生产者线程在等待消费,而消费者线程也同样在等待生产,那么这两个线程互相等待永远都不 可能继续执行,这时就处于死锁是状态
2、notify()方法:唤醒处于等待的某一个线程,使其结束等待
3、notifyAll()方法:唤醒所有处于等待的线程,使它们结束等待
三:代码
1、队列类-Queue
package com.imooc.queue;
public class Queue {
private int n;
//标志 false没有数据 true有数据
boolean flag = false;
//消费
public synchronized int getN() {
//没有数据,消费线程等待,生产线程获取CPU使用权,执行生产操作
if (!flag){
try{
wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
System.out.println("消费:"+n);
//消费完毕,容器中没有数据
flag = false;
//不唤醒线程可能会发生死锁,程序不能继续执行下去
notifyAll();
return n;
}
//生产
public synchronized void setN(int n) {
if (flag){
try{
wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
System.out.println("生产:"+n);
this.n = n;
//生产完毕,容器中已经有数据
flag = true;
//不唤醒线程可能会发生死锁,程序不能继续执行下去
notifyAll();
}
}
2、生产者
package com.imooc.queue;
//生产者
public class Producer implements Runnable{
Queue queue;
public Producer(Queue queue){
this.queue = queue;
}
@Override
public void run() {
int i = 0;
while (true){
queue.setN(i++);
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
3、消费者
package com.imooc.queue;
//消费者
public class Consumer implements Runnable{
Queue queue;
public Consumer(Queue queue){
this.queue = queue;
}
@Override
public void run() {
while (true){
queue.getN();
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
4、测试类
package com.imooc.queue;
/**
* 测试
*/
public class Test {
//需求:生产一个消费一个
//不能出现2次生产1次消费;生产1次消费多次
public static void main(String[] args) {
Queue queue = new Queue();
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
}
}
1.9 同步方法和同步代码块的区别是什么?
同步方法默认用this或者当前类class对象作为锁;
同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法;
同步方法使用关键字 synchronized修饰方法,而同步代码块主要是修饰需要进行同步的代码,用 synchronized(object){代码内容} 进行修饰;
1.20 在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?
监视器和锁在Java虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许执行同步代码。
2. Java锁
2.1 什么是锁?
2.2 什么是死锁(deadlock)?
所谓死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
死锁产生的4个必要条件:
- 互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某 资源仅为一个进程- 所占有。此时若有其他进程请求该资源,则请求进程只能等待。
- 不剥夺条件: 进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
- 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
- 循环等待条件: 存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。
2.3 如何确保N个线程可以访问N个资源同时又不导致死锁?
使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。
因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。