【并发】线程基础

并发程序

 JMM(java内存模型)

1.1原子性

是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始就不会被别的线程打断。

public class MutipleLong {
	public static Long long1=0l;
	public MutipleLong() {
		// TODO Auto-generated constructor stub
	}

	public static void main(String[] args) {
		new Thread(new WriteT(111l)).start();
		new Thread(new WriteT(222l)).start();
		new Thread(new WriteT(333l)).start();
		new Thread(new WriteT(444l)).start();
		new Thread(new ReadeT()).start();
	}
}
class WriteT implements Runnable {
	private long to;
  public WriteT(long longs) {
     this.to=longs;
	}
	@Override
	public void run() {
		while(true){
			MutipleLong.long1=to;
			Thread.yield();
		}
	}
}	
class ReadeT implements Runnable{
		@Override
		public void run() {
			while(true){
				long sys=MutipleLong.long1;
				if(sys!=111l&&sys!=222l&&sys!=333l&&sys!=444l){
					System.out.println(sys);
				}
				
			}
		}
}

如下代码片段所示,如果是32的操作系统你会发现。读线程出现了数据,然而并不是你期待的结果,为什么。因为32位的操作系统每次读写都是以32个byte作为操作单元的,long是64位的数据类型,那么就意味着单个线程要读两次,然而如果无法保证写线程的原子性的话,边写边读就有可能只读到了一半。然而在64位的操作系统中就不会发生这样的问题。

1.2可见性

可见性是指当一个线程修改了变量的值,其他线程是否立马能知道这个修改。对于串行的程序来说是没有这样的问题的。修改以后,再去读取变量的值。自然是变量的新值。在并行程序中就不一定了。如果某个线程修改了一个全局变量,其他线程是否立马能知道这个修改,显然在并行程序中就不一定了。如果CPU1与CPU2同时操作一个类变量t,CPU1把这个变量读取到寄存器或者缓存中。然而CPU2操作这个全局变量t将它修改成其他的值。对于CPU1来说是不可见的,CPU1还在操作着寄存器或者缓存。

比如如上所示的代码,你觉得会不会出现一种可能就是r2=A r1=1。上面的这组指令,看似没有问题,当然顺序执行的话,是不可能发生上面的情况的,不过如果指令重排的话就不一定不会产生这种情况了。

1.3有序性

有序性问题是很相对比较难理解的,可能我们早已经习惯了,代码是从上往下顺序执行的。然而事实在真实的环境下,是存在多线程并发执行的,这个时候程序的执行就未必是从上到下的了。有序性问题的原因是程序在运行的时候可能会进行指令重排,重排后的指令与原指令未必一致。

public class Demo2 {
	boolean flag=false;
	public static int  a=1;
	public Demo2() {
		
	}
	public void writer(){
		a=10;
		flag=true;
	}
	
	public void reader(){
		if(flag){
		   if(a==1){
			   System.out.println("存在指令重排");
		   }
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		Demo2 demo2=new Demo2();
	    ExecutorService executorService=Executors.newCachedThreadPool();
	    for(int i=0;i<1000000;i++){
	    	executorService.execute(new ThreadA(demo2));
	    }
	    for(int j=0;j<1000000;j++){
	    	executorService.execute(new ThreadB(demo2));
	    }
	}
	
}

class ThreadA implements Runnable{
	private Demo2 demo2=null;
	public ThreadA(Demo2 demo2) {
		this.demo2=demo2;
	}
	@Override
	public void run() {
		demo2.writer();
	}
}
class ThreadB implements Runnable{
	private Demo2 demo2=null;
	public ThreadB(Demo2 demo2) {
		this.demo2=demo2;
	}
	@Override
	public void run() {
		demo2.reader();
	}
}

据说如上代码所示,存在如下图所示的指令重排现象。意思就是说对于线程B来说,线程A中的指令执行,未必是有序的。不过遗憾的是我测试的代码并没有发生指令重排现象。所示这段代码是否会指令重排是不一定的,不过计算机的工作原理当然是存在的。

计算机当中往往把一些

对后续无影响的指令,进行乱序执行,所以才会出现指令重排的现象。

1.4 happen before原则

    程序顺序原则: 一个线程内保证语义的串行性。

Volatile:volitile变量的写,发生在读之前,这就保证了volatile变量的可见性。

锁规则:解锁必然发生在随后的加锁之前。

传递:A先与B,B先于C,那么A必然先于C

线程:start方法先于每一个动作

线程操作先于线程终结Thread.join();

线程中断先于被中断的代码

对象的构造函数执行,结束先于finalize();

二 线程基础

2.1线程生命周期

如上图所示是线程的生命周期,线程start()是线程的启动状态,等到线程启动以后,这个时候线程处于运行状态,如果线程碰到了synchronied那么线就会处于Blocked状态,如果线程因为某种原因就会出现wait()状态进行永远的等待或者出现tme waitting状态进行限时等待,等待别人的线程唤醒以后又回到了runnable状态。

2.2创建线程的方式

public class Demo3 {
	public static void main(String[] args) {
		new ThreadD().start();
		new Thread(new ThreadC()).start();
	}
}

class ThreadD extends Thread{
   public void run(){
	   System.out.println("线程ThreadD-------");
   }
}

class ThreadC implements Runnable{
	@Override
	public void run() {
		System.out.println("线程ThreadC-------");
	}
}

2.3停止线程

public class ReadAndWriteThread {
	private static User u=new User();
	//读线程
	static class ReadThread extends Thread{
		public void run(){
			while(true){
				synchronized (u) {
					if(null!=u){
						if(u.getAge()!=null&&u.getName()!=null){
							if(!u.getAge().equals(u.getName())){
								System.out.println(u.toString());
							}
						}
					}
				}
				Thread.yield();
			}
		}
	}
	
	//写线程
	static class WriteThread extends Thread{
		public void run(){
			while(true){
				synchronized (u) {
					long time=System.currentTimeMillis()/1000;
					u.setName(time+"");
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					u.setAge(time+"");
				}
				Thread.yield();
			}
		}
	}
	
	static class User{
		private String name;
		private String age;
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public String getAge() {
			return age;
		}
		public void setAge(String age) {
			this.age = age;
		}
		@Override
		public String toString() {
			return "User [name=" + name + ", age=" + age + "]";
		}
	}
	
	public static void main(String[] args) {
		ReadThread readThread=new ReadThread();
		readThread.start();
		
		while(true){
			WriteThread writeThread = new WriteThread();
			writeThread.start();
		    try {
				Thread.sleep(150);
				writeThread.stop();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

如上代码所示,其实到jdk高版本以后,大家可以看到线程的stop方法是已经被禁止了的。这其实是一种不对的终止线程的方式。

如上代码所示终止线程,容易出现数据不一致的问题。那我们要如何去停止一个线程呢。

//写线程

static class WriteThread extends Thread{
		boolean isStopme=false;
		public void stopMe(boolean stopMe){
			this.isStopme=stopMe;
		}
		public void run(){
			while(true){
				if(isStopme){
					break;
				}
				synchronized (u) {
					long time=System.currentTimeMillis()/1000;
					u.setName(time+"");
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					u.setAge(time+"");
				}
				Thread.yield();
			}
		}
	}

如上代码所示,我们设置标志位,优雅的结束掉这个线程。

 

Thread.currentThread().isInterrupted()		判断当前线程是否被中断
Thread.interrupted();		判断当前线程是否被中断,并清除当前中断状态
threadInterrupted.interrupt();						  中断当前线程

public class ThreadInterrupted extends Thread{
	@Override
	public void run() {
		while(true){
			System.out.println(System.currentTimeMillis());
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				Thread.interrupted();
			}
			if(Thread.currentThread().isInterrupted()){
				break;
			}
		}
	}
	
	public static void main(String[] args) {
		ThreadInterrupted threadInterrupted=new ThreadInterrupted();
		threadInterrupted.start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		threadInterrupted.interrupt();
	}
}

 

2.4 wait()与notify()


 

public class ThreadInfo{
	public static void main(String[] args) {
		ExecutorService executorService=Executors.newCachedThreadPool();
		Object object=new Object();
		for(int i=0;i<10;i++){
			executorService.execute(new WaitThread(object));
		}
		try {
			Thread.sleep(1000);
			notifyThread notifyThread=new notifyThread(object);
			notifyThread.start();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

class WaitThread extends Thread{
	private Object obj=new Object();
	public WaitThread(Object ob) {
	    this.obj=ob;
	}
	@Override
	public void run() {
		synchronized (obj) {
			System.out.println("等待-------------"+Thread.currentThread().getName());
			try {
				obj.wait();//等待以后会释放锁
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("唤醒--------------"+Thread.currentThread().getName());
		}
	}
}

class notifyThread extends Thread{
	private Object obj=new Object();
	public notifyThread(Object ob) {
	    this.obj=ob;
	}
	@Override
	public void run() {
		while(true){
			synchronized (obj) {
				System.out.println("一次唤醒一个线程");
				obj.notify();
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
}

上图为如上代码所示的执行结果,可以看到wait()方法实际上在它等待的时候是释放锁的。也就是说其他的线程进来以后依然会被锁住。然而对于notify方法来说它一次却只能唤醒一个随机的线程。Wait(),notify()方法都是对象的方法。

如上图所示,wait与notify方法满足,如上等待队列所示的等待与唤醒机制。
2.5 wait()与notifyAll()

2.5 wait()与notifyAll()
public class ThreadInfo{
	public static void main(String[] args) {
		ExecutorService executorService=Executors.newCachedThreadPool();
		Object object=new Object();
		for(int i=0;i<10;i++){
			executorService.execute(new WaitThread(object));
		}
		NotifyAllThread notifyAllThread=new NotifyAllThread(object);
		notifyAllThread.start();
	}
}

class WaitThread extends Thread{
	private Object obj=new Object();
	public WaitThread(Object ob) {
	    this.obj=ob;
	}
	@Override
	public void run() {
		synchronized (obj) {
			System.out.println("等待-------------"+Thread.currentThread().getName());
			try {
				obj.wait();//等待以后会释放锁
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("唤醒--------------"+Thread.currentThread().getName());
		}
	}
}

class NotifyAllThread extends Thread{
	private Object obj=new Object();
	public NotifyAllThread(Object ob) {
	    this.obj=ob;
	}
	@Override
	public void run() {
		synchronized (obj) {
			System.out.println("一次唤醒所有线程");
			obj.notifyAll();
		}
	}
}

 

如上代码所示是线程通信的第二种方式,唤醒所有的线程。

如上图所示描述了这个等待与唤醒的过程。在这里要强调一下的是wait()与sleep()都是等待的方法,然而wait()是可以被唤醒的,并且唤醒以后还能获取到锁。但是sleep()是不能被唤醒的。

 

2.6 join()与yield()

Join() 线程等待

Join(long xx) 线程等待,指定时间

如上图所示两个方法,一个是让当前线程一直阻塞等待,直到目标线程执行完。另一个是让目标线程等待指定的秒数,目标线程再执行。

public class Thread_Join_demo7 extends Thread{
	private static AtomicInteger ato=new AtomicInteger();
	@Override
	public void run() {
		while(ato.get()<1000000){
			ato.incrementAndGet();
		}
	}
	public static void main(String[] args) throws InterruptedException {
		Thread_Join_demo7 threads=new Thread_Join_demo7();
		threads.start();
		//threads.join();
		System.out.println(ato.get());
	}
}

如上代码所示,原本我们期待的值是1000000但是由于在主线程中,程序是正常执行的,不会等待,所以这里可能输出的值是一个小于1000000的任意值,如果使用join()的话,就会让当前主线程等待其他线程执行完,以后再输出结果。就是1000000。如果我们打开注释以后,就能获取到我们预想的那个值。

 

 

Yield()线程让步

这是一个静态方法,一旦执行,它会使线程让出CPU.让出CPU以后,并不代表不在争夺CPU资源。是否能够再分配就不一定了。如果一个线程不是很重要,优先级相对比较低。又担心它占用大量CPU资源,就可以调用yield()方法来进行适当的线程让步。

2.7线程组

public class ThreadG {
	public static void main(String[] args) {
		ThreadGroup threadGroup=new ThreadGroup("ftpServer");
		Thread th=new Thread(threadGroup,new Thread1(),"线程一");
		Thread th1=new Thread(threadGroup,new Thread2(),"线程二");
		th.start();
		th1.start();
		System.out.println("xxx==="+threadGroup.activeCount());
		threadGroup.list();
	}
}
class Thread1 implements Runnable{
	@Override
	public void run() {
		while(true){}
	}
}
class Thread2 implements Runnable{
	@Override
	public void run() {
		while(true){}
	}
}

 

如上所示是线程组的概念,如上所示

执行以后得到如上图所示的结果。这个是在告诉我们通过线程组可以

2.8线程优先级

public class ThreadPropriorty {
    public static void main(String[] args) {
		HighThread highThread=new HighThread();
		MidieThread midieThread=new MidieThread();
		LowThread lowThread=new LowThread();
		lowThread.setPriority(Thread.MAX_PRIORITY);
		midieThread.setPriority(Thread.NORM_PRIORITY);
		highThread.setPriority(Thread.MIN_PRIORITY);
		lowThread.start();
		midieThread.start();
		highThread.start();
	}
}
class HighThread extends Thread{
	public void run(){
			for(int i=0;i<1000;i++){
	System.out.println("HighThread"+Thread.currentThread().getName());
			}
	}
}

class MidieThread extends Thread{
	public void run(){
			for(int i=0;i<1000;i++){
		System.out.println("MidieThread"+Thread.currentThread().getName());
			}
	}
}
class LowThread extends Thread{
	public void run(){
			for(int i=0;i<1000;i++){
				System.out.println("LowThread"+Thread.currentThread().getName());
			}
	}
}

如上面的代码所示,会产生这样的现象,线程优先级低的线程总是在最后执行完成的。这也证实了一点,线程的优先级决定了这个线程拿到CPU的概率。虽然线程命名的时候已经命名好了高低优先级,然而最终还是由设置的优先级来决定线程的情况。

 

 

 

猜你喜欢

转载自blog.csdn.net/worn_xiao/article/details/82689005