Java线程创建、三大特性

Java线程创建、三大特性,第1张

Java线程创建、三大特性

文章目录

线程的概念进程的概念线程和进程的比较线程的生命周期创建线程的三种方式

第一种、继承Thread类第二种、实现Runnable接口第三种、实现Callable接口 创建线程对象的3种方法线程控制阻塞的几种方式

sleep()方法join()方法yield()方法守护线程 线程的三大特性

原子性

CAS 可见性

volatile关键字 有序性

线程的概念

1.线程是程序执行的最小单位,而进程是 *** 作系统分配资源的最小单位;
2. 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
3.进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)
及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
4.调度和切换:线程上下文切换比进程上下文切换要快得多


进程的概念


线程和进程的比较

线程的生命周期

创建线程的三种方式 第一种、继承Thread类

创建线程的第一种方法。继承java.lang包的Thread类。

Thread类也是通过Runnable接口来创建线程对象。本质上Thread是Runnable的一个包装类。

package com.wdy.thread;

public class ThreadDemo01 extends Thread {  //继承了Thread类。产生的对象是线程对象
    private String name;//线程名称
	   
	public ThreadDemo01(String name) {
		this.name=name;
	}
	
	@Override
    public void run() {  //覆盖run方法
    	for(int i=1;i<=10;i++) {
    		System.out.println(this.name+"-"+i);
    		try {
				sleep(200);  //线程阻塞
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    	}
    }
		
	public static void main(String[] args) {
		Thread t1=new ThreadDemo01("A"); //创建3个线程对象
		Thread t2=new ThreadDemo01("B");
		Thread t3=new ThreadDemo01("C");
		
		t1.start(); //线程对象就绪
		t2.start();
		t3.start();	
	}
}

执行结果:从结果可以看出多个线程是交错执行的。

第二种、实现Runnable接口

因为java是单继承,继承了Thread类,就不能继承其它类。所以第二种方法用Runnable接口来实现,在实现Runnable接口的同时还能继承一个类,在实际项目中运用较多。

package com.wdy.thread;
	
	public class ThreadDemo02 implements Runnable {  //继承了Thread类。产生的对象是线程对象
	    private String name;//线程名称
			    
		public ThreadDemo02(String name) {
			this.name=name;
		}
		
		@Override
	    public void run() {  //覆盖run方法
	    	for(int i=1;i<=10;i++) {
	    		System.out.println(this.name+"-"+i);
	    		try {
					Thread.sleep(200); //阻塞
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
	    	}
	    }
				
		public static void main(String[] args) {
			Thread t1=new Thread(new ThreadDemo02("A")); // 创建3个线程对象 Thread.Runnable()方法
			Thread t2=new Thread(new ThreadDemo02("B")); // 创建3个线程对象
			Thread t3=new Thread(new ThreadDemo02("C")); // 创建3个线程对象
				
			t1.start(); //线程对象就绪
			t2.start();
			t3.start();		
		}	
}

执行结果:

第三种、实现Callable接口

唯一一种有返回值的。
在第三种方法。通过Callable接口创建线程对象使用的也是Runnable接口的实现类FutureTask()。

package com.wdy.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadDemo03 implements Callable {
	@Override
	public Integer call() throws Exception {
        int s=0;
        for(int i=1;i<=10000;i++) {
        	s+=i;
        }
		return s;
	}
	
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		FutureTask ft=new FutureTask(new ThreadDemo03());
	    ft.run();
	    System.out.println("结果为:"+ft.get());
	}
}

执行结果:

创建线程对象的3种方法

通过类来创建线程对象

通过匿名内部类

通过函数式编程
Runnable接口实现了FunctionalInterface接口(函数接口)

package com.wdy.thread;
	public class ThreadDemo02 implements Runnable {  //继承了Thread类。产生的对象是线程对象
	    private String name;//线程名称
		
	    
		public ThreadDemo02(String name) {
			this.name=name;
		}
		
		@Override
	    public void run() {  //覆盖run方法
	    	for(int i=1;i<=10;i++) {
	    		System.out.println(this.name+"-"+i);
	    		try {
					Thread.sleep(200); //阻塞
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
	    	}
	    }
		
	     	
		public static void main(String[] args) {
//			Thread t1=new Thread(new ThreadDemo02("A")); // 创建3个线程对象 Thread.Runnable()方法
//			Thread t2=new Thread(new ThreadDemo02("B")); // 创建3个线程对象
//			Thread t3=new Thread(new ThreadDemo02("C")); // 创建3个线程对象
//				
//			t1.start(); //线程对象就绪
//			t2.start();
//			t3.start();		
//		
		
		 	
		 new Thread(new Runnable() {
			
			@Override
			public void run() {
				for(int i=1;i<=10;i++) {
		    		System.out.println(Thread.currentThread().getName()+"-"+i);
		    		try {
						Thread.sleep(200); //阻塞
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
		    	}
				
			}
		});
		
//			
//			new Thread(()->{
//				
//				for(int i=1;i<=10;i++) {
//		    		System.out.println(Thread.currentThread().getName()+"-"+i);
//		    		try {
//						Thread.sleep(200); //阻塞
//					} catch (InterruptedException e) {
//						// TODO Auto-generated catch block
//						e.printStackTrace();
//					}
//		    	}
//						
//			}).start();;	
		}
}

线程控制阻塞的几种方式 sleep()方法

sleep()实现线程阻塞的方法,我们称之为“线程睡眠”,方式是超时等待,就是sleep()通过传入“睡眠时间”作为方法的参数,时间一到就从“睡眠”中“醒来”;

join()方法

在t1.start()之后,对t1线程实现join() ,阻塞其它线程,控制t1线程优先执行完毕。

package com.wdy.thread;

public class ThreadBlockDemo01 extends Thread{
    private String name;//线程名称
	
	public ThreadBlockDemo01(String name) {
		this.name=name;
	}
	
	public void run() {  //覆盖run方法
    	for(int i=1;i<=10;i++) {
    		System.out.println(this.name+":"+i);
    		try {
				sleep(200); //阻塞
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    	}
    }
	
	public static void main(String[] args) throws InterruptedException {
		Thread t1=new Thread(new ThreadDemo02("A")); // 创建3个线程对象 Thread.Runnable()方法
		Thread t2=new Thread(new ThreadDemo02("B")); // 创建3个线程对象
		Thread t3=new Thread(new ThreadDemo02("C")); // 创建3个线程对象
			
		t1.start(); //线程对象就绪
		t1.join();  //阻塞线程A,让A线程优先执行
		t2.start();
//		t2.join();//阻塞线程B,让B线程优先执行
		t3.start();	
		
}
}

同理,在t2.start()之后,再加一个join() ,控制t2线程优先执行。执行结果如下:

yield()方法
package com.wdy.thread;

public class ThreadBlockDemo3 extends Thread{
    private String name;//线程名称
	
	public ThreadBlockDemo3(String name) {
		this.name=name;
	}
	
	public void run() {  //覆盖run方法
    	for(int i=1;i<=10;i++) {
    		if(i%2==0) {//当前线程为偶数,暂时让出cpu运行时间片
    			Thread.yield();
    		}
    		System.out.println(this.name+":"+i);
    		try {
				sleep(200); //阻塞
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    	}
    }	

	public static void main(String[] args) throws InterruptedException {
		Thread t1=new Thread(new ThreadDemo02("A")); // 创建3个线程对象 Thread.Runnable()方法
		Thread t2=new Thread(new ThreadDemo02("B")); // 创建3个线程对象
		Thread t3=new Thread(new ThreadDemo02("C")); // 创建3个线程对象
			
		t1.start(); //线程对象就绪	
		t2.start();
		t3.start();		
}
}
守护线程
package com.wdy.thread;

public class Demo2 extends Thread {
    @Override
    public void run() {
    	while(true) {
    		System.out.println("线程运行中");
    		try {
				sleep(200);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    	}
    }
        
    public static void main(String[] args) throws InterruptedException {
		Thread t=new Demo2();//主线程
		t.setDaemon(true);//守护线程:主线程结束,其它线程也结束
		t.start();
		
    	for(int i=0;i<10;i++) { 
			System.out.println(i);
			Thread.sleep(200);
		}
	}
}

不加守护线程,会陷入死循环。结果如下

加入守护线程的运行结果如下
t.setDaemon(true);主线程终止之后,其余线程也会被终止。

线程的三大特性 原子性

即一个 *** 作或多个 *** 作要么全部执行并且执行过程中不被任何因素打断,要么就不执行。

package com.wdy.thread;

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadAtomicDemo {
   
	public static int i=0;

	public static class AtomicThread extends Thread{ //静态内部类继承Thread
		@Override
		public void run() {
			while(true) {
				i++;//i++,i--,++i,--i 不是原子性 *** 作。
				//比如i++ 先赋值,后自增两个步骤,中间有间隔。这个两个步骤可能出现在两个线程中。因此结果中出现了两个连续重复的i
				//因此这样的 *** 作是线程不安全的。
						
				System.out.println(this.getName()+"::"+i);
				try {
					sleep(200);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
	
	public static void main(String[] args) {
		Thread t1=new AtomicThread();
		t1.setName("A");
		Thread t2=new AtomicThread();
		t2.setName("B");
		Thread t3=new AtomicThread();	
		t3.setName("C");
		
		t1.start();
		t2.start();
		t3.start();	
	}
}

i++,i–,++i,–i 不是原子性 *** 作。比如i++ 先赋值,后自增两个步骤,中间有间隔。这个两个步骤可能出现在两个线程中。因此结果中出现了两个连续重复的i。因此这样的 *** 作是线程不安全的。执行结果:

使用AtomicInteger类创建原子性的对象,i.incrementAndGet()方法进行自增,保证原子性,线程安全。

CAS

通过不断检查线程中的值是否被改变过,这个过程叫自旋。自旋就是CAS的一个 *** 作周期。如果值已经改变,那么就等下下一个自增值再更新值。模式图如下:

ABA问题:
因为CAS需要在 *** 作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A, 变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。这样就线程不安全了。

0–>0(v1.1)–>0+1–>是否为0–>1(v1.1) 版本控制

当出现了ABA问题,CAS通过定义版本号的方法来确认数据是否已经被改动。底层代码如下。

CAS存在的一些其它问题:
1.自旋如果长时间不成功,会给CPU带来非常大的执行开销。
2.只能保证一个共享变量的原子 *** 作。
当对一个共享变量执行 *** 作时,我们可以使用循环CAS的方式来保证原子 *** 作,但是对多个共享变量 *** 作时,循环CAS就无法保证 *** 作的原子性。


package com.wdy.thread;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadAtomicDemo {
   
	public static int i=0;

	public final static AtomicInteger i=new AtomicInteger(0);
	
	public static class AtomicThread extends Thread{ //静态内部类继承Thread
		@Override
		public void run() {
			while(true) {
			i++;
		System.out.println(this.getName()+"::"+i.incrementAndGet());
				try {
					sleep(200);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
	
	public static void main(String[] args) {
		Thread t1=new AtomicThread();
		t1.setName("A");
		Thread t2=new AtomicThread();
		t2.setName("B");
		Thread t3=new AtomicThread();	
		t3.setName("C");
		
		t1.start();
		t2.start();
		t3.start();	
	}
}

执行结果如下:

可见性

当多个线程同时访问一个变量时,一个线程修改了这个变量的值,其它线程能立即看得到它修改的值。

volatile关键字

volatile关键字就是用于保证内存可见性,当线程A更新了volatile修饰的变量时,它会立即刷新到主内存中,并且将其余缓存中该变量的值清空,导
致其余线程只能去主内存读取最新值。

使用volatile关键词修饰的变量每次读取都会得到最新的数据,不管哪个线程对这个变量的修改都会立即刷新到主内存。

package com.wdy.thread;
public class ViasbleDemo01 {
     public static volatile boolean f=false;
   
     public static class A extends Thread{
    	 @Override
    	public void run() {  //如果f=true,执行循环
    		while(true) {
    			if(f) {
    				System.out.println(getName()+"运行:"+f);
    			break;
    			}
    		}
			}   	
     }
     
     public static class B extends Thread{
    	 @Override
    	public void run() {
    		try {
				sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
    		f=true;
    		System.out.println(getName()+"运行"+f);
    	}
     }
     
     public static void main(String[] args) {
		Thread t1=new A();
		t1.setName("A");
		Thread t2=new B();
		t2.setName("B");
		
		t1.start();
		t2.start();	
	}
}

初始状态,f 变量没有加volatile(可见性)。A线程f=false,不执行。B线程执行结果:f=true。
此时f变为true。那么A线程应该也会打印,但从结果来看,并没有执行A。这是因为线程之间是不可见的。虽然B线程把f变为true,但对A线程来说是不可见的。


那么如果要让B线程可见,只需在全局变量 f 前面加上 volatile(可见性)。此时,B线程结果可见。执行结果如下:

A、B线程打印顺序和sleep时间有关。把A的sleep改成10,B线程睡眠时间足够短,那就是B先打印。结果如下:

有序性

在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

在多个线程运行时,不要调整内部指令执行的顺序。

package com.wdy.thread;

public class OrderThreadDemo {	
	private static volatile OrderThreadDemo instance = null;
    //添加volatile的作用;解决可变性,保证应用的有序性(按第一种方式),
	private OrderThreadDemo() {}
	
	public static OrderThreadDemo getInstance() {
	    
		
	if(instance==null) { //判断OrderThreadDemo是否存在
		instance=new OrderThreadDemo();
	}
	return instance;
	}
}

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5713236.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-18
下一篇 2022-12-18

发表评论

登录后才能评论

评论列表(0条)

保存