java多线程基础知识,创建,状态、线程安全、死锁,通信

一:五种基本状态

1、新建状态new

线程对象创建后Thread t = new my Thread(),线程就处于新建状态

2、就绪状态Runnable

线程调用start()方法进入就绪,等待CPU调度执行。

3、运行状态Running

当CPU调度线程得以执行,自动调用run()方法,此方法定义该线程的操作和功能。
【就绪状态是进入运行状态的唯一入口】

4、阻塞状态Blocked

运行中的线程由于某种原因,暂时放弃CPU的使用权,停止执行,直到进入就绪状态。

5、等待阻塞

运行中执行wait()方法。
同步阻塞:获取synchronized同步锁失败(锁被其他线程占用)。
其他阻塞:sleep()、join()或发出I/O请求时。sleep()状态超时,join()等待终止或超时,或I/O处理完毕,重新转入就绪状态。
死亡状态Dead:线程执行完了或因异常退出了run()方法,结束生命周期。

二、线程的创建

1、继承Thread类,重写run()方法。run()称之为线程执行体。
2、实现Runnable接口,重写run()方法,将其实现类的实例作为Thread对象的target创建线程。
3、使用Callable和Future接口创建线程。创建Callable实现类实现call()方法,FutureTask类包装Callable实现类,作为Thread对象的target创建线程,这里call()为线程执行体。
【Thread类中的run()方法调用的是Runnable接口中的run()方法,也就是说此方法由Runnable子类完成,所以通过继承Thread类实现多线程必须覆写run()。实现Runnable可以更加方便的实现资源的共享】

三、线程转换

1、就绪转运行:此线程的得到处理资源
2、运行转就绪:此线程主动调用yieId()方法,或运行过程中失去处理资源。
3、运行转死亡:执行体执行完毕,发生了异常。

四、引起阻塞的几种方法

1、join()

让一个线程等待另一个线程完成才继续执行。如A线程线程执行体中调用B线程的join()方法,则A线程被阻塞,直到B线程执行完为止,A才能得以继续执行。

2、sleep()

让当前的正在执行的线程暂停指定的时间,并进入阻塞状态。在其睡眠的时间段内,该线程由于不是处于就绪状态,因此不会得到执行的机会。即使此时系统中没有任何其他可执行的线程,处于sleep()中的线程也不会执行。因此sleep()方法常用来暂停线程执行。【让出CPU,但不会释放锁资源】

3、后台线程

后台线程主要是为其他线程(相对可以称之为前台线程)提供服务,当前台线程都进入死亡状态时,后台线程会自动死亡。调用setDaemon(true)方法可以将指定线程设置为后台线程,调用isDeamon()方法判断是否为后台线程。

4、改变线程的优先级setPriority()

优先级高的线程具有较多的执行机会(而非优先执行,只是有更多机会)。setPriority(int priorityLevel),参数范围在1-10之间,MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5
获取优先级:getPriority()。

5、线程让步yield()

当某个线程调用yeild()方法从运行转为就绪状态后,CPU从就绪状态队列中只会选择与该线程优先级相同或优先级更高的线程去执行。

五、start()方法和run()方法

  • start():start()是Thread类的方法,具有异步执行效果,可以实现多线程,不能重复调用,是真正启动线程的方法。
  • run():run()和普通方法一样,只有同步执行效果,可以重复调用,不能启动线程。

六、线程安全问题

CPU可能随机的在多个处于就绪状态的线程中进行切换:当thread1执行到1处代码时,判断条件为true,此时CPU切换到thread2,执行到1处时,发现依然为真,然后执行完thread2,接着切换到thread1,执行完毕,然后就会出错。(比如典型的银行存钱取钱问题)

1、同步方法

对共享资源进行访问时加上synchronized关键字进行修饰。首先要获得此同步锁,只有线程执行完此同步方法后,才会释放锁对象,其他线程才有可能获取此同步锁。

public class MyRunnable implements Runnable {
    private int i = 0;
    Bank bank = new Bank(100);
    @Override
    public synchronized void run() {
		try {
			for (i = 0; i < 5; i++) {
				bank.deposit(10);
				Thread.sleep(100);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

2、同步代码块

一般选择共享资源对象作为锁对象。

public class MyRunnable implements Runnable {
    private int i = 0;
    Bank bank = new Bank(100);
    @Override
	public void run() {
		synchronized (bank) {
			try {
				for (i = 0; i < 5; i++) {
					bank.deposit(10);
					Thread.sleep(100);
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

	}
}

3、Lock对象同步锁

public class MyRunnable implements Runnable {
    private int i = 0;
    Bank bank = new Bank(100);
    
    private final ReentrantLock lock= new ReentrantLock();//定义锁对象
    @Override
	public void run() {
		lock.lock();
			try {
				for (i = 0; i < 5; i++) {
					bank.deposit(10);
					Thread.sleep(100);
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}finally {
				lock.unlock();//解锁
			}
		}

}

三者运行结果:两个线程独立运行,不会交叉运行产生错误
在这里插入图片描述
设计一个实验,说明同步方法是否具有继承性。
答:同步方法不具有继承性。
设计思想:在第一个MyRunnable类里用同步方法,然后MyRunnable2类继承第一个类,但是里面的方法不用同步方法,然后看两个类的线程结果安不安全。每个类都创建两个线程,如果这两个线程交叉运行则不安全,依次运行则安全。如果第二个和第一个一样是安全的说明有继承性,如果不安全说明没有继承性。

public class MyRunnable2 extends MyRunnable{

	   Bank bank = new Bank(100);
	    
		@Override
		public void run() {
			method();
		}
		//在MyRunnable里面使用public synchronized void run() 
		public void method() {
			try {
				System.out.println("myRunnable2"+Thread.currentThread().getName()+"存钱开始");
				bank.deposit(10);
				Thread.sleep(100);
				System.out.println("myRunnable2"+Thread.currentThread().getName()+"存钱结束");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

}

运行结果:
在这里插入图片描述
运行结果显示,MyRunnable2的时候,Thread2开始之后还没结束就Thread3开始了,说明是线程不安全的,而MyRunnable1的是安全的,所以不具有继承性。

七、死锁

造成死锁的必要条件

  • 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  • 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  • 环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

当两个线程互相等待对方释放同步监视器就会发生死锁,Java虚拟机没有检测,也没有采取措施来处理死锁的情况,所以多线程程序应该采取措施避免死锁出现,一旦出现死锁,程序既不会发生任何异常,也不会给出任何提示,只是所有线程都处于阻塞状态,无法继续。
比如:

public void run() {
		// TODO Auto-generated method stub
		if(username.equals("a")) {
			synchronized(lock1){
				try {
					System.out.println("username="+username);
					Thread.sleep(3000);
				}catch(InterruptedException e) {
					e.printStackTrace();
				}
				synchronized(lock2) {
					System.out.println("按lock1-》lock2代码顺序执行了");
					//lock2.notify();解决办法二
				}
			}
		}
		else if(username.equals("b")) {
			synchronized(lock2){
				try {
				    //lock2.wait();解决办法二
					System.out.println("username="+username);
					Thread.sleep(3000);
				}catch(InterruptedException e) {
					e.printStackTrace();
				}
				synchronized(lock1) {	
					System.out.println("按lock2-》lock1代码顺序执行了");
				}
			}
		}
	}

如果在主方法开启两个线程,一个传“a”,一个传“b”,这个时候当线程1获得lock1的同时,线程2也获得了lock2,然后又在线程1里面想要获得lock2,在线程2里面获得lock1,此时lock1和lock2都处于被竞争性的访问状态,线程1和线程2处于互相等待对方释放资源的情况,两个线程互不相让,没有一个线程可以继续往下执行,所以卡死在那。
解决办法:

1、每一线程都按照相同的顺序去访问共同想要的资源。

在这里插入图片描述
让第二个线程和第一个线程一样先访问lock1,然后访问lock2.

2、线程通信的方法

先让线程二的lock2处于等待状态,直到线程一唤醒lock2,这时候线程二不会和线程一竞争,直到线程一全部执行完毕,将lock1和lock2都释放,然后执行线程二
在这里插入图片描述

八、线程通信

1、synchronized机制

同五里面的同步方法,由于线程A和线程B持有同一个MyObject类的对象object,尽管这两个线程需要调用不同的方法,但是它们是同步执行的,比如:线程B需要等待线程A执行完了methodA()方法之后,它才能执行methodB()方法。这样,线程A和线程B就实现了 通信。
这种方式,本质上就是“共享内存”式的通信。多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行。

2、wait(),notify(),notifyAll()机制

1、wait()
导致当前线程等待并进入到等待阻塞状态,直到其他线程调用该同步锁对象的notify()或notifyAll()方法来唤醒此线程。
2、notify()
唤醒在此同步锁对象上等待的单个线程,如果有多个线程都在此同步锁对象上等待,则会任意选择其中某个线程进行唤醒操作,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。
3、notifyAll()
唤醒在此同步锁对象上等待的所有线程,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。
注意:
1、wait()方法执行后,当前线程立即进入到等待阻塞状态,其后面的代码不会执行
2、notify()/notifyAll()方法执行后,将唤醒此同步锁对象上的(任意一个-notify()/所有-notifyAll())线程对象,但是,此时还并没有释放同步锁对象,也就是说,如果notify()/notifyAll()后面还有代码,还会继续进行,知道当前线程执行完毕才会释放同步锁对象;
3、notify()/notifyAll()执行后,如果右面有sleep()方法,则会使当前线程进入到阻塞状态,但是同步对象锁没有释放,依然自己保留,那么一定时候后还是会继续执行此线程,接下来同2;

3、while轮循机制

在这种方式下,线程A不断地改变条件,线程ThreadB不停地通过while语句检测这个条件(list.size()==5)是否成立 ,从而实现了线程间的通信。但是这种方式会浪费CPU资源。之所以说它浪费资源,是因为JVM调度器将CPU交给线程B执行时,它没做啥“有用”的工作,只是在不断地测试 某个条件是否成立。

4、管道通信

就是使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值