多线程

线程与进程:
线程:是指程序的运行流程。多线程机制可以同时运行多个程序块,使程序的运行的效率更快。
进程:一个由操作系统分配的内存空间,包含一个或多个线程。
理解线程与进程:
打开一个微博客户端就是打开了一个进程,打开微博以后除了可以直接看见微博内容,还看见有“系统消息提示”、“好友消息提示”、“微博更新情况”等等信息,而后面提到的三个功能就是线程所支持的。也就可以理解什么叫“进程包含多个线程”,“线程是进程的更小执行单位”。

线程的执行:
Java中线程的执行有两种执行方式:继承Thread类、实现Runnable接口。

启动线程都需要调用start()完成:
继承Thread类——可以直接依靠实例化对象调用start()。
实现Runnable接口——需要靠Tread的构造器方法接受Runnable子类实例化对象,再靠Thread类的实例化对象调用start()。

1、继承Thread类:
步骤:

  1. 继承Thread类。
  2. 覆写Thread类中的run()方法。
  3. 通过实例化Thread子类的对象运行start()方法。
    启动线程虽然调用的是start(),但实际调用的是run()定义的主体——调用start()才能启动线程是因为启动线程需要操作系统的支持(下面 native关键字表示调用本机操作系统函数 )。详细情况还可以参考JDK文档——https://docs.oracle.com/javase/8/docs/api/。

start()部分源码:

public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }

    private native void start0();
public class ThreadDemo01 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ThreadOne th1=new ThreadOne("线程1");
		ThreadOne th2=new ThreadOne("线程2");
		th1.start();	//3、通过start()启动线程。
		th2.start();
		
	}

}

class ThreadOne extends Thread{	//1、继承Tread类
	String  name;
	public ThreadOne(String name) {
		this.name=name;
	}
	public void run() {	//2、覆写Thread类的中的子类
		for(int i=0;i<4;i++) {
			System.out.println(this.name+"第"+i+"个数字");
		}
	}
}

有趣的运行结果:

线程1第0个数字
线程2第0个数字
线程2第1个数字
线程1第1个数字

通过运行发现,结果并不是按照“线程1”——>“线程2”的运行方式进行执行。为什么呢?
哪个线程抢到CPU资源,哪个线程先执行。
2、实现Runnable接口。
步骤:

  1. 实现Runnable接口。
  2. 非接口就覆写Runnable接口的run()方法。
  3. 实例化Runnable子类对象。
  4. 实例化Thread类对象。
  5. 启动线程
public class RunnableDemo01 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		RunnableOne run1=new RunnableOne("线程1");	//3、实例化Runnable接口子类对象
		RunnableOne run2=new RunnableOne("线程2");
		Thread t1=new Thread(run1);		//4、实例化Thread对象,并接受Runnable子类对象
		Thread t2=new Thread(run2);
		t1.start();		//调用start()启动线程
		t2.start();
	}

}
class RunnableOne implements Runnable{	//1、实现Runnable接口
	String name;
	public RunnableOne(String name) {
		this.name=name;
	}
	@Override
	public void run() {	//覆写接口的run()
		// TODO Auto-generated method stub
		for(int i=0;i<2;i++) {
			System.out.println(this.name+",第"+i+"个数字");
		}
	}
	
}

运行结果:

线程1,第0个数字
线程2,第0个数字
线程1,第1个数字
线程2,第1个数字

Thread类和Runnable接口的区别:

1、Thread类是Runnable接口的子类。

public class Thread extends Object implements Runnable

Thread虽是Runnable的子类但并未实现Runnable里面的所有方法——>
Thread类部分代码:

private Runnable target;

public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
                      ..........
   					 this.target = target;
   					 ..........
    }
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

从定义中可以发现,在Thread类中的run()方法调用的是Runnable接口的run(),因此通过Thread实现多线程必须覆写run()方法。——工厂设计模式(忘记请参考我博客https://blog.csdn.net/qq_41590178/article/details/89977923)——

2、继承Thread类不适合进行资源共享,实现Runnable接口适合资源共享。
继承Thread类,资源不能共享:

public class ThreadDemo02 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ThreadTwo tt1=new ThreadTwo();
		ThreadTwo tt2=new ThreadTwo();
		tt1.start();
		tt2.start();
	}

}
class ThreadTwo extends Thread{
	int num=3;	//总共三个可用数字
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=1;i<10;i++) {
			if(num>0) {
				System.out.println("可以数字还有"+(num--)+"个");
			}
		}
	}
}

不合理的结果:

可以数字还有3个
可以数字还有2个
可以数字还有1个
可以数字还有3个
可以数字还有2个
可以数字还有1个

总共就三个数字,但每个线程都取到了3个数字。显然不合理
实现Runnable接口,资源可以共享:

public class RunnableDemo02 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		RunnableTwo rt1=new RunnableTwo();
		new Thread(rt1).start();
		new Thread(rt1).start();
	}

}
class RunnableTwo implements Runnable{
	int num=3;	//总共3个数字

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=1;i<10;i++) {
			if(num>0)
				System.out.println("可用的数字还有:"+(num--)+"个");
		}
	}
	
}

理想的数据共享结果:

可用的数字还有:3个
可用的数字还有:2个
可用的数字还有:1个

实现Runnable接口进行多线程操作的优点:
1、避免单继承的局限。(接口知识忘记,可参考https://blog.csdn.net/qq_41590178/article/details/89928598)
2、适合多个线程处理同一资源,实现资源共享。(如想回顾static关键字,https://blog.csdn.net/qq_41590178/article/details/89818975可参考)
3、增强代码健壮性,代码能够被多个线程共享,代码与数据是独立的。

线程的状态:
线程五种状态

  1. 创建状态
    使用如Thread thread=new Thread();的构造方法创建一个线程后,线程就处于新建状态,此时它已拥有内存空间和其他资源,但还处于不可运行状态。
  2. 就绪状态
    新建的线程调用start()方法启动线程,线程就处于就绪状态,进入线程队列进行排队,等待CPU服务。
  3. 运行状态
    就绪状态的线程获得CPU资源后就处于运行状态,会自动调用线程的run()。
  4. 堵塞状态
    因某些原因如进行号是操作,线程会放弃CPU资源并终止自己的行为,进入阻塞状态。当处于阻塞状态线程不会在线程队列进行排队,当阻塞状态消除会转入就绪状态。
  5. 死亡状态
    线程调用stop()或运行完run()会自动进入死亡状态,结束线程。

线程操作的常用方法:
1、返回正准执行的线程——currentThread()
2、取得和设置线程名称——setName()、getName()

public class RunnableDemo03 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		RunnableThree rte1=new RunnableThree();
		new Thread(rte1,"线程1").start();
		new Thread(rte1).start();
		
	}

}
class RunnableThree implements Runnable{
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("当前线程名称为:"+Thread.currentThread().getName());
	}
	
}

结果:

当前线程名称为:Thread-0
当前线程名称为:线程1

如果没有为线程指定一个名称,系统会以“Thread-Xx”格式为线程命名。

3、判断线程是否存活——isAlive()
由上面线程状态知道,当线程调用start()后就处于就绪状态,进入线程队列中排队等待获取CPU资源,以便存活运行。即isAlive()是在线程获取CPU资源进入运行状态后才开始生效。

.........
Thread thread=new Thread();
或
Thread thread=new Thread(接收Runnable子类对象+线程名字);
thread.start();
thread.isAlive();	//判断线程是否存活

4、线程的强制执行——join()
当需要某个线程先执行时就可以调用join(),这时其他线程全部处于阻塞状态,直到该线程运行结束。

Thread thread=new Thread();
或
Thread thread=new Thread(接收Runnable子类对象+线程名字);
thread.start();
thread.join();

5、线程的休眠——sleep()
需要某个线程进行暂时休眠可以调用Thread.sleep()。

//使用Thread.sleep(),通常需要进行异常处理。
try(){
	Thread.sleep(等待的毫秒数);
}catch (Exception e){}.....

6、中断线程——interrupt()
一个线程可以调用interrupt()使另一个线程中断其运行状态。
如使用interrupt()使休眠的线程中断休眠,这时之前休眠线程就会执行catch()中的代码。

Thread thread=new Thread();
thread。interrupt();

6、将线程设置为后台——setDaemon()
在Java中,只要前台有一个线程在运行,则整个Java进程都不会消失,将线程设置为后台,即使Java进程结束,后台线程也会继续运行。

Thread thread=new Thread();
thread.setDaemon(true); //当为true表示该线程在后天执行

7、设置线程的优先级——setPriority()
在Java线程操作中,所有线程在运行前都保持在就绪状态中,优先级高的线程就可能优先获得CPU资源优先执行。

/**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

从以上Java源码,我们可以回忆起public final static修饰的变量为常量,常量名要大写。
使用方法:

Thread thread=new Thread();
thread.setPriority(MIN_PRIORITY/NORM_PRIORITY/MAX_PRIORITY);

8、线程的礼让——yield()
当某个线程需要更快执行,我们处理中断其他线程我们还可以请其他线程礼让,让它优先执行。

Thread.currentThread().yield()

同步与死锁
同步:在同一时间内只能有一个线程执行,其他线程需要等待该线程执行结束才可以继续执行。
1、代码同步:

synchronized(需要同步的对象){
		需要同步的代码;
}
public class SyncDemo01 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		SyncOne sy=new SyncOne();
		new Thread(sy,"线程A").start();
		new Thread(sy,"线程B").start();
	}
}
class SyncOne implements Runnable{
	int num=5;
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=1;i<10;i++) {
			synchronized(this) {
				if(num>0) {
					try{
						Thread.sleep(300);
					}catch(Exception e) {
						e.printStackTrace();
					}
					System.out.println("可用数据还剩:"+(num--)+"个");
				}
			}
		}
	}
}

2、方法同步

访问权限 [final] [static] [synchronized] 返回值类型 方法名(参数)[throws Exception1,Exception2]{
			[return [返回值|返回调用处]]; 
}

死锁:过量使用同步,以至于任何线程都在等待对方先执行,造成程序停滞。一般程序的死锁都是在程序运行时出现的。

public class SyncDemo02 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ThreadDeadLock th1=new ThreadDeadLock();
		ThreadDeadLock th2=new ThreadDeadLock();
		th1.flag=true;
		th2.flag=false;
		new Thread(th1).start();
		new Thread(th2).start();
	}

}
class ThreadDeadLock implements Runnable{
	static Zhangsan zs=new Zhangsan();
	static Lisi ls=new Lisi();
	//在同步中通过标识判断谁先执行——常用方法
	boolean flag=true;
	@Override
	public void run() {
		// TODO Auto-generated method stub
		if(flag) {
			synchronized (zs) {
				zs.start();
				try {
					Thread.sleep(3000);
				}catch(Exception e) {
					e.printStackTrace();
				}
				synchronized (ls) {
					zs.end();
				}
			}
		}else {
			synchronized (ls) {
				ls.start();
				try {
					Thread.sleep(3000);
				}catch(Exception e) {
					e.printStackTrace();
				}
				synchronized (zs) {
					ls.end();
				}
			}
		}
	}
	
}
class Zhangsan{
	public void start() {
		System.out.println("张三开始工作");
	}
	public void end() {
		System.out.println("张三下班了");
	}
}
class Lisi{
	public void start() {
		System.out.println("李四开始工作");
	}
	public void end() {
		System.out.println("李四下班了");
	}
}

结果:

张三开始工作
李四开始工作

从程序的运行结果来看,程序无法继续向下执行进入死锁,张三、李四都在工作每人下班。

Object对线程的支持
1、public final void wait() throws InterruptedException:线程等待
2、public final void wait(long timeout) throws InterruptedException:线程在指定的最长时间内进行等待,已毫秒为单位。
3、public final void wait(long timeout,int nanos) throws InterruptedException:线程等待,并指定最长的毫秒及纳秒。
4、public final void notify():唤醒第1个等待的线程。
5、public final void notifyAll():唤醒所有的线程。

线程生命周期:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值