线程与进程:
线程:是指程序的运行流程。多线程机制可以同时运行多个程序块,使程序的运行的效率更快。
进程:一个由操作系统分配的内存空间,包含一个或多个线程。
理解线程与进程:
打开一个微博客户端就是打开了一个进程,打开微博以后除了可以直接看见微博内容,还看见有“系统消息提示”、“好友消息提示”、“微博更新情况”等等信息,而后面提到的三个功能就是线程所支持的。也就可以理解什么叫“进程包含多个线程”,“线程是进程的更小执行单位”。
线程的执行:
Java中线程的执行有两种执行方式:继承Thread类、实现Runnable接口。
启动线程都需要调用start()完成:
继承Thread类——可以直接依靠实例化对象调用start()。
实现Runnable接口——需要靠Tread的构造器方法接受Runnable子类实例化对象,再靠Thread类的实例化对象调用start()。
1、继承Thread类:
步骤:
- 继承Thread类。
- 覆写Thread类中的run()方法。
- 通过实例化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接口。
步骤:
- 实现Runnable接口。
- 非接口就覆写Runnable接口的run()方法。
- 实例化Runnable子类对象。
- 实例化Thread类对象。
- 启动线程
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、增强代码健壮性,代码能够被多个线程共享,代码与数据是独立的。
线程的状态:
- 创建状态
使用如Thread thread=new Thread();的构造方法创建一个线程后,线程就处于新建状态,此时它已拥有内存空间和其他资源,但还处于不可运行状态。 - 就绪状态
新建的线程调用start()方法启动线程,线程就处于就绪状态,进入线程队列进行排队,等待CPU服务。 - 运行状态
就绪状态的线程获得CPU资源后就处于运行状态,会自动调用线程的run()。 - 堵塞状态
因某些原因如进行号是操作,线程会放弃CPU资源并终止自己的行为,进入阻塞状态。当处于阻塞状态线程不会在线程队列进行排队,当阻塞状态消除会转入就绪状态。 - 死亡状态
线程调用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():唤醒所有的线程。
线程生命周期: