java多线程-线程基础

1、进程
程序运行起来就是进程(程序就是指放在磁盘上的代码、进程是指程序运行起来,动态的概念)

2、进程间数据是独立的,线程间是有可能共享的

3、main方法产生主线程

4、进程间也是可以通信的(代价比较昂贵),线程通信当然就比较低了

实现方式:
1、实现方式分两种
(1)继承Thread
重写run方法,但不要直接调用run()方法,而是调用start()方法才能启动线程
(2)实现Runnable接口
实现run方法

例子:
public class MyThreadTest {
public static void main(String[] args) {
Thread thread = new TestThread();
thread.start();

Thread runnable = new Thread(new TestRun());
runnable.start();
}
}

class TestRun implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("runnable is " + i);
}
}
}

class TestThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("thread is " + i);
}
}
}

为什么会出现这样的结果,这之间是什么关系(有必要研究Thread类):
(1)Thread类实现了Runnable接口,因此Thread也会实现run()方法
(2)
Thread结构
Thread{
start(){
start0();//start0是一个native方法,但实质就是调用run()方法
}

public void run() {
if (target != null) {
target.run(); //target实质就是runnable对象
}
}
}

Runnable结构
public interface Runnable {
public abstract void run();
}
若我们直接调用new Thread();则必须重写Thread,所以我们调用start之后,最后会调用我们自己重写的run()方法;
若我们调用new Thread(Runnable target);那么我们必须实现Runnable 接口的run()方法,我们接着调用Thread的start()方法,然后会调用Thread类的run()方法,然后在run()方法中判断,此时target不为null,则会调用target的run方法(即我们自己重写的run方法)。
这就是实现线程的两种方法的机制。
(3)至于用哪种方式起一个线程,视情况而定,但一个类若已经继承了另一个类,此时只能使用实现runnable接口的方式来实现一个线程类了。

有一个例子可以对上述过程进行一个验证:
public class MyThreadTest {
public static void main(String[] args) {
Runnable run = new TestAsy();
Thread threadA = new TestA();
Thread threadB = new TestA();

Thread threadC = new Thread(run);
Thread threadD = new Thread(run);
threadA.start();
threadB.start();

threadC.start();
threadD.start();
}
}

class TestAsy implements Runnable{
int i;
public void run() {
while (true) {
System.out.println("i am " + i++);
try {
Thread.sleep((long)Math.random() * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i == 10) {
break;
}
}
}
}

class TestA extends Thread {
private int i;
public void run() {
while (true) {
System.out.println("i am " + i++);
try {
Thread.sleep((long)Math.random() * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i == 10) {
break;
}
}
}
}

6、线程对象,即:是一个对象,这个对象继承Thread或实现了Runnable接口。
线程类同样理解。

7、若直接调用线程类里的run方法,则此时会将线程类当做普通的类对象,不再具有线程的性质

8、总的来说,无论通过Thread还是Runnable,最后都是通过new Thread方式来获得线程的,只不过是Thread通过无参构造函数,而Runnable是通过Thread(Runnbale target)方式。

线程生命周期:
1、声明周期
(1)创建状态
(2)可运行时状态
(3)不可运行状态
(4)消亡状态

(1)首先需要创建一个线程对象,即new Thread;
(2)启动线程的start()方法,进入可运行状态(此时线程还没执行,没执行的原因有可能是因为没有抢占到cpu)
(3)若线程抢占到cpu,则进入到running状态
(4)若线程正常执行完,则进入dead状态;
(5)若正在执行的线程(running状态)若此时碰到有一些事情要处理,如IO,则会退让cpu,进入Blocked状态来处理IO,当IO处理完成之后,再次进入可运行状态(runnable),等待cpu的分配。
(6)也有可能直接从运行状态到可运行状态,cpu将其权利剥夺(如有更高优先级的线程出现等等)


2、不可运行状态:
(1)调用sleep(long millions)
(2)调用wait()
(3)调用IO

3、返回可运行状态可能性
(1)sleep睡眠时间完成
(2)wait状态通过notify或者notifyAll给唤醒
(3)如果线程是因为IO阻塞进入不可运行状态,此时IO完成

4、线程优先级
(1)1~10之间
(2)子线程继承父线程的优先级
(3)默认:5
说明:一个线程是否执行,并不完全依赖其优先级大小。(程序不应该由约定优先级大小来控制线程优先执行顺序)。
可以从这两方面理解:
首先,比如一个线程优先级比较小,但它等待时间可能比较长,也有可能会执行。
其次,可以认为线程优先级会随着时间而动态变化(张龙培训课程中所说)。
总结:具体执行顺序视具体情况、操作系统等而定。

5、有可能终止线程执行的几种可能(即从running到blocked或runnable状态)
(1)线程调用yield方法让出cpu使用权(进入runnable)
(2)调用sleep方法(进入blocked)
(3)IO阻塞(进入blocked)
(4)另一高优先级的线程出现(进入runnable)
(5)在支持时间分片的操作系统中,时间片用完了(进入runnable)

线程同步
1、线程同步的作用就是能够合理的利用资源,例如有多个线程同时访问某一个资源,此时若有必要对这个资源进行合理的控制,此时则要引入线程的同步机制。

2、这里有一个例子,导致最后出现问题:
public class FecthMoney {
public static void main(String[] args) {
Account account = new Account(1000);
GetMoneyThread getMoneyThread = new GetMoneyThread(account, 800);
GetMoneyThread getMoneyThread1 = new GetMoneyThread(account, 800);

getMoneyThread.start();
getMoneyThread1.start();

try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("账户剩余钱:" + account.money);
}
}

class GetMoneyThread extends Thread {
private Account account;
private int money;
public GetMoneyThread(Account account, int money) {
this.account = account;
this.money = money;
}
@Override
public void run() {
account.fetchMoney(money);
}
}

class Account{
public int money;
public Account(int money) {
this.money = money;
}

public void fetchMoney(int money) {
if (this.money < money) { //进行判断,若钱不够,则不能取出钱
System.out.println("钱不够");
return;
}
//这里不考虑异常情况,如money为负
//并假设取钱是一个很长的操作,如需要查数据库等
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.money = this.money - money;
}
}
运行结果:账户剩余钱:-600
分析:实质在取钱之前已经判断过当前用户取钱数量是否小于账户余额,但最后还是出现问题了。这就需要对资源进行加锁处理。

以下代码是解决方案(synchronized)
synchronized方法
public class FecthMoney {
public static void main(String[] args) {
Account account = new Account(1000);
GetMoneyThread getMoneyThread = new GetMoneyThread(account, 800);
GetMoneyThread getMoneyThread1 = new GetMoneyThread(account, 800);

getMoneyThread.start();
getMoneyThread1.start();

try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("账户剩余钱:" + account.money);
}
}

class GetMoneyThread extends Thread {
private Account account;
private int money;
public GetMoneyThread(Account account, int money) {
this.account = account;
this.money = money;
}
@Override
public void run() {
account.fetchMoney(money);
}
}

class Account{
public int money;
public Account(int money) {
this.money = money;
}

public synchronized void fetchMoney(int money) {
if (this.money < money) { //进行判断,若钱不够,则不能取出钱
System.out.println("钱不够");
return;
}
//这里不考虑异常情况,如money为负
//并假设取钱是一个很长的操作,如需要查数据库等
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.money = this.money - money;
}
}
这里需要说明的是:
(1)上锁是给对象上锁,更清楚一点是给堆中的对象上锁(而不是引用)
(2)上锁之后,对象的所用的synchronized方法都会被锁住(非synchronized方法不会被锁)
(3)若,synchronized 所修饰的方法是static 静态方法,如:public synchronized static void fun(),则当前锁的是当前对象所对应的class<?>对象(同一个类的所有对象只有一个class<?>对象)。

synchronized(object)
代码如下
public class FecthMoney {
public static void main(String[] args) {
Account account = new Account(1000);
GetMoneyThread getMoneyThread = new GetMoneyThread(account, 800);
GetMoneyThread getMoneyThread1 = new GetMoneyThread(account, 800);

getMoneyThread.start();
getMoneyThread1.start();

try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("账户剩余钱:" + account.money);
}
}

class GetMoneyThread extends Thread {
private Account account;
private int money;
public GetMoneyThread(Account account, int money) {
this.account = account;
this.money = money;
}
@Override
public void run() {
account.fetchMoney(money);
}
}

class Account{
private Object object = new Object();

public int money;
public Account(int money) {
this.money = money;
}

public void fetchMoney(int money) {
synchronized(object) {
if (this.money < money) { //进行判断,若钱不够,则不能取出钱
System.out.println("钱不够");
return;
}
//这里不考虑异常情况,如money为负
//并假设取钱是一个很长的操作,如需要查数据库等
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.money = this.money - money;
}
}
}
说明:
(1)这里的object没有其他作用,就是一个作为锁的标志作用。
(2)有时,我们也将object换成this,那么锁的就是当前对象了。
(3)用 synchronized(this)之后,就和synchronized方法一模一样了,通过混合加锁可以验证,如下例子:
public void fetchMoney(int money) {
synchronized(this) {
if (this.money < money) { //进行判断,若钱不够,则不能取出钱
System.out.println("钱不够");
return;
}
//这里不考虑异常情况,如money为负
//并假设取钱是一个很长的操作,如需要查数据库等
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.money = this.money - money;
}
}

public synchronized void fetchMoney1(int money) {
if (this.money < money) { //进行判断,若钱不够,则不能取出钱
System.out.println("钱不够");
return;
}
//这里不考虑异常情况,如money为负
//并假设取钱是一个很长的操作,如需要查数据库等
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.money = this.money - money;
}

比较:
(1)synchronized(object)相对来说,更细腻,仅是同步块内的代码,块之外的代码是不会被同步的。
(2)能用synchronized(object)尽量用synchronized(object)

增加了同步之后的状态图:

相比于没有增加synchronized状态图,主要是增加了上图中框框的部分。
说明:增加的一部分主要是,一个线程获得cpu资源后,进入running状态,若此时发现需要执行同步代码,且此时同步代码被锁住了,则会进入锁池(lock pool),只有获得锁池,则再次进入runnable状态。


wati()和notify()方法:
1、基本方法:
(1)wait(); 释放当前对象锁,并暂停执行
(2)notify(); 唤醒wait()等待队列中的第一个线程,并将它移至锁申请队列中,即lock pool中,去竞争对象锁
(3)notifyAll(); 唤醒wait()等待队列中的所有线程,并将所有移至锁申请队列中,即lock pool中,去竞争对象锁
(4)yield();放弃当前cpu使用权,进入runnable状态,让优先级相同或更高的线程去竞争cpu;
(5)sleep();线程停止,但并不会放弃cpu使用权。

2、引入目的:
方便进程间的通信

3、基本方法的总结
(1)wait() 和notify一般成对出现,且在一个类中成对出现。
(2)ait() 和notify 在object类中已经定义
(3)ait() 和notify都是final类型,不能被重写
(4)调用时,线程必须获得了当前对象的锁(必须放在synchronized方法或块中)
(5)当线程执行了wait()方法时,会释放对象的锁

4、sleep()和wait()比较
(1)sleep()和wait()同样会使线程暂停;
(2)但sleep()继承Thread而wait()继承Object类;
(3)同时,sleep()不会释放线程锁,而wait()是会释放对象锁的。

5、 添加wait()和notify()方法之后的状态图(最完整的图):

说明:
在运行状态,当遇到wait()函数时,会进入wait()池,同时,释放对象锁;若有notify()或notifyAll()时,则会将这个线程唤醒,并将这个线程移至对象锁池中,并去竞争对象锁。

线程总结:
1、线程概念,线程与进程的区别等;
2、如何定义实现线程(两种方式);
3、synchronized关键字(synchronized方法和synchronized块),这一部分要明白锁到底锁的是什么(是对象呢还是class<?>对象还是其他);
4、wait()和notify()两个方法,以及这两个方法的运用,以及wait()和sleep()之间的区别。
5、线程三种形式的转换图(最基本的、添加synchronized的、添加wait()方法的)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值