文章目录
线程
一.线程与进程
1.操作系统可以执行多个任务,每个任务通常就是一个程序,运行着的程序就是一个进程,进程中可能包含多个顺序执行流,每个顺序执行流就是一个线程
2.进程是系统进行资源分配和调度的一个独立单位
3.进程主要三个特性:
独立性:进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间
动态性:程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,进程拥有自己的声明周期和不同的状态
并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响
注意:并发:同一时刻只有一条指令执行,多个进程指令快速轮换执行,并行:同一时刻,有多条指令在多个处理器上同时执行
4.一个线程必须有一个父进程。线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不再拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源,编程时要确保线程不会妨碍同一进程的其他线程。
5.线程是独立运行的,它并不知道进程中其他线程的存在,线程的执行是抢占式的,可能被挂起,也可能被执行
6.一个线程可以创建和撤销另一个线程,同一个线程中的多个线程之间可以并发执行
二.多线程优势
1.进程间不能共享内存,但线程之间共享内存非常容易
2.创建进程,系统要重新分配系统资源,代价比线程大
3.JAVA内置多线程功能支持
三.线程创建方式
1.继承Thread
使用继承Thread类的方法创建线程类,多条线程之间无法共享线程类的实例变量
public class DemoByThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("i = " + i + "-->线程名:"+getName());
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println("主线程名:"+ Thread.currentThread().getName());
if(i == 5){
new DemoByThread().start();
new DemoByThread().start();
}
}
}
}
2.实现Runnable
public class DemoByRun implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("i = " + i + "-->线程名:"+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println("主线程名:"+ Thread.currentThread().getName());
if(i == 5){
new DemoByThread().start();
new DemoByThread().start();
}
}
}
}
两种方式区别
四.线程生命周期
图源
1.新建:使用new()关键字创建线程,虚拟机为其分配了内存并初始化变量值
2.就绪:调用start()方法,虚拟机为其创建方法调用栈和程序计数器,表示可以运行了
阻塞到就绪:
3.运行:执行run()方法,线程不会一直都处于运行状态,执行一段时间后,系统会收回线程所占用的资源,根据抢占式策略,让其他线程获得执行机会,收回后,系统会考虑优先级选线程
4.阻塞:执行sleep()或yield()方法主动放弃线程占用的资源,则进入此状态
运行到阻塞:
5.死亡:run()方法执行完,用isAlive()查看是否已死亡,死亡和创建时isAlive都是false,其他三种都是true
注意:
不要对已经处于启动状态的线程再次调用start方法,否则将引起IllegalThreadStateException
也不要对处于死亡状态的线程调用start()方法
当主线程结束时候,其他线程不受任何影响,并不会随之结束。一旦子线程启动起来后,它就拥有和 主线程相同的地位,它不会受主线程的影响
五.线程控制
join
当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join方法加入的join线程完成为止
换句话说,就是当前线程加进来一个子线程,先把这个子线程运行完后,才返回来执行当前线程的逻辑。
public class ThreadJoin extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程 "+getName()+" 计数"+i);
}
}
public static void main(String[] args) throws Exception{
for (int i = 0; i < 30; i++) {
System.out.println("主线程 " + Thread.currentThread().getName() + "计数"+ i);
if(i == 15){
ThreadJoin tj = new ThreadJoin();
tj.start();//调用join之前,必须要调用start,不然线程是不会执行的
tj.join();
}
}
}
}
后台线程(守护线程)
后台运行,为其他线程提供服务。JVM垃圾回收线程就是后台线程
特征:如果所有的前台线程都死亡,后台线程会自动死亡,不过死亡不会立即死亡
注意:前台线程死亡后,JVM会通知后台线程死亡,但从它接收指令到作出响应,需要一定时间。
setDaemon必须在start方法之前调用
public class ThreadDaemon extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"-->"+i);
}
}
public static void main(String[] args) {
ThreadDaemon t = new ThreadDaemon();
t.setDaemon(true);
t.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
//当程序执行到这里,前台线程(main)执行结束,后台线程(t)也会随之结束
}
}
sleep
当线程使用sleep方法阻塞后,在其sleep时间段内,该线程不会获得执行的机会,即使系统中没有其他可运行的线程,处于
sleep中的线程也不会运行
注意:sleep只阻止当前线程的运行,而其他线程是不受影响的,会继续执行下去
public class ThreadSleep extends Thread{
@Override
public void run() {
for (int i = 0; i < 25; i++) {
try {
sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" 子线程sleep300s-->"+i+"-->时间:"+showDate());
}
}
public static void main(String[] args) {
new ThreadSleep().start();
for (int i = 0; i < 50; i++) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" 主线程sleep100s-->"+i+"-->时间:"+showDate());
}
}
public static String showDate(){
Date dNow = new Date( );
SimpleDateFormat ft =
new SimpleDateFormat (" YYYY/MM/dd HH:mm:ss.SSS");
return ft.format(dNow);
}
}
yield
让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将线程转入就绪状态。
让系统的线程调度器重新调度一次,完全可能的情况是:当某个线程调用yeild方法暂停之后,线程
调度器又将其调度出来重新执行。
实际上,某个线程调用了yield方法暂停后,只有优先级与当前线程相同或更高才有机会获得执行机会
注意:实际代码验证中,线程优先级不一定高的优先级一定先执行,只表示执行机会比较多而已
public class ThreadYield extends Thread{
public ThreadYield() {
}
public ThreadYield(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(getName()+"**"+i);
Thread.yield();
}
}
public static void main(String[] args) {
ThreadYield tl = new ThreadYield("低级");
tl.setPriority(Thread.MIN_PRIORITY);
tl.start();
// ThreadYield tm = new ThreadYield("中级");
//// tm.setPriority(Thread.NORM_PRIORITY);
//// tm.start();
ThreadYield tg = new ThreadYield("高级");
tg.setPriority(Thread.MAX_PRIORITY);
tg.start();
}
}
优先级
不同系统提供的线程优先级不一样,我们应该尽量避免直接为线程执行优先级,而应使用
JAVA那三个静态常量来设置优先级,这样才可以保证程序最好的可移植性
六.线程同步
安全问题
账户类
public class Account {
private String accountNo;//账户编号
private double balance;//金额
public Account() {
}
public Account(String accountNo, double balance) {
this.accountNo = accountNo;
this.balance = balance;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Account account = (Account) o;
return Objects.equals(accountNo, account.accountNo);
}
@Override
public int hashCode() {
return Objects.hash(accountNo);
}
}
取款类
public class DrawThreadNoSafe extends Thread{
private Account account;
private double drawAmount;
public DrawThreadNoSafe(String name, Account account, double drawAmount) {
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
@Override
public void run() {
if(account.getBalance() >= drawAmount){
System.out.println(this.getName()+"取钱成功:取出"+drawAmount+"元");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.setBalance(account.getBalance()-drawAmount);//这里会有同步问题,account里的金额可能被其他线程抢先一步修改
System.out.println("当前余额为: " + account.getBalance());
}else{
System.out.println(this.getName()+"余额不足,取款失败");
}
}
}
测试类
public class TestThread {
public static void main(String[] args) {
Account account = new Account("A账户", 1000);
new DrawThreadNoSafe("甲取款",account,600).start();
new DrawThreadNoSafe("乙取款",account,600).start();
}
}
结果
synchronized
同步块
注意:任何时刻只有一条线程可以或得同步监视器的锁定,当同步代码块执行结束后,该线程自然释放了对该同步监视器的锁定
通常推荐使用可能被并发访问的共享资源充当同步监视器
取款类
public void run() {
synchronized (account){//account是两个线程都要操作的资源,可以把它作为同步锁来控制线程访问
if(account.getBalance() >= drawAmount){
System.out.println(this.getName()+"取钱成功:取出"+drawAmount+"元");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.setBalance(account.getBalance()-drawAmount);
System.out.println("当前余额为: " + account.getBalance());
}else{
System.out.println(this.getName()+"余额不足,取款失败");
}
}
}
同步方法
取款类
public void run() {
account.draw(drawAmount);
}
账户类
public synchronized void draw(double drawAmount){
if(this.getBalance() >= drawAmount){
System.out.println(Thread.currentThread().getName()+"取钱成功:取出"+drawAmount+"元");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(this.getBalance()-drawAmount);
System.out.println("当前余额为: " + this.getBalance());
}else{
System.out.println(Thread.currentThread().getName()+"余额不足,取款失败");
}
}
同步监视器释放
volatile
可见性
关键字volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值
线程打印类
public class PrintString implements Runnable{
private static volatile boolean continuePrint = true;
public boolean isContinuePrint() {
return continuePrint;
}
public void setContinuePrint(boolean continuePrint) {
this.continuePrint = continuePrint;
}
public void printStringMethod(){
try {
while (continuePrint == true){//这里面不要有代码,不然运行模拟不出来
}
System.out.println("run printStringMethod " + Thread.currentThread().getName());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
printStringMethod();
}
}
测试类
private static void test() {
PrintString ps = new PrintString();//启动一个子线程打印
new Thread(ps).start();
System.out.println("主线程要关闭子线程了"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ps.setContinuePrint(false);//关闭子线程
}
非原子性
当多线程对同一个资源进行操作时,volatile并不能保证该资源操作是原子性操作,比如都对count进行相加操作
线程计算类
public class ThreadI extends Thread{
volatile public static int count;//公共资源
private static void addCount(){//每个线程执行100次相加
for (int i = 0; i < 100; i++) {
count++;
}
System.out.println("count "+count);
}
@Override
public void run() {
addCount();
}
}
测试类
private static void test() {
ThreadI[] ti = new ThreadI[100];
for (int i = 0; i < 100; i++) {
ti[i] = new ThreadI();
}
for (int i = 0; i < 100; i++) {
ti[i].start();
}
}
由结果可知,100个线程计算100次,总的结果却不是10000
这是因为按正常逻辑来说,第一个线程加到100,第二个线程从100基础上再加到200,但是,当执行count++的时候,有些线程才加到比如288的时候,马上另外一个线程就会读取count进行相加,这个时候的值为288,那么就从288开始相加,而不是从300相加,这就不能保证每个线程执行完后再执行下一个线程的原子性操作
同步锁Lock
账户类
public class Account {
private String accountNo;//账户编号
private double balance;//金额
private final ReentrantLock lock = new ReentrantLock();
...
public void draw(double drawAmount){
lock.lock();//增加同步锁
try {
if(this.getBalance() >= drawAmount){
System.out.println(Thread.currentThread().getName()+"取钱成功:取出"+drawAmount+"元");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(this.getBalance()-drawAmount);
System.out.println("当前余额为: " + this.getBalance());
}else{
System.out.println(Thread.currentThread().getName()+"余额不足,取款失败");
}
}finally {
lock.unlock();
}
}
注意:不管用Lock还是synchronized,作用的对象都是需要操作公共资源的那个对象,比如上面的Account,是一个公共资源,多个线程都会访问这个Account,那么就需要对这个对象进行加锁处理来达到同步效果!若在非公共资源上加锁,那么锁只作用于当前类,每个线程都会访问新的当前类,从而不能达到同步效果,比如把锁加在DrawThreadNoSafe 类上
死锁
资源A类
public class A {
public synchronized void before(B b){
System.out.println("当前线程"+Thread.currentThread().getName()+"访问了A的Before");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程"+Thread.currentThread().getName()+"即将访问B的last方法");
b.last();//b.before的时候b对象已经加锁,这里阻塞
}
public synchronized void last(){
System.out.println(Thread.currentThread().getName()+"正在访问A的last方法");
}
}
资源B类
public class B {
public synchronized void before(A a){
System.out.println("当前线程"+Thread.currentThread().getName()+"访问了B的Before");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程"+Thread.currentThread().getName()+"即将访问A的last方法");
a.last();//a.before的时候a对象已经加锁,这里阻塞
}
public synchronized void last(){
System.out.println(Thread.currentThread().getName()+"正在访问B的last方法");
}
}
线程调用类
public class ThreadDead extends Thread{
A a = new A();
B b = new B();
public void init(){
Thread.currentThread().setName("mian主线程调用");
a.before(b);
System.out.println("mian主线程调用结束");
}
@Override
public void run() {
Thread.currentThread().setName("ThreadDead线程调用");
b.before(a);
System.out.println("ThreadDead线程调用结束");
}
public static void main(String[] args) {
ThreadDead td = new ThreadDead();
//启动两个线程进行调用
td.start();//子线程
td.init();//主线程
}
}
注意:由于Thread类的suspend也很容易导致死锁,故Java不再推荐使用该方法来暂停线程的执行
七.线程通讯
存钱取钱问题(object方法控制)
账户类
public class Account {
private String accountNo;
private double balance;
private boolean flag = false;
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public Account(String accountNo, double balance) {
this.accountNo = accountNo;
this.balance = balance;
}
public Account(double balance) {
this.balance = balance;
}
public synchronized void draw(double drawAmount) {//取钱
try {
if (!flag) {
wait();
} else {
System.out.println(Thread.currentThread().getName() + "取钱成功:取出" + drawAmount + "元。");
this.setBalance(this.getBalance() - drawAmount);
System.out.println("当前余额为:" + this.getBalance());
flag = false;//false表示钱已经取了,不能再取了
notifyAll();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public synchronized void deposit(double drawAmount){//存钱
try {
if(flag){
wait();
}else{
System.out.println(Thread.currentThread().getName()+"存钱成功。");
this.setBalance(drawAmount);
System.out.println("当前余额为:"+this.getBalance());
flag = true;//通知钱已经存了
notifyAll();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
存款类
public class DepositThread extends Thread{
private Account account;
private double drawAmount;
public DepositThread(String name,Account account, double drawAmount) {
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
account.deposit(drawAmount);
}
}
}
取款类
public class DrawThread extends Thread{
private Account account;
private double drawAmount;
public DrawThread(String name,Account account, double drawAmount) {
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
account.draw(drawAmount);
}
}
}
模拟用户类
public class TestAccount {
public static void main(String[] args) {
Account account = new Account("A账户",1000);
new DrawThread("甲取款", account, 800).start();
new DepositThread("A存款", account, 800).start();
new DepositThread("B存款", account, 800).start();
new DepositThread("C存款", account, 800).start();
}
}
使用条件变量控制协调(Lock)
前面部分类无需修改,这里只修改账户类即可
账户类
public class Account {
private String accountNo;
private double balance;
private boolean flag = false;
private final Lock lock = new ReentrantLock();//同步锁
private final Condition con = lock.newCondition();//同步锁条件
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public Account(String accountNo, double balance) {
this.accountNo = accountNo;
this.balance = balance;
}
public Account(double balance) {
this.balance = balance;
}
public void draw(double drawAmount) {//取钱
lock.lock();
try {
if (!flag) {
con.await();
} else {
System.out.println(Thread.currentThread().getName() + "取钱成功:取出" + drawAmount + "元。");
this.setBalance(this.getBalance() - drawAmount);
System.out.println("当前余额为:" + this.getBalance());
flag = false;//false表示钱已经取了,不能再取了
con.signalAll();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void deposit(double drawAmount){//存钱
lock.lock();
try {
if(flag){
con.await();
}else{
System.out.println(Thread.currentThread().getName()+"存钱成功。");
this.setBalance(drawAmount);
System.out.println("当前余额为:"+this.getBalance());
flag = true;//通知钱已经存了
con.signalAll();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
八.线程组与线程池
后续更新…