一、线程同步优缺点
1.优点
线程同步可以保证多线程在操作同一个资源时,结果的正确性, 能够保证可见性, 指令有序性, 以及原子性。
2.缺点
二、volatile特点
- 保证内存的可见性
规定线程每次修改变量副本后立刻同步到主内存中
规定线程每次使用变量前,先从主内存中刷新最新的值到工作内存,用于保证能看见其他线程对变量修改的最新值
- 保证有序性
添加内存屏障(处理器的一些指令),防止指令的重排,保证有序性
//自定义的方法
public class Test01 {
static volatile int a=0;
public void test(){
for (int i = 0; i < 10000; i++) {
a++;
}
}
}
public class Test02 {
public static void main(String[] args) {//主线程,主线程只能有一个
/**
多个线程同时执行
创建5个线程,5个线程都执行test()
*/
Test01 test01=new Test01();
for (int i = 0; i < 5; i++) {
new Thread(()->{
test01.test();
System.out.println(Thread.currentThread().getName());
}).start();
}
//主线程休眠两秒,打印a的值
try {
Thread.sleep(2000);
System.out.println(Test01.a);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
三、synchronized同步锁
- 实例方法上(非静态方法):对象锁——- 需要在类实例化后, 在进行调用
- 静态方法上:类锁——-静态方法属于类级别的方法, 类不是实例化就可以使用
- 修饰代码块(对象锁—锁为this、类锁—锁为class和锁为固定值时)
- 哪个方法加上了synchronzied,哪个方法就加了锁
- 必须使用同一个对象
- 只要时一个类创建的对象,锁都生效
synchronized的加锁和解锁过程不需要程序员手动控制,只要执行到synchronized作用范围会自动加锁(获取锁/持有锁),执行完成后会自动解锁(释放锁)。锁同一时刻,只能被一个线程持有。
public class Test03 {
static int a=0;
//public synchronized void test()
public synchronized static void test(){
for (int i = 0; i < 10000; i++) {
a++;
}
}
}
public class Test04 {
public static void main(String[] args) {
// Test03 test03=new Test03();
for (int i = 0; i < 5; i++) {
new Thread(()->{
//test03.test();
Test03.test();
System.out.println(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(2000);
System.out.println(Test03.a);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
1.修饰实例方法
锁类型: 使用synchronized修饰实例方法时为 对象锁
锁范围: 锁的范围是加锁的方法
锁生效: 必须为同一个对象调用该方法该锁才有作用
//synchronized修饰实例方法
public class Test01 {
public static int a=0;
//实例方法加锁, 锁的范围是加锁的方法, 必须为同一个对象调用该方法该锁才会生效
public synchronized void test(){
for (int i = 0; i < 10000; i++) {
a++;
}
}
}
public class Test02 {
public static void main(String[] args) {
Test01 test01=new Test01();
for (int i = 0; i < 5; i++) {
new Thread(()->{
/**
* Test01 test01=new Test01();
* 每一个线程都会创建一个新对象, 不同对象调用加锁的方法, 锁失效
*/
test01.test();
}).start();
}
try {
Thread.sleep(2000);
System.out.println(Test01.a);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.修饰静态方法
锁类型: 使用synchronized修饰静态方法时为 类锁
锁范围: 锁的范围是加锁的方法
锁生效: 该类所有的对象调用加锁方法, 锁都生效
public class Test03 {
public static int a=0;
//静态方法加锁, 锁的范围是加锁的方法, 该类所有的对象调用加锁方法, 锁都生效,属于类锁
public static synchronized void test(){
for (int i = 0; i < 10000; i++) {
a++;
}
}
}
public class Test04 {
public static void main(String[] args) {
//Test03 test03=new Test03();
for (int i = 0; i < 5; i++) {
new Thread(()->{
/**
* 方式一:
* Test03 test03=new Test03();
* test03.test();
*/
Test03.test();//方式二
}).start();
}
try {
Thread.sleep(2000);
System.out.println(Test03.a);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.修饰代码块
语法:
synchronized(锁){
// 内容
}
锁代码块是非常重要的地方。添加锁的类型是Object类型。
运行过程:多线程执行时,每个线程执行到这个代码块时首先会判断是否有其他线程持有这个锁,如果没有,执行synchronized代码块。如果已经有其他线程持有锁,必须等待线程释放锁。 当一个线程执行完成synchronized代码块时会自动释放所持有锁。
3.1 代码演示: 锁为固定值
当锁为固定值时,每个线程执行到synchronized代码块时都会判断这个锁是否被其他线程持有,哪个线程抢到先执行哪个线程。当抢到的线程执行完synchronized代码块后,会释放锁,其他线程竞争,抢锁,抢到的持有锁,其他没抢到的继续等待
由于值固定不变, 所有的对象调用加锁的代码块, 都会争夺锁资源, 属于类锁``
public class Test06 {
public int a=0;
public int b=0;
public void test(){
synchronized ("sxt"){ //类锁
for (int i = 0; i < 10000; i++) {
a++;
}
}
for (int j = 0; j < 10000; j++) { //未加锁
b++;
}
}
}
public class Test05 {
public static void main(String[] args) {
Test06 test06=new Test06();
for (int i = 0; i < 5; i++) {
new Thread(()->{
test06.test();
System.out.println(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(2000);
System.out.println(test06.a);
System.out.println(test06.b);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.2 代码演示: 锁不同的内容
每个线程中synchronized的锁不相同时,相当于没有加锁。因为没有需要竞争锁的线程,线程执行到synchronized时,直接获取锁,进入到代码块。
public class Test07 {
//创建成员变量
public static int a=0;
//当锁为固定值时,每个线程执行到synchronized代码块时都会判断这个锁是否被其他线程持有
public void test(String name){
synchronized (name){
for (int i = 0; i < 10000; i++) {
a++;
}
}
}
}
public class Test08 {
public static void main(String[] args) {
Test07 test07=new Test07();
for (int i = 0; i < 5; i++) {
new Thread(()->{
String name = Thread.currentThread().getName();
System.out.println(name);
test07.test(name);
}).start();
}
try {
Thread.sleep(10000);
System.out.println(Test07.a);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.3 代码演示: 锁为this
当锁为this时,需要看线程中是否为同一个对象调用的包含synchronized所在的方法。这种写法也是比较常见的写法
同一个对象调用加锁方法时:
如果是同一个对象调用synchronized所在方法时,this代表的都是一个对象。this就相当于固定值。所以可以保证结果正确性, 属于对象锁
public class Test09 {
public static int a=0;
//当锁为this时,每个线程执行到synchronized代码块时都会判断这个锁是否被其他线程持有, this就相当于调用加锁代码块的对象, 同一个对象调用, 锁生效
public void test(){
synchronized(this){
for (int i = 0; i < 10000; i++) {
a++;
}
}
}
}
public class Test10 {
public static void main(String[] args) {
Test09 test09=new Test09();//此时创建this生效,for循环中的test09为Test09中的对象
for (int i = 0; i < 5; i++) {
new Thread(()->{
//Test09 test09=new Test09();
test09.test();
}).start();
}
try {
Thread.sleep(2000);//主线程休眠两秒
System.out.println(Test09.a);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.4 代码演示: 锁为class
当锁为Class时,是一个标准的类锁, 所有的对象调用加锁的代码块都生效
public class Test11 {
public static int a=0;
public void test(){
//当锁为类时, 所有对象执行加锁代码块, 锁都生效
synchronized (Test11.class){
for (int i = 0; i < 10000; i++) {
a++;
}
}
}
}
public class Test12 {
public static void main(String[] args) {
//Test11 test11=new Test11();
for (int i = 0; i < 5; i++) {//当锁为类时, 所有对象执行加锁代码块, 锁都生效
new Thread(()->{
Test11 test11=new Test11();
test11.test(); //五个子线程都调用test()方法
}).start();
}
try {
Thread.sleep(2000);
System.out.println(Test11.a);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4.synchronize特点
synchronized可以保证可见性,因为每次执行到synchronized代码块时会清空线程区。
synchronized 可以保证原子性。
synchronized 会不禁用指令重排,但可以保证有序性。因为同一个时刻只有一个线程能操作。