Java高并发程序设计(二)——JAVA并行程序基础(一)

JDK源码

线程是轻量级进程,是程序执行的最小单位,多个线程组成一个进程。
根据JDK源码Thread类定义

  • 线程的生命周期如下:
public enum State {
        NEW,//表示刚刚创建的线程,线程还没有执行
        RUNNABLE,//线程的start()方法调用后,才表示线程开始执行
        BLOCKED,//线程在执行的过程中遇到了synchronized块,就会进入阻塞状态
        WAITING,//线程的等待状态,无限制的等待
        TIMED_WAITING,//线程的等待状态,有时间限制的等待
        TERMINATED;//线程的结束状态
    }

JAVA层的状态转换图

  • 新建线程
    新建线程应使用start(),而不是run(),因为run()只会在当前线程中串行执行run()中的代码。默认情况下,run()方法什么都没有做,如果想让线程做点什么,必须重载run()方法。
    新建线程:
package JAVA线程方法解析;

/**
 * Author haozhixin
 * FUNC   新建线程
 */
public class Thread1  {
	public static void main(String[] args){
		Thread t1 = new Thread();
		t1.start();//开启新线程
		System.out.println(t1.getState());
		t1.run();//在当前线程中执行
		System.out.println(t1.getState());

		Thread t2 = new Thread(){
			@Override
			public void run(){//重载内部类,让线程做run方法的事情
				System.out.println("t2 is running");
			}
		};
		t2.run();
	}
}

注意:如果没有特别的需要,都可以通过继承Thread,重载run()方法来自定义线程。但考虑到继承比较奢侈,我们通常用Runnable接口来实现这种场景,Runnable接口定义了run方法。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Thread类提供了一个非常有用的构造方法,我们看一下Thread构造方法:

public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0);}
public Thread(Runnable target) {  init(null, target, "Thread-" + nextThreadNum(), 0);}
Thread(Runnable target, AccessControlContext acc) {  init(null, target, "Thread-" + nextThreadNum(), 0, acc);  }
public Thread(ThreadGroup group, Runnable target) { init(group, target, "Thread-" + nextThreadNum(), 0); }
public Thread(String name) {init(null, null, name, 0);}
public Thread(ThreadGroup group, String name) {init(group, null, name, 0); }
public Thread(Runnable target, String name) {init(null, target, name, 0);}
public Thread(ThreadGroup group, Runnable target, String name) { init(group, target, name, 0); }
public Thread(ThreadGroup group, Runnable target, String name,long stackSize) {
 init(group, target, name, stackSize); 
 }

Runnable target
实现了Runnable接口的类的实例。要注意的是Thread类也实现了Runnable接口,因此,从Thread类继承的类的实例也可以作为target传入这个构造方法。
String name
线程的名子。这个名子可以在建立Thread实例后通过Thread类的setName方法设置。如果不设置线程的名子,线程就使用默认的线程名:Thread-N,N是线程建立的顺序,是一个不重复的正整数
ThreadGroup group
当前建立的线程所属的线程组。如果不指定线程组,所有的线程都被加到一个默认的线程组中。

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

所以我们新建线程有两种方式:

/**
 * Author haozhixin
 * FUNC   新建线程(两种方式:1,继承Tread类  2,继承Runnable)
 */
public class Thread1  implements Runnable{
	public void run(){//重载内部类,让线程做run方法的事情
		System.out.println("t3 is running");
	}
	public static void main(String[] args){
		Thread t1 = new Thread();
		t1.start();//开启新线程
		System.out.println(t1.getState());
		t1.run();//在当前线程中执行
		System.out.println(t1.getState());
		Thread t2 = new Thread(){
			@Override
			public void run(){//重载内部类,让线程做run方法的事情
				System.out.println("t2 is running");
			}
		};
		t2.run();
		Thread t3 = new Thread(new Thread1());
		t3.start();
	}
}

  • 终止线程
    通常来说,一个线程正常的执行完毕后就会结束,但是也有例外,比如说常驻线程,如何正确的关闭一个线程呢?
    我们发现Thread源码中有一个stop()方法,标记为弃用,这是因为stop在终止线程时,会立即释放这个线程持有的锁,而这些锁恰恰是用来维持数据的一致性的。我们来看下边一个例子:
package JAVA线程方法解析;

/**
 * Author select you from me
 * func 展示stop方法导致的数据不一致问题
 */
public class StopTreadUnSafe {
	public static User user= new User();
	public static class User{
		private int id;
		private String name;
		public User() {
			id = 0;
			name = "0";
		}
		public void setId(int id) {
			this.id = id;
		}
		public void setName(String name) {
			this.name = name;
		}
		public int getId() {
			return id;
		}
		public String getName() {
			return name;
		}
		@Override
		public String toString() {
			return "User[id="+id+",name="+name+"]";
		}
	}
	public static  class ChangeObjectThread extends Thread{
		@Override
		public void run() {
			while(true){
				synchronized (user){
					int v = (int)(System.currentTimeMillis()/1000);
					user.setId(v);
					try{
						Thread.sleep(100);//changeObjectThread沉睡
					}catch (InterruptedException e){
						e.printStackTrace();
					}
					user.setName(String.valueOf(v));
				}
				Thread.yield();
			}
		}
	}
	public static class ReadObjectThread extends Thread{
		@Override
		public void run() {
			while(true){
				synchronized (user){
					if(user.getId()!=Integer.parseInt(user.getName())){
						System.out.println(user.toString());
					}
				}
				Thread.yield();
			}
		}
	}
	public static void main(String[] args) throws InterruptedException{
		new ReadObjectThread().start();
		while(true){
			Thread t = new ChangeObjectThread();
			t.start();
			Thread.sleep(150);//main线程沉睡
			t.stop();
		}
	}
}

输出的部分结果如下:

User[id=1563263376,name=1563263375]
User[id=1563263376,name=1563263375]
User[id=1563263376,name=1563263375]
User[id=1563263377,name=1563263376]
User[id=1563263377,name=1563263376]
User[id=1563263377,name=1563263376]

如何解决上边的问题?
我们可以设置一个stopMe变量,代码延伸为:

public static  class ChangeObjectThread extends Thread{
		volatile boolean stopme = false;
		public void stopMe(){
			stopme=true;
		}
		@Override
		public void run() {
			while(true){
				if(stopme){
					break;
				}
				synchronized (user){
					int v = (int)(System.currentTimeMillis()/1000);
					user.setId(v);
					try{
						Thread.sleep(100);//changeObjectThread沉睡
					}catch (InterruptedException e){
						e.printStackTrace();
					}
					user.setName(String.valueOf(v));
				}
				Thread.yield();
			}
		}
	}

那么有没有更强大的方法实现这个功能呢?下面我们来讨论一下JDK为我们提供的线程中断。

  • 线程中断
    线程中断在线程中是一种重要的协作机制,与线程中断有关的,一共有三个方法,这三个方法看起来很像,容易引起混淆和误用。
public void Tread.interrupt();//设置中断标志位
public boolean Tread.isInterrupted();//判断中断标志位状态
public static boolean Thread.interrupted();//通过中断标志位判断是否被中断,清除标志位状态

请看示例:

package JAVA线程方法解析;

/**
 * Author haozhixin
 * func  测试线程中断
 */
public class InterrrputThread {
	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("ThreadFlag is true");
						break;
					}
					Thread.yield();
				}
			}
		};
		t1.start();
		Thread.sleep(2000);
		t1.interrupt();//只是设置了中断标志位,并没有对线程进行干预。线程不会结束
	}
}

上边看起来与stopme标志位的手法是一样的,但是interrupt的功能更为强大。我们在wait和sleep方法中看下代码:

package JAVA线程方法解析;
/**
 * Author haozhixin
 * func  测试线程中断
 */
public class InterruptThreadSleep {
	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("ThreadFlag is true");
						break;
					}
					try{
						Thread.sleep(2000);//sleep 和wait会抛出异常,如果在
					}catch (InterruptedException e){//捕获了异常以后,中断状态被清空
						System.out.println("ThreadFlag is:"+Thread.currentThread().isInterrupted());//false
						Thread.currentThread().interrupt();//继续设置一次中断状态,如果不设置中断状态,那么线程不会被中断
					}
				}
			}
		};
		t1.start();
		Thread.sleep(2000);
		t1.interrupt();//设置第一次中断状态
	}
}

  • 等待和通知
    为了提高多线程之间的协作关系,JDK提供了两个非常重要的接口wait()和notify()方法。注意:这两个类是Object类中声明的类方法,意味着任何对象都可以调用这两个方法。
public final void wait() throws Interruption;
public final native void notify();

(1)当一个线程调用了wait()方法后,当前线程就会在这个对象上等待。等待到什么时候呢?线程A会一直等待到其他线程调用了obj.notify()方法。
(2)notify()方法是在所有等待的线程中随机选择一个进行唤醒。notifyall()是将所有的等待线程进行唤醒。
(3)还需要注意一点,wait()方法并不是可以随便调用的,必须包含在对应的synchronzied语句中,无论是wait()还是notify()必须先获得对象的监视器。
注意:wait()会释放目标对象的锁,sleep()不会释放任何资源。

  • 挂起和继续执行
    挂起suspend 继续执行 resume 是JDK标记为废弃的方法,原因如下:
    (1)suspend方法在导致线程挂起的同时,不会释放任何锁资源,其他任何线程想要访问被他牵连的锁时,导致阻塞,直到线程进行了resume操作。
    (2)如果因为某些不可知因素,resume意外的在suspend之前执行了,那么锁资源一直不是放,将可能导致死锁。
    (3)对于被挂起的线程,线程的状态居然是runnable,严重影响开发人员对于系统状态的判断。
    那么,我们如何得到一个可靠的suspend函数呢?
    请看示例代码:

  • 等待线程结束和谦让
    (1)多线程应用中,一个线程的输入可能非常依赖另外一个或者多个线程的输出。这个线程需要等待依赖线程执行完毕,才能继续执行。这时需要使用join()。我们可以对join()设定时间join(long millis)。在时间内如果这个线程还没有结束,那么下边的线程将会继续执行。其本质是被等待的线程在结束前调用notifyall()方法。

public final void join() throws Interruption;
public final void join(long mills)throws Interruption;

(2)线程谦让,yield(),这是一个静态方法,他会使当前线程让出CPU,让出之后本身也会对CPU资源进行争夺,但是是否能被再次分配到就不一定了。当你觉得某个线程不那么重要或者优先级很低,可以调用这个方法。

public static native void yield();

第二章总结到这里,下一期我们来踩一下JAVA并行程序基础的坑。

作者:select you from me
链接:https://mp.csdn.net/mdeditor/96120050
来源:CSDN
转载请联系作者获得授权并注明出处。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值