多线程开发1--线程的基本操作

上一篇文章主要介绍了一些准备知识,从这篇文章开始正式进入多线程开发阶段。

新建线程

Java中对于线程的创建提供了两种方法,一种是继承Thread类(本质上还是实现Runnable接口),另一种是实现Runnable接口。

//继承Thread类
public class MyThread1 extends Thread{
        @Override
        public void run(){
            System.out.println("Hello Thread");
        }

    }

    //实现Runnable接口
    public class Mythread2 implements Runnable{
        @Override
        public void run(){
            System.out.println("Hello Thread");
        }
    }

以上两种方式就是我们一般自己创建线程时使用的方法,但只是创建了这个类,调用方法如下。

//继承Thread类
MyThread1 mt1=new MyThread();
mt1.start();
//实现Runnable接口
MyThread mt2=new MyThread();
//新建一个Thread对象,把实现了Runnable的实例当作构造参数传进去
Thread t2=new Thread(mt2);
t2.start();

这里启动线程时我们发现是通过调用start()方法,然而我们并没有写这个方法啊。其实对于Thread这个类来说,start()方法就是新建一个线程并让他执行run()方法。run()方法一般是需要我们覆写的,用来实现我们想要的功能。那这里又有一个问题,如果我们直接调用run()方法会发生什么事呢,直接调用的话实际上就把run()方法当成一个普通方法了,只会在当前线程中串行执行run()中的代码。那么还有一个问题,上面两种调用方法好像不太一样啊,没错,我们实现了Runnable接口的类是无法直接调用start()方法的,我们要通过Thread类提供的一个构造方法新建一个Thread对象,然后再通过start()方法启动线程。乍看之下这种实现接口的方式好麻烦,其实不然。Java中存在单继承的限制,所以继承就显得尤为“珍贵”,一般情况能实现接口就尽量实现接口,而不是去继承,这点在开发中很重要。所以尽量使用实现接口的方式进行多线程开发。那么还有一个问题(怎么这么多问题,这不是为了让读者了解清楚没有疑问嘛),笔者在前面讲了继承Thread类本质上还是实现Runnable接口,这是为什么呢,感兴趣的读者可以查看Thread类的源码,会发现Thread这个类本身就是实现Runnable接口的,所以回归本质实现Runnable接口是最合理的方式。

线程中断

线程中断不会立刻让线程退出,而是给该线程一个通知,至于接到该通知后是否退出,由目标线程自行决定。与线程中断有关的有三个方法

//中断线程
public void Thread.interrupt()
//判断线程是否被中断
public boolean Thread.isInterrupted()
//判断是否被中断并清除中断状态
public static boolean Thread.interrupted()

Thread.interrupt()方法通知目标线程中断也就是设置目标中断位,表示当前线程被中断了。

public static void main(String[] args) throws InterruptedException{
    Thread t1=new Thread(){
        @Override
        public void run(){
            while(true){
                if(Thread.currentThread().isInterrupted()){
                    System.out.println("Interrupted");
                    break;
                }
                Thread.yield();
            }
        }
    };
    t1.start();
    Thread.sleep(2000);
    t1.interrupt();
}

我们显示的调用了中断方法,并在run()方法里设置了中断标志位来检测是否已经中断。

线程中其他一些方法

//睡眠
public static native void sleep(long millis) throws InterruptedException

//等待和通知(这两个方法是Object类中的)
public final void wait() Throws InterruptedException
public final native void notify()

//挂起和继续执行(这两个方法已过时)
public final void suspend()
public final void resume()

//等待线程结束和谦让
public final void join() throws InterruptedException
public final sysnchronized void join(long mills) throws InterruptedException
public static native void yield()

//终止线程(该方法已过时)
public final void stop()

sleep()使线程进入阻塞状态,即并不释放对象锁(如果此时其他线程试图进入synchronized同步块是不被允许的)。此时会将cpu让给其他线程,无论其他线程的优先级。需要捕捉异常(InterruptedException)。
join() 方法使调用该方法的线程在此之前执行完毕,也就是等待调用该方法的线程执行完毕后再往下继续执行。注意该方法也需要捕捉异常。
yield()函数表示一种愿意是放cpu给其他线程使用的声明,但具体哪个线程运行由cpu自己进行调度,即可能声明后也仍然是这个进程在运行。若是给其他进程运行的机会则该进程的优先级一般不小于原来的进程。
wait()则是真正释放当前对象锁,进入等待池,只有使用notify()或者notifyAll()唤醒后该线程才能够进入等锁池,获得对象锁后就会进入就绪状态。
notify()唤醒处于等待状态的一个线程,但无法确定唤醒的究竟是哪一个,由JVM确定是哪个线程,且与优先级无关。
notifyAll()唤醒所有等待的线程,但这些线程需要竞争得到锁才能进入就绪状态。

上面还有三个已过时的方法,这表示他们存在一些问题可能会在未来版本中删除。
suspend在挂起线程的同时并不会释放任何锁,其他线程需要访问这个锁资源时都会被阻塞,除非该线程进行了resume()操作,使得被挂起的线程继续执行,从而阻塞在相关锁上的线程也可以继续进行,但是如果resume()操作未能在suspend()操作之后执行,那么挂起的线程就很难继续执行下去,而且它占用的锁不会被释放,从而导致整个系统工作不正常。对于这种挂起操作,我们尽量使用wait()和notify()来代替

stop()操作会强制把执行到一半的线程终止,这可能会引发一些数据不一致问题,所以同样不推荐使用。

线程组

如果线程数量很多,而且功能分配明确,就可以将相同功能线程放入线程组里。

public class ThreadGroupName implements Runnable{
    public static void manin(String[] args){
        ThreadGroup tg=new ThreadGroup("PrintGroup");
        //将新建的线程放入线程组中并指定名称
        Thread t1=new Thread(tg,new ThreadGroupName(),"T1");
        Thread t2=new Thread(tg,new ThreadGroupName(),"T2");
        t1.start();
        t2.start();
        //获得活动线程的总数(仅仅是估计值,因为线程是动态的)
        System.out.println(tg.activeCount());
        tg.list();
    }
    @Override
    public void run(){
        String groupAndName=
        Thread.currentThread().getThreadGroup().getName()+
        "-"+Thread.currentThread().getName();
        while(true){
            System.out.println("I am "+groupAndName);
            try{
                Thread.sleep(3000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

volatile 与 synchronized

当使用volatile声明一个变量时相当于告诉虚拟机这个变量可能会被某些程序或者线程修改,这样当一个线程修改了这个变量时其它线程能够立刻知道更新后的值。但无法保证一些复合操作的原子性。
synchronized关键字的作用是实现线程间的同步,对需要同步的代码块加锁,使得每次只有一个线程进入同步块,从而保证线程间的安全性。
1. 指定加锁对象:给指定对象加锁,进入同步代码块前要先获得对象的锁
2. 直接作用于实例方法:相当于对当前实例加锁,进入代码块前要获得当前实例的锁
3. 直接作用于静态方法:相当于对当前类加锁,进入同步代码块前要获得当前类的锁

public class Demo implements Runnable{
    static Demo instance=new Demo();
    static int i=0;
    @Override
    public void run(){
        for(int j=0;j<100;j++){
            //对指定对象加锁
            synchronized(instance){
                i++;
            }
        }
    }
}

public class Demo implements Runnable{
    static Demo instance=new Demo();
    static int i=0;
    //直接作用于实例方法
    public sychronized void increase(){
        i++;
    }
    @Override
    public void run(){
        for(int j=0;j<100;j++){
            increase();
        }
    }

}
//直接作用于静态方法
public static sychronized void increase(){
    i++;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值