Java多线程以及线程安全,通俗易懂

线程 | ProcessOn991 | 思维导图(新) | ProcessOn

线程 | ProcessOn991 | 思维导图(新) | ProcessOn

一.实现线程的几种方式

        1.继承Thread类创建线程 

        2.实现Runnable接口创建线程

        3.Callable,Future,可以有返回结果的线程

        4.ThreadPoolExecutor线程池类

//继承Thread 
public class MyThread extends Thread {  
  public void run() {  
   System.out.println("MyThread.run()");  
  }  
}   
MyThread myThread1 = new MyThread();  
MyThread myThread2 = new MyThread();  
myThread1.start();  
myThread2.start();




//2.实现Runnable 
public class MyThread extends OtherClass implements Runnable {  
  public void run() {  
   System.out.println("MyThread.run()");  
  }  
}
MyThread myThread = new MyThread();  
Thread thread = new Thread(myThread);  
thread.start(); 

/**事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码
public void run() {  
  if (target != null) {  
   target.run();  
  }  
}  
*/

二.线程的生命周期

 线程的生命周期分为5个部分:分别是新建状态、就绪状态、运行状态,阻塞状态、死亡状态。

  1. 新建状态(new):指新建了一个线程对象。Thread    t1 =new Thread();这里就新建了一个Thread类的线程对象。
  2. 就绪状态(Runnable):当线程对象创建后,该线程对象自身或者其他对象调用了该对象的start()方法。该线程就位于了可运行池中,变的可运行,等待获取cpu的使用权。因为在同一时间里cpu只能执行某一个线程。
  3. 运行状态(Running): 当就绪状态的线程获取了cpu的时间片或者说获取了cpu的执行时间,这时就会调用该线程对象的run()方法,然后就从就绪状态就入了运行状态。
  4. 阻塞状态(Blocked):阻塞状态就是线程因为某种原因暂时放弃了对cpu的使用权,暂时停止运行。直到线程再次进入就绪状态,才有机会转到运行状态。阻塞状态分为三种情况:
    1. 等待阻塞:运行状态的线程调用了wait()方法后,该线程会释放它所持有的锁,然后被jvm放入到等待池中,只有等其他线程调用Object类的notify()方法或者norifyAll()方法时,才能进入重新进入到就绪状态。
    2. 同步阻塞:运行的线程在获取对象的(synchronized)同步锁时,若该同步锁被别的线程占用,JVM就会把该线程设置为阻塞状态,一直到线程获取到同步锁,才能转入就绪状态。
    3. 其它阻塞:运行的线程在执行sleep()或者join()方法时,或者发出了I/O请求,JVM就会把该线程设置为阻塞状态,当sleep()状态超时、join()等待等待线程终止或者超时、或者I/O处理完毕时,线程重进转入到就绪状态。在这需要注意的是sleep()方法和wait()不同,sleep不会释放自身所持有的锁。

  5. 死亡状态(Dead):当线程执行完了或者因异常退出了run()的执行,该线程的生命周期就结束了。

三.什么是线程安全

总结:当一个全局变量/对象/方法同时被多个线程执行写的操作,值存存在不确定性。

线程安全是通过线程同步控制来实现的,也就是synchronized关键字。

1.为什么会有线程安全问题

        多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。

2.引起线程安全的原因有哪些

​         线程安全问题大多是由全局变量及静态变量引起的,局部变量逃逸也可能导致线程安全问题。 若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。 ​

线程安全的类        Vector HashTable StringBuffer

非线程安全的类        ArrayList HashMap StringBuilder

四.如何处理线程安全问题

通过synchronized,lock加锁来实现。

为什么是通过加锁来实现? 

        锁的意义在于使高并发/多线程,由原来的并行,变为串行(单线程),达到对变量的唯一安全控制、

        锁只有一把,谁先获取,谁就拥有执行权,其他线程则阻塞

1.volatile关键字

        volatile 关键字修饰共享变量,一个线程修改,其他线程立即可见;

2.如何控制某个方法允许并发访问线程的个数

        使用Semaphore控制, Semaphore mSemaphore = new Semaphore(5);表示生成五个信号量的实例,保证只有5个线程可以同时执行;

五.线程池

  1. 所谓线程池本质是一个hashSet。多余的任务会放在阻塞队列中。
  2. 只有当阻塞队列满了后,才会触发非核心线程的创建。所以非核心线程只是临时过来打杂的。直到空闲了,然后自己关闭了。
  3. 线程池提供了两个钩子(beforeExecute,afterExecute)给我们,我们继承线程池,在执行任务前后做一些事情。
  4. 线程池原理关键技术:锁(lock,cas)、阻塞队列、hashSet(资源池)

package com.Thread;

import java.util.*;
import java.util.concurrent.*;

public class MyThread extends Thread{

	@Override 
    //重写run方法
    public void run() {
        // TODO Auto-generated method stub
		System.out.println(1);
    }
	
	public static void startThread1(){
		MyThread myThread = new MyThread();
		myThread.start();
		int taskSize = 5; 
        ExecutorService pool = Executors.newFixedThreadPool(taskSize);
        // 创建多个有返回值的任务  
        for (int i = 0; i < taskSize; i++) {  
        	pool.execute(new MyThread());
        }
        // 关闭线程池  
        pool.shutdown();  
		
	}
	public static void startThread2() throws InterruptedException, ExecutionException{
		Date date1 = new Date();  
		int taskSize = 5; 
        ExecutorService pool = Executors.newFixedThreadPool(taskSize);
        List<Future> list = new ArrayList<Future>();  
        for (int i = 0; i < taskSize; i++) {  
        	
         Callable c = new MyCallable(i + " ");  
         // 执行任务并获取Future对象  
         Future f = pool.submit(c);  
         // System.out.println(">>>" + f.get().toString());  
         list.add(f);  
        }  
        // 关闭线程池  
        pool.shutdown();  
       
        // 获取所有并发任务的运行结果  
        for (Future f : list) {  
         // 从Future对象上获取任务的返回值,并输出到控制台  
         System.out.println(">>>" + f.get().toString());  
        }
	}
	
	/**
	 * @param args
	 * @throws ExecutionException 
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException, ExecutionException  {
		// TODO Auto-generated method stub
		startThread1();
		//startThread2();
	}

}

//============================
package com.Thread;

import java.util.Date;
import java.util.concurrent.Callable;

public class MyCallable implements Callable<Object> {
	
	private String taskNum;  
	  
	MyCallable(String taskNum) {  
	   this.taskNum = taskNum;  
	}  
	  
	public Object call() throws Exception {  
	   System.out.println(">>>" + taskNum + "任务启动");  
	   Date dateTmp1 = new Date();  
	   Thread.sleep(1000);  
	   Date dateTmp2 = new Date();  
	   long time = dateTmp2.getTime() - dateTmp1.getTime();  
	   System.out.println(">>>" + taskNum + "任务终止");  
	   return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";  
	}  
}

六.多线程

        当线程执行缓慢,可以采用多线程,来节省时间,可以采用线程池创建。

七.异步线程

        当主线程中存在影响执行效率的代码时,考虑采用异步,如记录日志,发送邮件等。

        spring提供了一个@Asyn注解,来实现异步线程。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值