线程基础知识

Posted 听风者-better

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程基础知识相关的知识,希望对你有一定的参考价值。

线程基础知识

1.并发和并行

  • 并发是没有时间上的重叠的,两个任务是交替执行的,由于切换的非常快,对于外界调用者来说相当于同一时刻多个任务一起执行了;
  • 并行可以看到时间上是由重叠的,也就是说并行才是真正意义上的同一时刻可以有多个任务同时执行。

2.线程的状态

线程一般分为:新建,就绪,运行,阻塞,死亡五中状态

当创建一个线程后,并没有运行,处于新建状态,需要通过调用start方法,让线程处于就绪状态,但是否运行取决于cpu分配的执行机会,当得到cpu的执行机会后马上运行,一个正在执行的线程可以通过很多方式进入阻塞状态,当执行完所有操作后就进入死亡状态。

3.线程的实现方式

1.继承Thread类

2.实现Runnable接口,重写run方法

3.实现Callable接口,需要重写call()方法
(Callable是一种具有类型参数的泛型,它的类型参数表的是从方法call()中返回的值,并且必须使用ExecutorServices.submit()方法调用它)

package thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Test 
	public static void main(String[] args) 
		MyThread thread1 = new MyThread();
		Thread thread2 = new Thread(new MyRunable());
		ExecutorService threadPool = Executors.newSingleThreadExecutor();
		Future<String> future = threadPool.submit(new MyCallable());
		thread1.start();
		thread2.start();
		try 
			//获取返回值
			System.out.println(future.get());
		 catch (InterruptedException | ExecutionException e) 
			e.printStackTrace();
		 finally 
			threadPool.shutdown();
		
	


class MyThread extends Thread 
	@Override
	public void run() 
		System.out.println("MyThread"+ Thread.currentThread().getName());
	


class MyRunable implements Runnable 

	@Override
	public void run() 
		System.out.println("MyRunable"+ Thread.currentThread().getName());
	



class MyCallable implements Callable<String> 

	@Override
	public String call() throws Exception 
		System.out.println("MyCallable 线程:" + Thread.currentThread().getName());
		return "MyCallable";
	


运行结果:

4.什么是线程同步,什么是线程安全

同步:当两个或两个以上的线程需要共享资源,通过同步方法限制资源在一次仅被一个线程占用

线程安全:线程安全就是多线程操作同一个对象不会产生数据污染,

线程同步一般来保护线程安全,final修饰的也是线程安全

5.什么是死锁,死锁发生的几个条件是什么

死锁就是当有两个或两个以上的线程都获得对方的资源,但彼此都不肯放开,处于僵持阶段,此时就造成了死锁

条件:两个或两个以上的线程,同时想要获得对方的资源,彼此又不肯放开

6.什么是同步和异步,分别用例子说明,同步有几种方式?

同步是排队去做事情,异步就是各做各的

7.原子性、可见性、有序性

事物有原子性,这个概念大概都清楚,即一个操作或多个操作要么执行的过程中不被任何因素打断,要么不执行。

可见性指当一个线程修改了共享变量的值,其它线程能够立即得知这个修改。Java 内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值来实现可见性的。

主要有有三种实现可见性的方式:

volatile

synchronized,对一个变量执行 unlock 操作之前,必须把变量值同步回主内存。

final,被 final 关键字修饰的字段在构造器中一旦初始化完成,并且没有发生 this 逃逸(其它线程通过 this 引用访问到初始化了一半的对象),那么其它线程就能看见 final 字段的值。

有序性指在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排序。在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

8.volatile关键字

1、volatile可以使变量在多个线程间可见(可见性)。

2、不能保证操作的原子性。

3、volatile 关键字通过添加内存屏障的方式来禁止指令重排,即重排序时不能把后面的指令放到内存屏障之前,实现有序性

9.synchronized和lock

Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。

synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized 可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。

lock(显示锁):需要显示指定起始位置和终止位置。一般使用 ReentrantLock 类做为锁,多个线程中必须要使用一个 ReentrantLock 类做为对象才能保证锁的生效。且在加锁和解锁处需要通过 lock() 和 unlock() 显示指出。所以一般会在 finally 块中写 unlock() 以防死锁。

synchronized 原始采用的是 CPU 悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。

Lock 用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是 CAS 操作。

10.死锁

第一种synchronized方式死锁:

线程thread1先获取锁locka,然后在同步块里嵌套竞争锁lockb。而线程thread2先获取锁lockb,然后在同步块里嵌套竞争锁locka(此时已经被线程thread1拥有,而thread1在等待lockb,而lockb被thread2拥有,thread2在等待locka……无线循环)。

public class SyncDeadLock 
	private static Object locka = new Object();
	private static Object lockb = new Object();

	public static void main(String[] args) 
		new SyncDeadLock().deadLock();
	

	private void deadLock() 
		Thread thread1 = new Thread(new Runnable() 
			@Override
			public void run() 
				synchronized (locka) 
					try 
						System.out.println(Thread.currentThread().getName() + " get locka ing!");
						Thread.sleep(500);
						System.out.println(Thread.currentThread().getName() + " after sleep 500ms!");
					 catch (Exception e) 
						e.printStackTrace();
					
					System.out.println(Thread.currentThread().getName() + " need lockb!Just waiting!");
					synchronized (lockb) 
						System.out.println(Thread.currentThread().getName() + "get lockb ing!");
					
				
			
		);

		Thread thread2 = new Thread(new Runnable() 
			@Override
			public void run() 
				synchronized (lockb) 
					try 
						System.out.println(Thread.currentThread().getName() + " get lockb ing!");
						Thread.sleep(500);
						System.out.println(Thread.currentThread().getName() + " after sleep 500ms!");
					 catch (Exception e) 
						e.printStackTrace();
					
					System.out.println(Thread.currentThread().getName() + " need locka!Just waiting!");
					synchronized (locka) 
						System.out.println(Thread.currentThread().getName() + "get locka ing!");
					
				
			
		);

		thread1.start();
		thread2.start();
	


运行结果:

第二种concurrent包Lock错误使用,导致死锁:

lock.unlock();释放锁使用地方不规范,导致死锁不能正常释放

建议在finally块里去释放锁

11.乐观锁和悲观锁

悲观锁:假定会发生并发冲突,独占锁,屏蔽一切可能违反数据完整性的操作。

乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。乐观锁不能解决脏读的问题。

12.对象锁、类锁

1.一个锁的是类对象,一个锁的是实例对象

2.若类对象被lock,则类对象的所有同步方法全被lock

3.若实例对象被lock,则该实例对象的所有同步方法全被lock

参考资料:Synchronized(对象锁)和Static Synchronized(类锁)的区别

13.sleep和wait有什么区别
1.sleep 和 wait
①wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
②wait() 会释放锁,sleep() 不会。

2.有什么区别
①sleep() 方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态)。②wait() 是 Object 类的方法,调用对象的 wait() 方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的 notify() 方法(或 notifyAll() 方法)时才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。

14.线程间是怎么通信的,通过调用几个方法来交互的

join()

在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。

对于以下代码,虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出。

public class JoinExample 
	private class A extends Thread 
		@Override
		public void run() 
			System.out.println("A");
		
	

	private class B extends Thread 
		private A a;

		B(A a) 
			this.a = a;
		

		@Override
		public void run() 
			try 
				a.join();
			 catch (InterruptedException e) 
				e.printStackTrace();
			
			System.out.println("B");
		
	

	public void test() 
		A a = new A();
		B b = new B(a);
		b.start();
		a.start();
	

	public static void main(String[] args) 
		JoinExample example = new JoinExample();
		example.test();
	

运行结果:

wait() notify() notifyAll()

调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify()(随机叫醒一个) 或者 notifyAll() (叫醒所有 wait 线程,争夺时间片的线程只有一个)来唤醒挂起的线程。

它们都属于 Object 的一部分,而不属于 Thread。

只能用在同步方法或者同步控制块中使用! 否则会在运行时抛出 IllegalMonitorStateExeception。

使用 wait() 挂起期间,线程会释放锁。这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,造成死锁。

public class WaitNotifyExample 
	public synchronized void before() 
		System.out.println("before");
		notifyAll();
	

	public synchronized void after() 
		try 
			wait();
		 catch (InterruptedException e) 
			e.printStackTrace();
		
		System.out.println("after");
	

	public static void main(String[] args) 
		ExecutorService executorService = Executors.newCachedThreadPool();
		WaitNotifyExample example = new WaitNotifyExample();
		executorService.execute(() -> example.after());
		executorService.execute(() -> example.before());
	

15.线程池

线程是稀缺资源,如果被无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,合理的使用线程池对线程进行统一分配、调优和监控,有以下好处:

1.降低资源消耗;
2.提高响应速度;
3.提高线程的可管理性。

以上是关于线程基础知识的主要内容,如果未能解决你的问题,请参考以下文章

线程知识点

零基础学Java—多线程(四十九)

线程和并发--基础

c#基础并行Linq

Java基础-多线程篇

多线程