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一样的线程同步(原子性)的效果。