JAVA岗面试之多线程(一)

1.线程安全三大特性

多线程的三大特性:原子性、可见性、有序性。

原子性:表示组成一个事务的多个操作是一个不可分割的原子单元。多个操作要么全都执行,要么全都不执行。
可见性:当多个线程共享同一个变量时,其中一个线程修改了变量,其他的线程必须立即得知并获取了最新的该变量的值。
有序性:程序执行的顺序,是按照代码的顺序依次执行的,就被成为有序。

2.线程创建方式

  • 继承Thread类,重新run方法,创建Thread子类对象,调用start方法。
public class MyThread {
	public static void main(String[] args) {
		// 创建Thread的子类对象,创建线程。
		ThreadDemo  d1 = new ThreadDemo("线程1");
		ThreadDemo d2 = new ThreadDemo("线程2");
		d1.start(); // 开启线程
		d2.start();
	}
}
 
// 定义一个类继承Thread
class ThreadDemo extends Thread {
 
	private String name;
	ThreadDemo (String name) {
		this.name = name;
	}
 
	@Override
	public void run() {
		//业务代码
	}
}
  • 实现Runnable接口,子类中重写run方法,创建实现子类的对象,通过Thread类创建线程对象,并将该子类对象作为构造器的参数进行传递,调用start方法。
//1.实现Runnable接口
class RunnableDemo implements Runnable {
 
	// 2、子类中重写run方法:将线程的任务代码封装到run方法中;
	@Override
	public void run() {
		//业务代码
	}
}

public class MyRunnable {
 
	public static void main(String[] args) {
 
		// 3.创建实现类对象
		RunnableDemo d = new RunnableDemo();
		// 4.通过Thread类创建线程对象
		Thread t1 = new Thread(d);
		// 5. 调用start方法
		t1.start();
	}
}

  • 实现Callable接口:创建Callable的实现类,重写call方法,将线程的任务代码封装到call方法中,创建Callable接口实现子类的对象;创建FutureTask的对象,将此Callable接口实现类的对象作为构造器的参数进行传递,创建Thread对象,并调用start()。将FutureTask的对象作为参数传递到Thread类的构造器中,获取Callable中call方法的返回值。 get() 返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
public class MyCallable {
 
	public static void main(String[] args) {
		// 3、创建Callable接口实现子类的对象
		CallableDemo d = new CallableDemo ();
		// 4、创建FutureTask的对象,将此Callable接口实现类的对象作为构造器的参数进行传递
		FutureTask futureTask = new FutureTask(d);
		// 5、创建Thread对象
		Thread thread = new Thread(futureTask);
		thread.start();
		Object sum;
		try {
			//6、获取Callable中call方法的返回值
			sum = futureTask.get();
			System.out.println("总和是:" + sum);
		}
		catch (InterruptedException e) {
			e.printStackTrace();
		}
		catch (ExecutionException e) {
			e.printStackTrace();
		}
	}
 
}
 
//1、创建Callable的实现类
class CallableDemo implements Callable {
 
	//2、重写call方法,将线程的任务代码封装到call方法中
	@Override
	public Object call() throws Exception {
		// 遍历1-20打印出偶数之和
		int sum = 0;
		for (int i = 1; i <= 20; i++) {
			if ((i & 1) == 0) { // i%2 == 0
				System.out.println(i);
				sum += i;
			}
		}
		return sum;
	}
 
}
  • 使用线程池:提供指定线程数量的线程池,执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象,关闭连接池。
public class MyExecutors {
 
	public static void main(String[] args) {
		//1、提供指定线程数量的线程池
		ExecutorService executors = Executors.newFixedThreadPool(10);
		//2、执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
		Future future = executors.submit(new ExecutorDemo ());// 适用于Callable
		try {
            Thread.sleep(5000);// 先延迟5秒
            System.out.println("输出结果"+future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }finally {
        	 //3. 关闭连接池
        	executors.shutdown();
		}
	}
}
 
class ExecutorDemo implements Callable {
	// 2. 重写call方法,将此线程需要执行的操作声明在call中
	@Override
	public Object call() throws Exception {
		// 遍历1-20打印出奇数之和
		int sum = 0;
		for (int i = 1; i <= 20; i++) {
			if ((i & 1) != 0) {
				System.out.println(i +"   "+ Thread.currentThread().getName());
				sum += i;
			}
		}
		return sum;
	}
}

start()负责启动线程,什么时候执行由CPU时间片分配调度,run()是线程具体执行的方法。

3.ThreadLocal

  • ThreadLocal提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程。
  • ThreadLocal的设计是:每个Thread维护一个ThreadLocalMap哈希表,这个哈希表的key是ThreadLocal实例本身,value才是真正要存储的值。
  • 对ThreadLocal的常用操作实际是对线程Thread中ThreadLocalMap进行操作。
  • ThreadLocal的缺点有:
    1 父子线程的不可继承性;
    2 不能实现不同子线程之间的数据共享;
    3 脏读数据
    4 内存溢出(较常出现)

4.Synchronized

synchronized是一个Java关键字,是jvm层级的,提供了一种排他机制,在同一时间点只能有一个线程执行某些操作。

synchronized关键字可以实现一个简单的策略来防止线程干扰内存一致性错误,如果一个对象对多个线程是可见的,那么该对象的所有读或者写都将通过同步的方式来进行。

在这里插入图片描述

  • 当synchronized作用在实例方法时,相当于对当前实例加锁 ,进入同步代码前要获取到该实例的锁;

  • 当synchronized作用在静态方法时,相当于给当前类加锁,进入同步代码之前要获取当前类的锁,因为Class数据存在于永久代,因此静态方法锁相当于该类的一个全局锁;

  • 当synchronized作用在某一个对象实例时,给指定对象加锁,进入同步代码之前要获取到该对象的锁;

5.Volatile

volatile关键字修饰的共享变量在发生变化时,其他线程会立刻觉察到,其他线程从主存中取得更后的值。不会加锁,不会阻塞代码。以轻量级的方式实现同步。

  • Volatile只能保证内存可见性有序性(指令有序)
  • 使用volatile关键字时,会多出一个lock前缀指令,确保指令重排序时不会把其后面的指令重排到内存屏障之前的位置,也不会把前面的指令排到内存屏障后面,即在执行到内存屏障这句指令时,前面的操作已经全部完成;
  • volatile是变量修饰符,仅能用于变量
  • volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化(如编译器重排序的优化)
  • Volatile不需要加锁,比Synchronized更轻量级,并不会阻塞线程(volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  • 而对n=n+1,n++等操作时,volatile关键字将失效,不能起到像synchronized一样的线程同步(原子性)的效果。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值