目标
1、实现方式
2、生命周期
3、 线程调度常用方法
1、实现方式
- 继承Thread类
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i <100; i++) {
System.out.println("thread1---->"+i);
}
}
}
public class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i <100; i++) {
System.out.println("thread2---->"+i);
}
}
}
public static void main(String[] args) {
MyThread thread1 = new MyThread();
System.out.println("线程1启动");
thread1.start();// 一定是start,不是run
MyThread2 thread2 = new MyThread2();
System.out.println("线程2启动");
thread2.start();
}
-
实现Runnable接口
public class RunnableDemo implements Runnable { @Override public void run() { for (int i = 0; i <1000; i++) { System.out.println("thread1---->"+i); } } } public class RunnableDemo2 implements Runnable { @Override public void run() { for (int i = 0; i <100; i++) { System.out.println("thread2---->"+i); } } }
public static void main(String[] args) {
RunnableDemo run1=new RunnableDemo();
Thread thread1 = new Thread(run1);
System.out.println("线程1启动");
thread1.start();// 一定是start,不是run
RunnableDemo2 run2= new RunnableDemo2();
Thread thread2 = new Thread(run2);
System.out.println("线程2启动");
thread2.start();
}
2、线程生命周期
线程的生命周期即线程的生老病死,即线程的状态
可以通过getState()返回此线程的状态。
返回值为枚举类型Enum Thread.State
线程状态。 线程可以处于以下状态之一:
-
[
NEW
]新建状态
尚未启动的线程处于此状态。新创建了一个线程对象,但未调用start方法 -
[
RUNNABLE
]就绪状态,即可运行状态,复合状态
包含ready和running两个状态
ready:表示该线程处于可被调度调度的状态
运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
Thread.yield()方法可以将线程由Running状态变为ready 状态 -
[
BLOCKED
]阻塞状态
被阻塞等待监视器锁定的线程处于此状态。
线程发起I/O操作或者申请由其他线程独占的资源,不占用CPU
当阻塞I/O执行完毕可或者获得了申请的资源,状态就变为RUNNABLE -
[
WAITING
]
正在等待另一个线程执行特定动作的线程处于此状态。
线程执行了object.wait() 或thread.join()方法就会把线程转换为等待状态,执行object.notify()方法或者加入的线程执行完毕,线程将重回RUNNABLE状态 -
[
TIMED_WAITING
]
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
与WAITING的区别是不会无限期的等待,而是等待指定时间后不管期望的操作是否执行完毕,线程都将重回RUNNABLE状态 -
[
TERMINATED
]终止状态
已退出的线程处于此状态。
线程状态图
image.png
3、线程常用方法
方法 功能
getName() 获取当前线程名称
setName() 获取当前线程名称
isAlive() 判断线程是否存活(就绪、运行、阻塞是存活状态)
getPriority() 获取线程优先级
setPriority() 设置线程优先级
Thread.sleep() 强迫线程睡眠(单位:毫秒)(不释放同步锁)
join() (插队)等待某线程结束,再恢复当前线程的运行
yield() 让出CPU资源,当前线程进入就绪状态
wait() 当前线程进入等待状态,即进入等待池。(释放同步锁,由notify()唤醒)
notify() 唤醒等待池中的某个线程
notifyAll() 唤醒等待池中的全部等待线程
1.5.1 判断线程是否存活
ThreadTest t1 = new ThreadTest();
ThreadTest t2 = new ThreadTest();
t1.start();
t2.start();
try {
Thread.sleep(1000); //让主线程睡眠1秒,t1线程肯定死亡。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t1.isAlive());
1.5.2 线程的优先级
Java线程有优先级,优先级高的线程会获得
较多的CPU运行机会。
Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
Java线程的优先级用整数表示,取值范围是1~10; 10最高级。
每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY(5)。
ThreadTest t1 = new ThreadTest();
t1.setPriority(1); //设置成最低级
ThreadTest t2 = new ThreadTest();
t2.setPriority(10); //设置成最高级
t1.start();
t2.start();
1.5.3 线程睡眠
Thread.sleep() 强迫线程睡眠(单位:毫秒)
class ThreadTest extends Thread{
public void run(){
for(int i=0;i<10;i++){
System.out.println(i);
try {
Thread.sleep(1000); //每隔一秒后输出
} catch (InterruptedException e) { //线程睡眠期间如果被打断,将抛出异常。
e.printStackTrace();
}
}
}
}
1.5.4 合并线程
join()的功能是:等待某线程结束,再恢复当前线程的运行
或者说:join()把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
class ThreadTest extends Thread{
public void run(){
for(int i=0;i<10;i++){
System.out.println(this.getName() + ":" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//相当于将tt线程和main线程合并成一个线程。
//那么main线程会等待tt线程结束后再运行。
public static void main(String[] args) {
ThreadTest tt = new ThreadTest ();
tt.start();
try {
tt.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0;i<10;i++){
System.out.println("main:" + i);
}
}
1.5.5 让出CPU资源
yield() 让出CPU资源,当前线程进入就绪状态
class ThreadTest extends Thread{
public void run(){
for(int i=0;i<100;i++){
System.out.println(this.getName() + ":" + i);
if(i%10==0){
yield();
}
}
}
}
public static void main(String[] args) {
ThreadTest tt1 = new ThreadTest ();
ThreadTest tt2 = new ThreadTest ();
tt1.start();
tt2.start();
}
运行结果:
运行到10时,线程让出CPU资源,进入就绪状态。
注意: yield()是让出资源,但并不放弃。它会进入就绪状态,也就是说:它还会与其他线程
一起抢占资源,所以yield()的线程,仍然有可能再次抢占资源。
在加上线程运行的不确定性,所以会导致上面的结论并不是绝对的,只是出现的
概率要高一些。
1.5.6 线程等待与唤醒
wait():当前线程进入等待状态,即进入等待池。(释放同步锁,由notify()唤醒);
notify()/notifyAll():唤醒等待池中的某个或全部等待线程。
wait()和notify()一系列的方法,是属于对象的,不是属于线程的。它们用在线程同步时,synchronized语句块中。因为他们是操作同步锁的。
通俗的说:
wait()意思是说,我等会儿再用这把锁,CPU也让给你们,我先休息一会儿!
notify()意思是说,我用完了,你们谁用?
也就是说,wait()会让出对象锁,同时,当前线程休眠,等待被唤醒,如果不被唤醒,就一直等在那儿。
notify()并不会让当前线程休眠,但会唤醒休眠的线程。
多线程同步
synchronized:同步锁(互斥锁):
在java语言中,引入了同步锁的概念,用以保证共享数据的安全性问题。
关键词synchronized用来给某个方法或某段代码加上一个同步锁。
https://blog.csdn.net/weixin_39214481/article/details/80489586
package com.neuedu.thread.sync;
public class Bank {
private int count=0;
//同步的第一种 方式在方法上加synchronized关键字
//存钱
public synchronized void add(int money){
count+=money;
System.out.println(System.currentTimeMillis()+"存入"+money);
}
//取钱
public void sub(int money){
synchronized (this) {
if(count>=money){
count-=money;
System.out.println(System.currentTimeMillis()+"取"+money);
}else{
System.out.println(System.currentTimeMillis()+"取"+money+"余额不足");
}
}
}
//查看余额
public void search(){
System.out.println("余额"+count);
}
}
package com.neuedu.thread.sync;
public class TestBank {
public static void main(String[] args) {
final Bank bank=new Bank();
Thread t1=new Thread(new Runnable(){
@Override
public void run() {
//在这个线程里做向bank里存钱的操作,并查询余额
for(int i=0;i<20;i++){
bank.add(100);
bank.search();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
Thread t2=new Thread(new Runnable(){
@Override
public void run() {
//在这个线程里做向bank里存钱的操作,并查询余额
for(int i=0;i<20;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
bank.sub(100);
bank.search();
}
}
});
t1.start();
t2.start();
}
}
生产者消费者问题
生产者-消费者(producer-consumer)问题,也称作有界缓冲区(bounded-buffer)问题,两个线程共享一个公共的固定大小的缓冲区。其中一个是生产者,用于将消息放入缓冲区;另外一个是消费者,用于从缓冲区中取出消息。问题出现在当缓冲区已经满了,而此时生产者还想向其中放入一个新的数据项的情形,其解决方法是让生产者此时进行休眠,等待消费者从缓冲区中取走了一个或者多个数据后再去唤醒它。同样地,当缓冲区已经空了,而消费者还想去取消息,此时也可以让消费者进行休眠,等待生产者放入一个或者多个数据时再唤醒它。
1、仓储类
public class Storage {
private int count = 0;
public synchronized void add() {
if (count >= 5) {
try {
System.out.println("库存>=5个,暂停生产" + count);
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println("生产了一个,仓库中有:" + count);
// 生产完了,通知消费者消费
this.notify();
}
public synchronized void sub() {
if (count <= 0) {
try {
System.out.println("库存<=0个,暂停售卖" + count);
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.println("消费了一个,仓库中有:" + count);
// 没有库存了,通知生产者生产
this.notify();
}
}
生产者
public class Producer extends Thread{
private Storage storage;
public Producer(Storage storage) {
this.storage=storage;
}
@Override
public void run() {
for(int i=0;i<50;i++){
this.storage.add();
}
}
}
消费者
public class Consumer extends Thread {
private Storage storage;
public Consumer(Storage storage) {
this.storage=storage;
}
@Override
public void run() {
for(int i=0;i<50;i++){
this.storage.sub();
}
}
}
测试生产/消费
public static void main(String[] args) {
Storage storage = new Storage();
Producer producer = new Producer(storage);
Consumer customer = new Consumer(storage);
customer.start();
producer.start();
}
线程死锁演示
public class TestLock {
public static String objA = "strA";
public static String objB = "strB";
public static void main(String[] args) {
Lock1 l1=new Lock1();
Thread t1=new Thread(l1);
Lock2 l2=new Lock2();
Thread t2=new Thread(l2);
t1.start();
t2.start();
}
}
public class Lock1 implements Runnable{
@Override
public void run() {
try{
System.out.println("Lock1 running");
while(true){
synchronized(TestLock.objA){
System.out.println("Lock1 lock strA");
Thread.sleep(5000);//获取strA后先等一会儿,让Lock2有足够的时间锁住strB
synchronized(TestLock.objB){
System.out.println("Lock1 lock strB");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
package com.neuedu.thread.lock;
public class Lock2 implements Runnable{
@Override
public void run() {
try{
System.out.println("Lock2 running");
while(true){
synchronized(TestLock.objB){
System.out.println("Lock1 lock strB");
Thread.sleep(5000);//获取strA后先等一会儿,让Lock2有足够的时间锁住strB
synchronized(TestLock.objA){
System.out.println("Lock1 lock strA");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
总结:
一、产生死锁的必要条件:
虽然线程在运行过程中可能会发生死锁,但产生死锁是必须具备一定条件的。产生死锁必须同时具备下面四个必要条件,只要其中任意一个条件不成立,死锁就不会产生:
(1)互斥条件:线程对所分配到的资源进行排他性使用,即在一段时间内,某资源只能被一个线程占用。如果此时还有其他进程请求该资源,则请求进程只能等待,直至占有该资源的线程释放该资源。
(2)请求和保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求线程被阻塞,但对自己以获得的资源保持不放。
(3)不可抢占条件:线程已获得的资源在未使用完之前不能被抢占,只能在进程使用完时由自己释放。
(4)循环等待条件。在发生死锁时,必然存在一个线程—资源的循环链,即线程集合{P0,P1,P2,P3,...,Pn}中的P0正在等待P1占用的资源,P1正在等待P2占用的资源,... ... ,Pn正在等待已被P0占用的资源。
二、处理死锁的方法
(1)预防死锁。该方法是通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或几个来预防产生死锁。
(2)避免死锁。在资源的动态分配过程中,用某种方法防止系统进入不安全状态,从而可以避免产生死锁。
(3)检测死锁。通过检测机构及时地检测出死锁的发生,然后采取适当的措施,把进程从死锁中解脱出来。
(4)解除死锁。当检测到系统中已发生死锁时,就采取相应的措施,将进程从死锁状态中解脱出来。常用方法是---撤销一些进程,回收他们的资源,将他们分配给已处于阻塞状态的进程,使其能继续运行。
网友评论