2.1 synchronized同步方法
方法中的变量不存在非线程安全问题,永远都是线程安全的。这是方法内部的变量私有特性造成的。
2.1.2 实例变量非线程安全
使用多个线程并发访问PrivateNum类中的addI方法。
public class PrivateNum {
private int num = 0;
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");
}
}catch(Exception e){
e.printStackTrace();
}
}
}
class ThreadA extends Thread {
private PrivateNum ref;
public ThreadA(PrivateNum ref){
super();
this.ref = ref;
}
@Override
public void run(){
super.run();
ref.addI("a");
}
}
class ThreadB ...
class Run {
public static void main(String[] args){
PrivateNum ref = new PrivateNum();
ThreadA a = new ThreadA(ref);
a.start();
ThreadB b = new ThreadB(ref);
b.start();
}
}
/*结果
a set over
a num = 100
b set over
b num = 200*/
只有共享资源的读写访问才需要同步化,如果不是共享资源,就没有同步的必要。
同一个类,有两个方法methodA(),methodB()。二者全都加锁synchronized。有两个线程ThreadA,ThreadB.分别调用methodA,methodB.这个线程执行在同一个main中。不同的方法加锁之后也是要按顺序执行的。对象锁只有一个。
但是如果对象锁的不是同一个对象,则会出现异步
public class Test {
public static void main(String[] args) {
Service service = new Service(); //不是同一个对象
Service service1 = new Service();
ThreadA a= new ThreadA(service);
a.setName("A");
a.start();
ThreadB b =new ThreadB(service1);
b.setName("B");
b.start();
}
}
class Service {
private String name;
private String password;
private String lockString = new String();
public void setName2Password(String name,String password){
try{
synchronized (lockString){
System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"进入同步块");
this.name = name;
Thread.sleep(1000);
this.password = password;
System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"离开同步块");
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
class ThreadA extends Thread{
private Service service;
public ThreadA(Service service){
super();
this.service = service;
}
public void run(){
service.setName2Password("a","aa");
}
}
class ThreadB extends Thread{
private Service service;
public ThreadB(Service service){
super();
this.service = service;
}
public void run(){
service.setName2Password("a","aa");
}
}
2.1.5 脏读
之前同步是针对赋值,但在取值时仍可能出现意外。
在读取实例变量时,此值已经被其他线程更改过了。针对变量的set和get方法都要加锁。保证轮流执行。
2.1.6 synchronized锁重入
当一个线程得到一个对象锁之后,再次请求此对象锁是可以再次得到该对象的锁。
一个synchronized方法、块内部调用本类其他synchronized方法、块时,是永远可以得到锁的。
可重入锁概念:
有一条线程获得某对象的锁,此时对象锁没释放,当其再次要获得这个对象的锁的时候是可以再次获得的,如果不可以锁重入的话,就会造成死锁。
可重入锁也支持父子类继承环境
public class Test {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
class Main {
protected int i = 10;
synchronized public void method() {
try {
i--;
System.out.println("main print i:" + i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Sub extends Main {
synchronized public void method() {
try {
while(i>0){
i--;
System.out.println("sub print i:" + i);
Thread.sleep(100);
this.method();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyThread extends Thread{
public void run(){
Sub sub = new Sub();
sub.method();
}
}
当父子类继承关系时,子类完全可通过“可重入锁”调用父类同步方法的。
2.1.7 出现异常,锁自动释放
2.1.8 同步不具有继承性
必须在子类方法中添加synchronized关键字才可以。
2.2 synchronized同步语句块
声明在方法上某时候是有弊端的,例如:A线程调用同步方法执行一个长时间任务,B线程必须等待很长时间。
两个并发线程访问同一个对象object中的synchronized(this)同步代码块时,一段时间内只能有一个线程被执行。另一个线程必须等待当前线程执行完这个代码块后才能执行。说明synchronized使用的“对象监视器”是一个。
2.2.4 一半异步,一半同步
在synchronized块中就是同步执行,不在synchronized块中的代码就是异步执行。
synchronized同步方法和synchronized(this)同步代码块都是对同个类的其他同步进行阻塞;同一时间只有一个线程可执行synchronized同步方法或者synchronized(this)同步代码块。
2.2.7 将任意对象作为对象监视器
使用非this对象作为同步代码块的参数。
public class Test {
public static void main(String[] args) {
Service service = new Service();
ThreadA a= new ThreadA(service);
a.setName("A");
a.start();
ThreadB b =new ThreadB(service);
b.setName("B");
b.start();
}
}
class Service {
private String name;
private String password;
private String lockString = new String();
public void setName2Password(String name,String password){
try{
synchronized (lockString){
System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"进入同步块");
this.name = name;
Thread.sleep(3000);
this.password = password;
System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"离开同步块");
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
class ThreadA extends Thread{
private Service service;
public ThreadA(Service service){
super();
this.service = service;
}
public void run(){
service.setName2Password("a","aa");
}
}
class ThreadB extends Thread{
private Service service;
public ThreadB(Service service){
super();
this.service = service;
}
public void run(){
service.setName2Password("a","aa");
}
}
锁非this对象的优点:
如果一个类有很多个synchronized方法,这时虽然能实现同步,但会阻塞,影响运行效率;
但如果用同步代码块锁非this对象,则synchronized(not this)代码块中的程序与同步方法是异步的,不和其他锁this的同步方法争抢this锁,则提高运行效率。
但是:
public class Service {
private String name;
private String password;
public void setName2Password(String name,String password){
try{
String lockString = new String(); //锁对象放在这里了
synchronized (lockString){
System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"进入同步块");
this.name = name;
Thread.sleep(3000);
this.password = password;
System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"离开同步块");
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
这样的话,同步就没有作用了。
2.2.8 synchronized(not this)3个结论
- 当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。
- 当其他线程执行x对象中synchronized同步方法时呈同步效果。
- 当其他线程执行x对象方法里面的synchronized(this)代码块时也呈同步效果。
验证第一个结论
class MyObject {}
//锁
class Service {
public void testMethod(MyObject my){
synchronized(my){
try{
System.out.println("getlock time:"+System.currentTimeMillis() + "thread name:"+Thread.currentThread().getName());
Thread.sleep(2000);
System.out.println("releaselock time:"+System.currentTimeMillis() + "thread name:"+Thread.currentThread().getName());
}catch(){
e.printStackTrace();
}
}
}
}
//Thread
class ThreadA extend Thread {
private Service service;
private MyObject my;
public ThreadA(Service service,MyObject my){
this.service = service;
this.my = my;
}
public void run(){
super.run();
service.testMethod(my);
}
}
public class Run_1 {
public static void main(String[] args){
Service service = new Service();
MyObject my = new MyObject();
ThreadA a= new ThreadA(service,my);
a.setName("a");
a.start();
ThreadB b= new ThreadB(service,my);
b.setName("b");
b.start();
}
}
如果MyObject不是同一个对象,则不是同步效果了。
第2个结论验证
class MyObject {
synchronized public void speedPrintString(){
System.out.println("getlock time:"+System.currentTimeMillis() + "thread name:"+Thread.currentThread().getName());
Thread.sleep(2000);
System.out.println("releaselock time:"+System.currentTimeMillis() + "thread name:"+Thread.currentThread().getName());
}
}
//锁
class Service {
public void testMethod(MyObject my){
synchronized(my){
try{
System.out.println("getlock time:"+System.currentTimeMillis() + "thread name:"+Thread.currentThread().getName());
Thread.sleep(2000);
System.out.println("releaselock time:"+System.currentTimeMillis() + "thread name:"+Thread.currentThread().getName());
}catch(){
e.printStackTrace();
}
}
}
}
class ThreadB extend Thread {
private MyObject my;
public ThreadA(MyObject my){
this.my = my;
}
public void run(){
super.run();
service.testMethod(my);
}
}
class ThreadA extend Thread {
private Service service;
private MyObject my;
public ThreadA(Service service,MyObject my){
this.service = service;
this.my = my;
}
public void run(){
super.run();
my.speedPrintString();
}
}
public class Run_1 {
public static void main(String[] args){
Service service = new Service();
MyObject my = new MyObject();
ThreadA a= new ThreadA(service,my);
a.setName("a");
a.start();
ThreadB b= new ThreadB(my);
b.setName("b");
b.start();
}
}
验证第三个结论
//上述代码中MyObject的方法加入代码块
class MyObject {
public void speedPrintString(){
synchronized(this){
System.out.println("getlock time:"+System.currentTimeMillis() + "thread name:"+Thread.currentThread().getName());
Thread.sleep(2000);
System.out.println("releaselock time:"+System.currentTimeMillis() + "thread name:"+Thread.currentThread().getName());
}
}
}
2.2.9 synchronized(class)代码块
synchronized可以用在static静态方法上,就是对某个类的Class类进行加锁。
public class Test {
public static void main(String[] args) {
ThreadA a= new ThreadA();
a.setName("A");
a.start();
ThreadB b =new ThreadB();
b.setName("B");
b.start();
}
}
class Service {
synchronized public static void printA(){
try{
System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"进入同步块");
Thread.sleep(1000);
System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"离开同步块");
}catch (InterruptedException e){
e.printStackTrace();
}
}
synchronized public static void printB(){
System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"进入同步块");
System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"离开同步块");
}
}
class ThreadA extends Thread{
public void run(){
Service.printA();
}
}
class ThreadB extends Thread{
public void run(){
Service.printB();
}
}
synchronized关键字加到static方法上是给Class类加锁,而针对非静态方法是给对象上锁。
在该类中如果有this锁的方法,那么和class锁会产生异步调用,因为class锁可对所有对象实例起作用。
2.2.10 String的常量池特性
注意synchronized(String)中,常量池带来的一些例外。
class Service {
public static void print(String param){
try{
synchronized(param){
while(true){
System.out.println(Thread.currentThread().getName());
Thread.sleep(1000);
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
class ThreadA extends Thread {
private Service service;
public ThreadA(Service service){
super();
this.service = service;
}
public void run(){
service.print("AA");
}
}
class ThreadB extends Thread {
private Service service;
public ThreadB(Service service){
super();
this.service = service;
}
public void run(){
service.print("AA");
}
}
public class Test {
public static void main(String[] args) {
Service ser = new Service();
ThreadA a= new ThreadA(ser);
a.setName("A");
a.start();
ThreadB b =new ThreadB(ser);
b.setName("B");
b.start();
}
}
因为两个线程用的字符串都是“AA”,这个是常量,因此两线程持有相同的锁。造成线程B不能执行。这是String 常量池带来的问题。通常不使用string作为锁对象。而改用,比如new Object()作为锁对象。
2.2.11 同步synchronized方法无限等待的解决办法
死循环造成其他同步方法无法获得锁
//methodB永远不会获取锁
public class Serive {
synchronized public void methodA(){
System.out.println("A begin");
boolean is = true;
while(is){
}
System.out.println("A end");
}
synchronized public void methodB(){
System.out.println("B begin");
System.out.println("B end");
}
}
可以用同步块来解决同步方法死锁的问题
public class Serive {
Object object1 = new Object();
public void methodA(){
synchronized (object1){
System.out.println("A begin");
boolean is = true;
while(is){
}
System.out.println("A end");
}
}
Object object2 = new Object();
public void methodB(){
synchronized (object2){
System.out.println("B begin");
System.out.println("B end");
}
}
}
2.2.12 多线程死锁
不同的线程都在等待根本不可能被释放的锁,导致所有任务无法继续完成。造成线程“假死”。
死锁实例
public class DeadThread implements Runnable {
public String name;
public Object lock1 = new Object();
public Object lock2 = new Object():
public void setFlag(String name){
this.name = name;
}
public void run(){
if(name.equals("a")){
synchronized(lock1){
try{
System.out.println("name"+name);
Thread.sleep(3000);
}catch(){
e.printStackTrace();
}
//互相持有对方的锁,造成死锁
synchronized (lock2){
System.out.println("lock1->lock2死锁");
}
}
}
if(name.equals("b")){
synchronized(lock2){
try{
System.out.println("name"+name);
Thread.sleep(3000);
}catch(){
e.printStackTrace();
}
synchronized (lock2){
System.out.println("lock2->lock1死锁");
}
}
}
}
}
2.3 volatile关键字
主要作用使变量在多个线程间可见。
作用:volatile强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。
public class RunThread extends Thread {
volatile private boolean isRun = true;
public boolean isRun(){
return isRun;
}
public void setRun(boolean isRun){
this.isRun = isRun;
}
public void run(){
System.out.println("进入run");
while(isRun == true){
}
System.out.println("线程被停止了");
}
}
public static void main(String[] args){
RunThread th = new RunThread();
th.start();
Thread.sleep(1000);
th.setRun(false);
}
缺点:不支持原子性。
synchronized和volatile比较:
- volatile是线程同步的轻量级实现,只能修饰变量,synchronized可以修改方法、以及代码块。
- 多线程访问volatile不会阻塞,而synchronized会阻塞。
- volatile保证数据可见性,不保证原子性;synchronized保证原子性,间接保证可见性,因为它将私有内存和公共内存中数据进行同步。
- 解决方向不同:volatile解决的是变量在多个线程间的可见性;synchronized解决的是多个线程之间访问资源的同步性。
如果addCount方法加锁,count变量没必要加volatile
class MyThread extends Thread {
//不用加
volatile public static int count;
synchronized private static void addCount(){
for(int i=0;i < 100;i++){
count++;
}
System.out.println("count="+count);
}
@Override
public void run(){
addCount();
}
}
public class Run {
public static void main(String[] args){
MyThread[] myArray = new MyThread[100];
for(int i=0;i<100;i++){
myArray[i] = new MyThread();
}
for(int i=0;i<100;i++){
myArray[i].start();
}
}
}
volatile出现非线程安全的原因
- read和load阶段:从主存复制变量到当前线程工作内存
- use和assign阶段:执行代码,改变共享变量值
- store和write阶段:用工作内存数据刷新主存对应变量值
load,use,asign都属于非原子性操作。
2.3.5 使用原子类进行i++操作
i++本身也是非线程安全的,分解步骤:
- 从内存中取出i的值
- 计算i的值
- 将i的值写到内存中
可以使用AtomicInteger保证原子性操作。
class AddCountThread extends Thread {
private AtomicInteger count = new AtomicInteger(0);
@Override
public void run(){
for(int i=0;i<10000;i++){
System.out.println(count.incrementAndGet());
}
}
}
public class Run {
public static void main(String[] args){
AddCountThread count = new AddCountThread();
Thread t1= new Thread(count);
t1.start();
Thread t2= new Thread(count);
t2.start();
Thread t3= new Thread(count);
t3.start();
}
}
2.3.6 原子类不安全的时候
当有一个方法例如addNum()对AtomicInteger进行操作时,如果多个线程调用addNum()方法,并且addNum()方法本身不是synchronized的,那么就会出现异步调用的问题。虽然AtomicInteger本身是线程安全的。