------- android培训、java培训、期待与您交流! ----------
进程:
是一个正在运行中的程序,每一个进程执行,都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元
线程:
进程中的一个独立的控制单元,线程在控制着进程的执行,一个进程中至少有一个线程(控制单元)
JVM:
运行时会有一个进程叫做java.exe,该进程中至少一个线程负责java程序的执行,而且这个程序的代码存在于main方法中该线程称之为主线程。
扩展:其实更细节的说明JVM,JVM启动时不止一个线程,还有一个垃圾回收机制的线程
如何在自定义的代码中自定义一个线程呢?
通过API文档,java已经提供了对线程这类事物的描述,就是Thread类。
继承Thread类(用于描述线程)
步骤:
1、首先,定义类继承Thread;
2、复写Thread类中的run方法;目的:将自定义代码存储在run方法中,让线程执行
3、调用线程的start方法(该方法有两个作用:启动线程和调用run()方法)
示例:
public class Test1 {
public static void main(String[] args)
{
Demo d = new Demo(); //创建线程
d.start(); //开启线程并执行该线程中的run方法
//d.run(); //为什么不是调用该方法,因为调用该方法,虽然线程是创建了
//但是并没有开启线程,所以还是单线程,按顺序执行代码
for(int i = 0; i < 40; i++)
System.out.println("主线程的:-----" + i);
}
}
class Demo extends Thread
{
public void run()
{
for(int i = 0; i < 40; i++)
System.out.println("子类的:----" + i);
}
}
为什么要复写run方法呢?
因为thread类定义了一个用于存储线程要运行的代码的方法,该方法就是run。
也就是说Thread类中的run方法,用于存储线程要运行的代码,主线程中的代码存储于main方法中
线程中的方法:
Thread.currentThread()---->获取当前线程的名称
getName()---->获取线程的名称
setName()或者super(name)---->设置线程的名称
实现Runnable接口。
步骤:
1、定义类实现Runnable接口
2、复写接口中的run方法
3、创建实现Runnable接口对象
4、创建Thread类对象,将实现Runnable接口的对象作为实际参数传递给Thread类的构造函数
5、调用Thread类的start方法,开启接口
示例:
class MyRunnable implements Runnable
{
public void run()
{
for(int i = 0; i < 10; i++)
{
System.out.println(Thread.currentThread() + "---->" + i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo {
public static void main(String[] args) throws InterruptedException
{
MyRunnable mb = new MyRunnable();
Thread t = new Thread(mb);
t.start();
for(int i = 0; i < 22; i++)
{
System.out.println(Thread.currentThread() + "---->" + i);
Thread.sleep(200);
}
}
}
继承Thread类和实现Runnable接口有什么区别?
继承Thread类:线程代码存放在Thread子类的run方法中
实现Runnable:线程代码存放在Runnable实现类的run方法中
实现Runnable避免了单继承的局限性,在定义线程时建议使用实现方式来完成
线程的生命周期:
线程的生命周期包含状态:新状态、就绪状态、阻塞状态、死亡状态
新状态:
Thread t = new Thread();
刚刚new出来的线程对象,还没有调用start()方法。
就绪状态(可运行状态):
t.start()之后,线程进入就绪状态!在某一个时间点上,只有一个线程是运行着的,其它的线程都没有运行,
所以我们说start()之后不是“运行状态”,而是“就绪状态”。
线程什么时候从就绪到运行,这由CPU来决定,?CPU会给运行的线程一个时间片,这个时间用完,
如果线程还没有主动阻塞,那么CPU会强行停止运行,给其他线程运行机会。
运行状态:
由CPU决定,CPU会在就绪状态的线程中随机选择一个,给其运行的时间片。
运行的线程,应该主动进入阻塞状态,这样给其他线程运行的时间。
阻塞状态:
睡眠:Thread.sleep(1000),当线程执行了sleep()方法,那么这个线程就进入了休眠状态。
休眠的线程必须要等到指定的毫秒过完,才能返回到就绪状态。
等待:当前线程执行了wait()方法,进入了对象的等待列表中。
只能期待其他线程调用notify()或notifyAll()来唤醒这个等待的线程。
IO阻塞:当线程正在完成IO操作,那么这个线程也就阻塞了。直到IO操作完成了!
锁定: 当线程要使用的对象被其他线程使用时,那么这个线程进入了锁定状态。
直到线程得到了要使用的对象后,那么就回到就绪状态。
死亡状态:
run()方法结束,正常死亡!
run()中抛出了异常,因为而死亡!
线程中的常用方法:
static Thread currentThread():获取当前线程的对象,相当于this
String getName():获取当前线程的名称
设置线程名称:void setName(String name)或通过构造器
当多个线程共享同一个数据时,导致共享数据出现错误。
解决方案:一个线程执行中,其它线程不能参与执行
Java对于多线程的安全问题,提供了专业的解决方案:
同步代码块:
synchronized(监视器对象)
{
//需要被同步的代码
}
虽然Java允许使用任何对象作为监视器对象,但想一下同步的目的:阻止多个线程对同一个共享资源并发访问,
因此通常使用可能被并发访问的资源作为同步监视器对象。
案例体现:模拟银行取钱操作
package com.itheima.bank;
public class Account {
//账户余额
private double balance;
public Account(double balance) {
this.balance = balance;
}
/*取钱的方法*/
public double drawBalance(double drawBalance)
{
balance = balance - drawBalance;
return balance;
}
/*查询余额*/
public double getBalance()
{
return balance;
}
}
package com.itheima.bank;
public class DrawThread extends Thread
{
private Account a;
//要取的金额
private double drawBalance;
public DrawThread(String name, Account a, double drawBalance)
{
super(name);
this.a = a;
this.drawBalance = drawBalance;
}
public void run()
{
while(true){
/*同步代码块,将共享访问的Account对象作为锁*/
synchronized(a){
if(a.getBalance() < drawBalance){
System.out.println(Thread.currentThread() + " : 余额不足,当前余额: " + a.getBalance());
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
a.drawBalance(drawBalance);
System.out.println(Thread.currentThread() + "取钱成功,取走金额:" + drawBalance + " ,当前余额:" + a.getBalance());
}
}
}
}
package com.itheima.bank;
public class DrawTest {
public static void main(String[] args) {
Account a = new Account(1000);
new DrawThread("A账户:", a, 500).start();
new DrawThread("B账户:", a, 300).start();
}
}
同步方法:
同步方法是使用synchronized关键字来修饰某个方法,则该方法就被称为同步方法。
例如:public synchronized void show(){}
对于同步方法而言,无需显示指定同步监视器对象,同步方法的同步监视器对象是this。
同步方法的特点:一个对象的一个同步方法被调用,它的其它同步方法都用不了
案例体现:
package com.itheima;
public class SynDemo1 {
public static void main(String[] args) {
A a = new A();
Thread t1 = new Thread1(a);
Thread t2 = new Thread2(a);
t1.start();
t2.start();
}
}
class Thread1 extends Thread {
private A a;
public Thread1(A a) {
this.a = a;
}
public void run() {
a.fun1();
}
}
class Thread2 extends Thread {
private A a;
public Thread2(A a) {
this.a = a;
}
public void run() {
a.fun2();
}
}
class A {
public synchronized void fun1() {
System.out.println("fun1进来了");
System.out.println("fun1()");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun1出去了");
}
public synchronized void fun2() {
System.out.println("fun2进来了");
System.out.println("fun2()");
System.out.println("fun2出去了");
}
}
打印结果:fun1进来了
fun1()
fun1出去了
fun2进来了
fun2()
fun2出去了
对象如同锁,持有锁的线程才能在同步中执行,没有锁的线程,即使获取cpu的执行权,也进不去
同步的前提:必须要有两个或以上的线程访问共享数据;必须是多个线程使用同一个锁(比如火车上的两个卫生间,就不是同一个锁)
好处:解决了多线程安全问题
弊端:多个线程都需要判断锁,较为消耗资源
使用同步块还是使用同步方法呢?一般使用同步块,灵活
静态同步方法:
静态同步方法使用的锁是该方法所在类的class对象。即类名.class。
单例设计模式:
饿汉式:
public class Single{
//私有化构造器
private Single(){}
//创建对象
private static final Single s = new Single();
//提供方法,供外部使用
public static Single getSingle()
{
return s;
}
}
懒汉式:
public class Single(){
private Single(){}
private static Single s = null;
public static Single getSingle()
{
if(s == null)
{
synchronized(Single.class)
{
if(s == null)
s = new Single();
}
}
}
}
懒汉式和饿汉式的区别:懒汉式的特点在于延迟加载,如果多个线程访问时,会出现安全问题。
可以加同步来解决,用同步代码块和同步方法都行,但是同步方法稍微有些低效,
所以一般用同步代码块,通过双重if判断来解决效率问题,同步锁是该类所属的字节码文件对象
死锁:
当两个线程相互等待对方释放同步监视器时就会发生死锁,Java虚拟机没有检测,也没有采取措施来处理死锁情况,
所以多线程应该避免死锁的出现。一旦程序出现死锁,整个程序既不会发生异常,也不会有任何提示,
只是所有线程都处于阻塞状态。一般这种情况发生在:同步中嵌套同步,但锁不同
比如说:a线程锁定一个资源,同时想获取b线程的资源
b线程锁定一个资源,同时想获取a线程的资源。
举例:
package com.itheima.lock;
public class LockDemo {
public static void main(String[] args) {
Thread t1 = new Thread1();
Thread t2 = new Thread2();
t1.start();
t2.start();
}
}
class Thread1 extends Thread {
public void run() {
synchronized (Object.class) {
System.out.println(Thread.currentThread().getName() + "...外层锁");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (String.class) {
System.out.println("haha,卡住了");
}
}
}
}
class Thread2 extends Thread {
public void run() {
synchronized (String.class) {
System.out.println(Thread.currentThread().getName() + "...外层锁");
synchronized (Object.class) {
System.out.println("haha,我也卡住了");
}
}
}
}
线程通信:
当多个线程访问同一资源,每个线程的实现功能不一样,而每个线程之间需要协调发展(两种线程交替执行)。
比如说:
老师:我们开始上课;
学生A:老师等一下,我要去厕所;
老师:OK,你快点,那我wait()了,等你回来notify我一下。
线程通信的前提:同步环境!
为了实现这种功能,需要借助Object类提供的wait(),notify(),notifyAll()这3个方法,这些方法不是Thread类的方法。
这三个方法都使用在同步中,因为要对持有监视器的锁进行操作,所以要定义在同步中,因为只有同步才具有锁的概念
为什么操作线程的方法要定义在Object类中?
因为这些方法在操作同步中线程时都必须要标识它们所操作线程持有的锁,
只有同一个锁上的被等待线程可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒
也就是说:等待和唤醒必须是同一把锁
而锁可以是任意对象,所以把可以被任意对象掉用的wait(),notify(),notityAll()定义在Object类中
wait():声明了InterruptedException异常!在使用wait()时,还要使用try/catch。
举例说明:
package com.itheima.bank1;
public class Bank {
private double money;
boolean flag = false;
public Bank(double money)
{
this.money = money;
}
/*取钱的方法*/
public double drawMoney(double drawMoney)
{
money = money - drawMoney;
return money;
}
/*存钱的方法*/
public double saveMoney(double saveMoney)
{
money = money + saveMoney;
return money;
}
/*查询余额*/
public double getMoney()
{
return money;
}
}
package com.itheima.bank1;
/*
* 取钱操作的线程
* */
public class DrawThread extends Thread{
private Bank b;
private double drawMoney;
public DrawThread(Bank b, double drawMoney, String name)
{
super(name);
this.b = b;
this.drawMoney = drawMoney;
}
public void run()
{
while(true)
{
synchronized(b)
{
if(!b.flag)
{
try {
b.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(b.getMoney() < drawMoney){
System.out.println("余额不足,当前余额是:" + b.getMoney());
break;
}
b.drawMoney(drawMoney);
System.out.println(Thread.currentThread().getName() + " 取款成功,取款金额是:" + drawMoney + ",账户余额:" + b.getMoney());
b.flag = false;
b.notify();
}
}
}
}
package com.itheima.bank1;
/*
* 存钱操作的线程
* */
public class SaveThread extends Thread {
private Bank b;
private double saveMoney;
public SaveThread(Bank b, double saveMoney, String name)
{
super(name);
this.b = b;
this.saveMoney = saveMoney;
}
public void run()
{
while(true)
{
synchronized(b)
{
if(b.flag)
{
try {
b.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
b.saveMoney(saveMoney);
System.out.println(Thread.currentThread().getName() + "存款成功,存款金额:" + saveMoney + ",当前余额:" + b.getMoney());
b.flag = true;
b.notify();
}
}
}
}
package com.itheima.bank1;
public class BankTest {
public static void main(String[] args) {
Bank b = new Bank(5000);
new SaveThread(b, 200, "存钱线程...").start();
new DrawThread(b, 500, "取钱线程...").start();
}
}
线程通信小结:
1、使用wait()、notify()、notifyAll()方法可以完成线程间的通讯,可叫它们通讯方法;
2、只能在同步环境下调用通讯方法;
3、只能使用监视器对象调用通讯方法;
4、每个监视器对象都有一个线程监狱:执行监视器对象.wait()的线程会被关押到监视器对象的线程监狱中;
5、若想释放出锁对象的线程监狱中的线程,那么需要调用监视器对象.notify()方法,
该方法只能保证在监视器对象的线程监狱中释放出一个线程,但不能保证释放的是哪一个;
6、还可以使用监视器.notifyAll()方法释放出监视器对象的监狱中关押的所有线程。
7、被wait()了的线程不能自己恢复到就绪状态,只能等待其他线程调用同一监视器对象上的notify()或notifyAll()方法来唤醒。
8、被wait()了的线程会释放监视器对象的对象锁,这样其他线程就可以进入他占用的同步环境。
9、被唤醒的线程恢复到了就绪状态,当再次获取监视器对象的锁后会在wait()处向下运行。
后台线程(守护线程):
停止线程:
如何停止线程呢?
只有一种,run()结束
怎么让run()结束呢?
开启多线程运行,通常是通过循环结构,只要控制住循环,就可以让run()结束,也就是线程结束
特殊情况:当线程处于冻结状态,线程就不会结束
当没有指定的方式让线程从冻结恢复到运行状态,这是需要对冻结进行清除,强制让它恢复到运行状态中来,
这样就可以操做标记让线程结束
interrupt():将处于冻结状态的线程,强制的恢复到运行状态
守护线程(后台线程):
void setDaemon(boolean on):将该线程标记为守护线程或用户线程。
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
后台线程的子线程,也是默认为后台线程!
什么叫子线程?A线程的任务中启动了B线程,那么B是A的子线程。
举例说明:
package com.itheima;
/*
* 演示后台线程!
*/
public class ServerThread {
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread();
t.setDaemon(true);//在start()之前把t设置为后台线程
t.start();
for(int i = 0; i < 3; i++) {
System.out.println(i);
Thread.sleep(300);
}
System.out.println("bye-bye!");
}
}
class MyThread extends Thread {
public void run() {
for(int i = 0; i < 10; i++) {
Thread t = new ZiThread();
t.start();
}
}
}
class ZiThread extends Thread {
public void run() {
while(true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
0
1
2
bye-bye!
合并线程(join):
void join():等待该线程终止。
join()方法的作用:使当前线程等待该线程结束再向下运行。
当A线程执行到了B线程的.join()方法时,A线程就会等待B线程执行完,才继续执行
join()可以用来临时加入线程执行
演示:
package com.itheima;
/*
* 演示join()
*/
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new JoinThread();
t.start();
System.out.println("我要等待t结束");
t.join();//当前线程是主线程,主线程要等待t结束
System.out.println("\n终于等到t结束了!");
}
}
class JoinThread extends Thread {
public void run() {
for(int i = 0; i < 10; i++) {
System.out.print(i + " ,");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
我要等待t结束
0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,
终于等到t结束了!
线程让步(yield):
在以前使用sleep()的地方就可以尝试使用yield
yield()方法的作用:说线程已经把最重要的工作做完了,告诉CPU可以切换给其他线程了。
让步不同于阻塞:让步的线程没有进入阻塞状态,只是从运行状态到就绪状态!