Java线程

1、进程是程序的一次动态执行过程,它需要经历从代码加载、代码执行到执行完毕的一个完整过程,这个过程也是进程本身从产生、发展到最终消亡的过程。多进程操作系统能同时运行多个进程,由于CPU具备分时机制,所以每个进程都能循环获得自己的CPU时间片。由于CPU执行速度非常快使得所有程序想在同时运行一样。
多线程是实现并发机制的一种有效手段,进程和线程一样,都是实现并发的一个基本单位线程是比进程跟小的执行单位,线程在进程基础上进行的进一步划分,所谓多线程是指一个进程在执行过程中可以产尘多个更小的程序片段,这些更小的片段成为线程,这些线程可以同时存在,同时运行,一个进程可能包含多个同时执行的线程。

2、Java中线程的实现
在java中实现多线程操作有两种手段,一种是继承Thread类,另一种就是实现Runnable接口。下面将分别简绍这几两种方式的使用。
2.1、继承Thread类
Thread类是在java.lang包中定义的,一个类只要继承了Thread类,此类就称为多线程实现类。在Thread类子类中,必须明确的覆写Thread类中的run()方法,此方法为县城的主题。线程类的定义如下。
class 类名 extends Thread{
属性…;
方法…;
public void run(){
线程主体;
}
}
例:

 
package com.shuai.ChapteNine;
 
 
class MyThread extends Thread{
private String name;
public MyThread(String name) {
// TODO Auto-generated constructor stub
this.name=name;
} 
public void run() {
for(int i=0;i<10;i++) {
System.out.println(name+"运行,i="+i);
}
}
}
 
public class ThreadByExtends {
public static void main(String[] args) {
MyThread mt1=new MyThread("线程A");
MyThread mt2=new MyThread("线程B");
mt1.run();
mt2.run();
}
 
}

结果:
在这里插入图片描述
可见上面的程序实现只想完mt1对象之后再执行mt2对象,而不是交替执行,那是因为,线程并没有启动,正确的启动方式是使用start()方法
例:

public static void main(String[] args) {
MyThread mt1=new MyThread("线程A");
MyThread mt2=new MyThread("线程B");
mt1.start();
mt2.start();
}

结果:
在这里插入图片描述
可见两个线程对现实交错运行的,哪个线程对象抢到了CPU资源,哪个线程就可以运行,所以每次运行的结果都不一样,在线程启动时虽然调用的是start()方法,但实际上调用的确实run()方法定义的主体。

2.2、问题:
为什么启动线程不能直接使用run()方法?
在启动多线程时为什么必须通过start()方法启动,而不能直接调用run()方法呢?
因为:现成的运行需要本机操作系统的支持。
首先来 看一下start()方法再Thread类中的定义。
// synchronized已同步

public synchronized void start() {
         group.add(this);
 
        boolean started = false;
        try {
     //调用本机操作系统函数
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                 //如果重复调用抛出异常
              throw new IllegalThreadStateException();
 
        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);
 
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

//native(本机):用来表示调用本机的操作系统函数
private native void start0();

从上面代码中可以发现,在一个类中的start()方法调用时真正调用的是start()方法,此方法在声明出使用了native关键字,表示才关键字调用本机的操作系统函数,因为多线程的实现需要依靠底层操作系统支持。

2.3、实现Tunnable接口来实现多线程
注意:当通过实现Runnable接口来实现多线程时,因为,此接口没有start()的定义需要依靠Thread类完成线程的启动,在Thread类中提供了public Thread(Runnable target)和public Thread(Runnable target,String name)两个构造方法,这连个构造方法都可以接受Runnable的子类实例对象,多以就依靠此点启动多线程。

package com.shuai.ChapteNine;
 
class MyThread1 implements Runnable{
private String name;
public MyThread1(String name) {
this.name=name;
}
public void run() {
for(int i=0;i<6;i++) {
System.out.println(name+"运行,i="+i);
}
}
 
}
public class ThreadByImplements {
public static void main(String[] args) {
MyThread1 my1=new MyThread1("线程A");
MyThread1 my2=new MyThread1("线程B");
//此处需要通过Thread对象来启动多线程
Thread th1=new Thread(my1);
Thread th2=new Thread(my2);
th1.start();
th2.start();
}
}

运行结果:
在这里插入图片描述
让我们瞅瞅Runnable接口与Thread类的关系
在这里插入图片描述
在这里插入图片描述

从Threade的定义可以看出,它也是实现了Runnable接口,而在Runnable中只有一个run()方法,所以即使实现了Runnable接口也不能启动,必须要依靠Thread类中的start()来启动线程。
这种模式与代理设计有点类似

在这里插入图片描述
事实上,Thread类和Runnable接口之间在使用上也是有区别的,如果一个类继承Thread类,则不适合用于多线程共享资源,而实现了Runnable几口,就可以方便的实现先资源共享。
例:当继承Thread实现多线程时:

package com.shuai.ChapteNine;
class MyThread3 extends Thread{
private int ticket=5;
  
public void run() {
for(int i=0;i<10;i++) {
if(ticket>0) {
    System.out.println( "买票,ticket="+ticket--);
}
}
}
}
class MyThread4 implements Runnable{
private int ticket=5;
  
public void run() {
for(int i=0;i<10;i++) {
if(ticket>0) {
    System.out.println( "买票,ticket="+ticket--);
}
}
}
}
public class ThreadAboutShared {
 
public static void main(String[] args) {
MyThread3 mt1=new MyThread3();
MyThread3 mt2=new MyThread3();
MyThread3 mt3=new MyThread3();
mt1.start();
mt2.start();
mt3.start();
 
/*MyThread4 mt1=new MyThread4();
new Thread(mt1).start();
new Thread(mt1).start();
new Thread(mt1).start();*/
 
}
 
}

结果:
在这里插入图片描述
可见:三个线程个卖出了5张票
当实现Runnable实现多线程时:
在这里插入图片描述
三个线程共卖出5张票
原因:当继承Thread实现多线程时,实际上是对三个Mythread对象进行多线程操作,而实现Runnable再通过Thread实现多线程是对一个MyThread对象进行多线程操作,因为只有一个对象使用的ticket自然是那一个对象的ticket。

3、线程的状态
要想实现多线程必须再主线程中创建新的线程对象。任何线程一般具有5中状态,即创建、就绪、运行、阻塞、终止。
线程间状态转换与方法之间的关系
在这里插入图片描述
3.1、创建状态
在程序中用构造方法创建了一个线程对象后,新的线程对象便处于创建状态,此时它已经有了相应的内存空间和其他资源,但还处于不可运行状态。新创建一个线程对象可采用Thread类的构造方法来实现。

3.2、就绪状态
新创建线程对象后,调用该线程的start()方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待CPU服务,表明它已经具备了运行的条件。

3.3、运行状态
当就绪状态的线程被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的run()方法。run()方法定义了该线程的操作和功能。

3.4、阻塞状态
一个正在执行的线程在特殊情况下,如果被认为挂起或需要执行耗时的输入输出操作时,会让出CPU并暂时中止自己的执行,进入阻塞状态。在可执行状态下,如果调用sleep(),suspend(),wait()等方法,线程都将进入阻塞状态,阻塞时,线程不能进入排队队列,只有当引起阻塞的原因消除后,线程才可以转入就绪状态。

3.5、死亡状态
线程调用stop()方法或run()方法执行结束后,即处于死亡状态,处于死亡状态的线程不具备继续运行的能力。

4、线程操作的相关方法

4.1、Thread.currentThread():获取当前运行线程的名字:

package com.shuai.ChapteNine;
 
class MyThread6 implements Runnable{
public void run() {
for(int i=0;i<3;i++) {
                   //Thread.currentThread():获取当前运行线程的名字
System.out.println(Thread.currentThread().getName()+"运行,i="+i);
}
}
}
public class ThreadAboutMethond {
public static void main(String[] args) {
MyThread6 my=new MyThread6();
new Thread(my).start();
new Thread(my,"线程A").start();
my.run();
}
 
}

结果:
在这里插入图片描述

注意:在以上程序中,主方法直接通过Runnable接口的子类对象调用其中的run()方法,另外一个是通过线程对象 调用start()方法启动的,从结果中发现,主方法实际上也是一个线程。另外要提醒读者的是,在java中所有的线程都是同时启动的,哪个线程先抢占到CPU资源,哪个线程就先运行。

提问:Java程序每次运行至少启动几个线程?
因为Java是多线程的编程语言,所以Java程序运行时也以线程的方式运行的,那么主方法也就是一个线程(main),但对于一个Java程序来说,一个Java程序运行至少会启动几个线程?
回答:至少启动两个线程。
从之前学习的知识中可以知道,每当使用java命令执行一个类时,实际上都会启动一个JVM,每个JVM实际上就是操作系统中启动了一个进程,Java本身具备了垃圾回收机制。所以在Java运行时至少会启动两个线程,一个main线程,另一个是垃圾收集线程。

4.2、
.isAlive() 判断线程是否启动

package com.shuai.ChapteNine;
 
class MyThread6 implements Runnable{
public void run() {
for(int i=0;i<3;i++) {
                   //Thread.currentThread():获取当前运行线程的名字
System.out.println(Thread.currentThread().getName()+"运行,i="+i);
}
}
}
public class ThreadAboutMethond {
public static void main(String[] args) {
MyThread6 my=new MyThread6();
Thread t= new Thread(my);
                             //.isAlive() 判断线程是否启动
 System.out.println("线程执行前:"+t.isAlive());
 t.start();
 System.out.println("线程执行后:"+t.isAlive());
my.run();
}
 
}

结果:
在这里插入图片描述

4.3、 .join()强制运行

package com.shuai.ChapteNine;
 
class MyThread6 implements Runnable{
public void run() {
for(int i=0;i<10;i++) {
                   //Thread.currentThread():获取当前运行线程的名字
System.out.println(Thread.currentThread().getName()+"运行,i="+i);
}
}
}
public class ThreadAboutMethond {
public static void main(String[] args) {
MyThread6 my=new MyThread6();
Thread t= new Thread(my);
t.start();
 
for(int i=0;i<10;i++) {
if(i>5) {
try{
if(t.isAlive()) {
System.out.print("强制运行:");
}
 
t.join();
}catch(Exception e) {}
}
System.out.println("Main 线程运行 "+i);
}
}
 
}

运行结果:
当i>5 是自定义线程被强制运行:
在这里插入图片描述

当不使用join()时两个线程交替运行

在这里插入图片描述

4.4、 .interrupt()中断线程操作
package com.shuai.ChapteNine;

 
import org.junit.Test;
 
class MyThread7 implements Runnable{
 
@Override
public void run() {
// TODO Auto-generated method stub
 System.out.println("1、进入run方法");
try {
Thread.sleep(2500);
System.out.println("2、完成休眠");
}catch(Exception e) {
System.out.println("3、run休眠被终止");
return ;
}
System.out.println("4、run方法正常结束");
 
}
 
}
 
public class ThreadMethond2 {
 
@Test
public void interruptTest() {
MyThread7 mt1=new MyThread7();
Thread t=new Thread(mt1);
t.start();
 
try {
//线程休眠
Thread.sleep(2000);
}catch (Exception e) {
// TODO: handle exception
}
//中断线程执行
t.interrupt();
}
}

运行结果:
在这里插入图片描述
可以看出线程被中断后,休眠终止

4.5、.setDeamon(true)设置后台线程

package com.shuai.ChapteNine;
 
import org.junit.Test;
 
/**
 * setDeamon()设置后台线程
 * @author guai
 *
 */
 
 
class Mythread8 implements Runnable{
 
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
System.out.println(Thread.currentThread().getName()+"在运行");
}
 
}
 
 
}
public class ThreadAboutSetDaemon {
@Test
public void testDeamon() {
Mythread8 my=new Mythread8();
Thread t=new Thread(my,"后台线程");
t.setDaemon(true);
t.start();
}
 
}

结果:
在这里插入图片描述

4.6、setPriority()设置线程优先级

package com.shuai.ChapteNine;
 
import org.junit.Test;
 
/**
 * 设置线程优先级 setPriority()
 * MAX_PRIORITY   最高级
 * NORM_PRIORITY  中等
 * MIN_PRIORITY   最低
 * 
 * 
 * @author guai
 *
 */
class MyThread9 implements Runnable{
 
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<5;i++) {
try {
//Thread.sleep(500);
}catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行,i="+i);
}
}
 
}
 
public class ThreadAboutPriority {
@Test
public void testPriority() {
Thread t1=new Thread(new MyThread9(),"线程A");
Thread t2=new Thread(new MyThread9(),"线程B");
Thread t3=new Thread(new MyThread9(),"线程C");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.NORM_PRIORITY);
t3.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
t3.start();
 
 
}
}

结果: 可以看出运行结果并没有按照其优先级大小来运行,哪个线程先执行由CPU调度决定,但这并不表示设置不起作用,因为CPU调度规则较为复杂,即使优先级较高也未必会先运行,但是优先级高的线程优先获取资源的概率会增加。
在这里插入图片描述

4.7、线程礼让 yield()

package com.shuai.ChapteNine;
 
import java.awt.event.MouseWheelEvent;
 
import org.junit.Test;
 
/**
 * yield 线程礼让
 * @author guai
 *
 */
class MyThread10 implements Runnable{
 
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<7;i++) {
System.out.println(Thread.currentThread().getName()+" run "+i);
 
if(i==3) {
 
System.out.println( "----Thread yeild ----");
//线程礼让
Thread.yield();
}
}
}
 
 
}
 
public class ThreadAboutYield {
 
@Test
public void testYield() {
MyThread10 my=new MyThread10();
Thread t1=new Thread(my,"Thread-A");
Thread t2=new Thread(my,"Thread-B");
t1.start();
t2.start();
}
 
}
 

结果:
在这里插入图片描述

5、同步与死锁
5.1、问题:当程序中加入延迟操作,在运行的最后出现了ticket 0,即票卖超了
代码如下:

package com.shuai.ChapteNine;
 
import org.junit.Test;
 
/**
 *线程同步
 * @author guai
 *
 */
class SynAndLockDemo implements Runnable{
 
private int ticket=5;
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
}
 
if(ticket>0) {
    System.out.println("sell ticket:"+ticket--);
} 
}
 
}
 
}
 
public class SynchronizationAndDeadlock {
 
public static void main(String[] args) {
SynAndLockDemo s1=new SynAndLockDemo();
Thread t1=new Thread(s1);
Thread t2=new Thread(s1);
Thread t3=new Thread(s1);
t1.start();
t2.start();
t3.start();
 
}
 
 
}

结果:
在这里插入图片描述

5.1.1为什么会出现上面的问题呢?
从上面的操作代码中可以发现对于票数的操作如下:
(1)判断票数是否大于0,大于0表示可以卖出
(2)如果票数大于0,则将票卖出

但是在上面的操作中,在步骤(1)和步骤(2)之间加入延迟操作后,一个线程可能在还没有对票数进行减一操作,其它线程就已经将票数减少了,这样就会出现票数为0的情况。
如果想解决这样的问题就必须使用同步,即多个操作在同一个时间段内只能有一个线程进行,其它线程必须等待此线程完成后,才能继续操作。

5.1.2、使用同步解决问题:
在这里插入图片描述
使用:
synchronized(同步对象){
需要同步的代码;
}
例1:

package com.shuai.ChapteNine;
 
/**
 * 线程同步
 * 
 * @author guai
 *
 */
class SynDemo implements Runnable {
 
private int ticket = 5;
 
public void run() {
// TODO Auto-generated method stub
 
while (true) {
synchronized (this) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
// e.printStackTrace();
}
if (ticket > 0) {
System.out.println("sell ticket:" + ticket--);
}
}
}
 
}
 
}
 
public class SynchronizationDemo {
 
public static void main(String[] args) {
SynDemo s1 = new SynDemo();
Thread t1 = new Thread(s1);
Thread t2 = new Thread(s1);
Thread t3 = new Thread(s1);
t1.start();
t2.start();
t3.start();
 
}
 
}

结果:
在这里插入图片描述
例2、也可以使用synchronized关键字声明方法

package com.shuai.ChapteNine;
 
/**
 * 线程同步
 * 
 * @author guai
 *
 */
class SynDemo implements Runnable {
 
private int ticket = 5;
 
@Override
public void run() {
// TODO Auto-generated method stub
this.sale();
 
}
 
public synchronized void  sale() {
 
while (true) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
// e.printStackTrace();
}
if (ticket > 0) {
System.out.println("sell ticket:" + ticket--);
}else {
return;
}
}
}
 
}
 
public class SynchronizationDemo {
 
public static void main(String[] args) {
SynDemo s1 = new SynDemo();
Thread t1 = new Thread(s1);
Thread t2 = new Thread(s1);
Thread t3 = new Thread(s1);
t1.start();
t2.start();
t3.start();
 
}
 
}

结果:
在这里插入图片描述

5.2、死锁
同步可以保证资源共享操作的正确性,但是过多同步可能导致死锁
如:
张三想要李四的画,李四想要张三的书,张三对李四说:你把画给我我把书给你,李四对张三说:你把书给我我给你画。此时李四在等张三给书,张三在等李四给画
,这样下去他们谁也得不到想要的东西,这就是死锁。
死锁就是两个线程都在等待对方先完成,造成了程序的停止。
下面举一个例子:

package com.shuai.ChapteNine;
 
import javax.xml.bind.helpers.ValidationEventImpl;
 
/**
 * 死锁
 * 
 * @author guai
 *
 */
class ZhangSan {
public void say() {
System.out.println("给我画,我给你书");
}
 
public void get() {
System.out.println("张三得到了画");
}
}
 
class LiSi {
public void say() {
System.out.println("给我书,我给你画");
}
 
public void get() {
System.out.println("李四得到书");
}
}
 
class ThreadDeadLock implements Runnable {
private static ZhangSan zs = new ZhangSan();
private static LiSi ls = new LiSi();
boolean flag;
 
@Override
public void run() {
// TODO Auto-generated method stub
if (flag) {
synchronized (zs) {
zs.say();
try {
Thread.sleep(200);
} catch (Exception e) {
// TODO: handle exception
}
synchronized (ls) {
ls.get();
}
}
} else {
synchronized (ls) {
ls.say();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (zs) {
ls.get();
}
}
 
}
 
}
 
}
 
public class DeadLock {
public static void main(String[] args) {
ThreadDeadLock t1 = new ThreadDeadLock();
ThreadDeadLock t2 = new ThreadDeadLock();
 
t1.flag = true;
t2.flag = false;
Thread thA = new Thread(t1);
Thread thB = new Thread(t2);
 
thA.start();
thB.start();
}
}

结果:
在这里插入图片描述
两个线程都在等待对方执行造成死锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值