1. synchronized 同步方法
1.1 方法内的变量是线程安全的
方法内部的变量是线程私有的,不存在线程不安全的问题
public class Service {
public void addI(String username) {
try {
int num = 0;
if (username.equals("a")) {
num = 100;
System.out.println("a set over");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over");
}
System.out.println(username + " num=" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
this.service = service;
}
@Override
public void run() {
service.addI("a");
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
this.service = service;
}
@Override
public void run() {
service.addI("b");
}
}
public class Test {
public static void main(String[] args) {
Service service = new Service();
ThreadA threadA = new ThreadA(service);
ThreadB threadB = new ThreadB(service);
threadA.start();
threadB.start();
}
}
1.2 实例变量线程不安全
-
实例变量是线程共享的,存在线程不安全问题
// 1.1 Service 改造,其他不变 public class Service { private int num; public void addI(String username) { try { if (username.equals("a")) { num = 100; System.out.println("a set over"); Thread.sleep(2000); } else { num = 200; System.out.println("b set over"); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { e.printStackTrace(); } } } -
在方法前加上
synchronized关键字,线程在调用方法时需要获取锁对象才能执行方法// 1.2 Service 改造,其他不变 public class Service { private int num; synchronized public void addI(String username) { try { if (username.equals("a")) { num = 100; System.out.println("a set over"); Thread.sleep(2000); } else { num = 200; System.out.println("b set over"); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { e.printStackTrace(); } } }
1.3 多个对象多个锁
关键字 synchronized 取得的锁都是对象锁,而不是把一段代码或方法(函数)当做锁
public class Service {
private int num;
synchronized public void addI(String username) {
try {
if (username.equals("a")) {
num = 100;
System.out.println("a set over");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over");
}
System.out.println(username + " num=" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
this.service = service;
}
@Override
public void run() {
service.addI("a");
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
this.service = service;
}
@Override
public void run() {
service.addI("b");
}
}
public class Test {
public static void main(String[] args) {
Service serviceA = new Service();
Service serviceB = new Service();
ThreadA threadA = new ThreadA(serviceA);
ThreadB threadB = new ThreadB(serviceB);
threadA.start();
threadB.start();
}
}
1.4 synchronized 锁重入
-
关键字
synchronized拥有锁重入的功能,当一个线程得到一个锁对象后,再次请求该对象锁时可以获取到该锁对象public class Service { synchronized public void testA() { System.out.println("enter testA()"); testB(); } synchronized public void testB() { System.out.println("enter testB()"); testC(); } synchronized public void testC() { System.out.println("enter testC()"); } } public class MyThread extends Thread { @Override public void run() { Service service = new Service(); service.testA(); } } public class Test { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } } -
当存在父子类继承关系时,子类可以通过“可重入锁”调用父类的同步方法
public class FatherService { protected int i = 10; synchronized public void fatherMethod() { try { i--; System.out.println("fatherMethod print i=" + i); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } public class SonService extends FatherService { synchronized public void sonMethod() { try { while (i > 0) { i--; System.out.println("sonMethod print i=" + i); Thread.sleep(100); this.fatherMethod(); } } catch (InterruptedException e) { e.printStackTrace(); } } } public class Test { public static void main(String[] args) { Runnable runnable = new Runnable() { @Override public void run() { SonService sonService = new SonService(); sonService.sonMethod(); } }; Thread thread = new Thread(runnable); thread.start(); } }
1.5 出现异常,锁自动释放
public class Service {
synchronized public void testMethod() {
if (Thread.currentThread().getName().equals("a")) {
System.out.println(Thread.currentThread().getName() + " run");
int i = 1 / 0;
while (true) {}
} else {
System.out.println(Thread.currentThread().getName() + " run");
}
}
}
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Service service = new Service();
ThreadA threadA = new ThreadA(service);
threadA.setName("a");
ThreadB threadB = new ThreadB(service);
threadB.setName("b");
threadA.start();
Thread.sleep(500);
threadB.start();
}
}
1.6 同步不具有继承性
public class FatherService {
synchronized public void testMethod() {
try {
System.out.println(Thread.currentThread().getName() + " FatherService testMethod begin time=" + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + " FatherService testMethod begin time=" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class SonService extends FatherService {
@Override
public void testMethod() {
try {
System.out.println(Thread.currentThread().getName() + " SonService testMethod begin time=" + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + " SonService testMethod begin time=" + System.currentTimeMillis());
super.testMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private SonService sonService;
public ThreadA(SonService sonService) {
this.sonService = sonService;
}
@Override
public void run() {
sonService.testMethod();
}
}
public class ThreadB extends Thread {
private SonService sonService;
public ThreadB(SonService sonService) {
this.sonService = sonService;
}
@Override
public void run() {
sonService.testMethod();
}
}
public class Test {
public static void main(String[] args) {
SonService sonService = new SonService();
ThreadA threadA = new ThreadA(sonService);
threadA.setName("Thread-A");
ThreadB threadB = new ThreadB(sonService);
threadB.setName("Thread-B");
threadA.start();
threadB.start();
}
}
2. synchronized 同步代码块
当 A 线程调用同步方法执行一个长时间的任务,那么 B 线程必须等待比较长的时间才能执行该方法。这种情况下可以使用同步代码块来解决
2.1 synchronized 同步代码块的使用
public class Service {
public void task() {
try {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " run task beginTime=" + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " run task endTime=" + System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
this.service = service;
}
@Override
public void run() {
service.task();
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
this.service = service;
}
@Override
public void run() {
service.task();
}
}
public class Test {
public static void main(String[] args) {
Service service = new Service();
ThreadA threadA = new ThreadA(service);
threadA.setName("Thread-A");
ThreadB threadB = new ThreadB(service);
threadB.setName("Thread-B");
threadA.start();
threadB.start();
}
}
2.2 用同步代码块解决同步方法的弊端
public class TimeRecord {
public static long beginTime1;
public static long endTime1;
public static long beginTime2;
public static long endTime2;
}
// 同步方法
public class Service {
private String data;
synchronized public void doLongTimeTask() {
try {
System.out.println(Thread.currentThread().getName() + " run begin");
// 模拟长时间获取数据
Thread.sleep(3000);
data = Thread.currentThread().getName() + " data";
System.out.println(data);
System.out.println(Thread.currentThread().getName() + " run end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 同步代码块
public class Service {
private String data;
public void doLongTimeTask() {
try {
System.out.println(Thread.currentThread().getName() + " run begin");
// 模拟长时间获取数据
Thread.sleep(3000);
synchronized (this) {
data = Thread.currentThread().getName() + " data";
System.out.println(data);
}
System.out.println(Thread.currentThread().getName() + " run end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
this.service = service;
}
@Override
public void run() {
TimeRecord.beginTime1 = System.currentTimeMillis();
service.doLongTimeTask();
TimeRecord.endTime1 = System.currentTimeMillis();
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
this.service = service;
}
@Override
public void run() {
TimeRecord.beginTime2 = System.currentTimeMillis();
service.doLongTimeTask();
TimeRecord.endTime2 = System.currentTimeMillis();
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Service service = new Service();
ThreadA threadA = new ThreadA(service);
threadA.setName("Thread-A");
ThreadB threadB = new ThreadB(service);
threadB.setName("Thread-B");
threadA.start();
threadB.start();
Thread.sleep(7000);
long beginTime = TimeRecord.beginTime1 > TimeRecord.beginTime2 ? TimeRecord.beginTime2 : TimeRecord.beginTime2;
long endTime = TimeRecord.endTime1 > TimeRecord.endTime2 ? TimeRecord.endTime1 : TimeRecord.endTime2;
System.out.println("耗时:" + (endTime -beginTime) + "毫秒");
}
}
2.3 synchronized(this) 代码块是锁定当前对象的
public class Service {
synchronized public void otherMethod() {
System.out.println(Thread.currentThread().getName() + " run otherMethod");
}
public void doLongTimeTask() {
try {
synchronized (this) {
for (int i = 0;i < 20;i++) {
System.out.println(Thread.currentThread().getName() + " run doLongTimeTask i=" + i);
Thread.sleep(500);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
final Service service = new Service();
Runnable runnable1 = new Runnable() {
@Override
public void run() {
service.doLongTimeTask();
}
};
Thread a = new Thread(runnable1, "Thread-A");
Runnable runnable2 = new Runnable() {
@Override
public void run() {
service.otherMethod();
}
};
Thread b = new Thread(runnable2, "Thread-B");
a.start();
Thread.sleep(5000);
b.start();
}
}
2.4 静态同步 synchronized 方法与 synchronized(class) 代码块
关键字 synchronized 还可以应用在 static 静态方法上,synchronized(class) 代码块和 synchronized static 方法的作用一样,获取的锁都是 Class 类
public class Service {
synchronized public static void printA() {
try {
System.out.println(Thread.currentThread().getName() + " run printA beginTime=" + System.currentTimeMillis());
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " run printA endTime=" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void printB() {
try {
System.out.println(Thread.currentThread().getName() + " run printB beginTime=" + System.currentTimeMillis());
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " run printB endTime=" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void printC() {
synchronized (Service.class) {
System.out.println(Thread.currentThread().getName() + " run printC beginTime=" + System.currentTimeMillis());
System.out.println(Thread.currentThread().getName() + " run printC endTime=" + System.currentTimeMillis());
}
}
}
public class ThreadA extends Thread {
@Override
public void run() {
Service.printA();
}
}
public class ThreadB extends Thread {
@Override
public void run() {
Service service = new Service();
service.printB();
}
}
public class ThreadC extends Thread {
@Override
public void run() {
Service.printC();
}
}
public class Test {
public static void main(String[] args) {
ThreadA a = new ThreadA();
a.setName("Thread-A");
ThreadB b = new ThreadB();
b.setName("Thread-B");
ThreadC c = new ThreadC();
c.setName("Thread-C");
a.start();
b.start();
c.start();
}
}
2.5 数据类型 String 的常量池特性
JVM 中具有 String 常量池缓存的功能,String str1 = "aa"; 和 String str2 = "aa"; 中 str1 和 str2 都是同一个对象,对 str1 和 str2 使用关键字 synchronized 的两个代码块是同步阻塞的。不建议使用 String 作为锁对象
public class Service {
public void testMethod(String str) {
try {
synchronized (str) {
while (true) {
System.out.println(Thread.currentThread().getName());
Thread.sleep(500);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
this.service = service;
}
@Override
public void run() {
service.testMethod("aa");
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
this.service = service;
}
@Override
public void run() {
service.testMethod("aa");
}
}
public class Test {
public static void main(String[] args) {
Service service = new Service();
ThreadA threadA = new ThreadA(service);
threadA.setName("Thread-A");
ThreadB threadB = new ThreadB(service);
threadB.setName("Thread-B");
threadA.start();
threadB.start();
}
}
2.6 多线程的死锁
当不同的线程都在等待不可能被释放的锁,任务都无法执行完成时,造成线程“假死”
public class Service {
private Object lock1 = new Object();
private Object lock2 = new Object();
public void testA() {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " get lock1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " get lock2");
}
}
}
public void testB() {
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " get lock2");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " get lock1");
}
}
}
}
public class Test {
public static void main(String[] args) {
final Service service = new Service();
Runnable runnable1 = new Runnable() {
@Override
public void run() {
service.testA();
}
};
Thread a = new Thread(runnable1, "Thread-A");
Runnable runnable2 = new Runnable() {
@Override
public void run() {
service.testB();
}
};
Thread b = new Thread(runnable2, "Thread-B");
a.start();
b.start();
}
}
-
查看进程号
jps -
跟踪堆栈
jstack -l <pid>
3. volatile 关键字
关键字 volatile 的主要作用是使变量在多个线程间可见
3.1 解决死循环
关键字 volatile 的作用是强制从公共堆栈中取得变量的值,而不是从线程的私有数据栈中取得变量的值
public class RunThread extends Thread {
// private boolean isRunning = true;
volatile private boolean isRunning = true;
public void setRunning(boolean running) {
isRunning = running;
}
@Override
public void run() {
System.out.println("run begin");
while (isRunning) {}
System.out.println("run end");
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
RunThread runThread = new RunThread();
runThread.start();
Thread.sleep(5000);
runThread.setRunning(false);
System.out.println("set isRunning false");
}
}
使用 volatile 关键字增加了实例变量在多个线程之间的可见性。但 volatile 关键字不支持原子性
volatile 和 synchronized 的比较:
- 关键字
volatile是线程同步的轻量级实现,所以性能比synchronized好,并且volatile只能修饰变量,而synchronized可以修饰方法和代码块。随着 JDK 新版本的发布,关键字synchronized在执行效率上得到很大的提升,在开发中使用synchronized关键字的比例还是比较大的 - 多线程访问
volatile不会发生阻塞,而synchronized会出现阻塞 volatile能保证数据的可见性,但是不保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步
3.2 volatile 非原子的特性
volatile 关键字不具有原子性
public class MyThread extends Thread {
volatile private static int count;
private void addCount() {
for (int i = 0;i < 100;i++) {
count++;
}
System.out.println("count=" + count);
}
@Override
public void run() {
addCount();
}
}
public class Test {
public static void main(String[] args) {
MyThread[] myThreads = new MyThread[100];
for (int i = 0;i < 100;i++) {
myThreads[i] = new MyThread();
}
for (int i = 0;i < 100;i++) {
myThreads[i].start();
}
}
}
i++ 的操作不是原子操作:
- step1:从内存中取出 i 的值
- step2:计算 i 的值
- step3:将 i 的值写到内存中
假如第二步计算值的时候,另一个线程也在修改 i 的值,那么这时就出现脏数据了
学自《Java多线程编程核心技术》