文章目录
synchronized关键字
对某个对象进行加锁
使用synchronized关键字有以下三种使用方式:
- 同步代码块
同步代码块锁的是括号里面的配置对象 - 同步方法
同步方法锁的是当前实例对象 - 静态同步方法
静态同步方法锁的是当前类的Class对象
- 自己新建一个对象作为锁住的对象
public class T{
private int count = 10;
private Object o =new Object();
public void m(){
synchronized(o){
count--;
System.out.println(Thtread.currentThread().getName() + "count = " + count);
}
}
}
- 使用this对象作为锁对象
public class T{
private int count = 10;
public void m(){
synchronized(this){//同步代码块
count--;
System.out.println(Thtread.currentThread().getName() + "count = " + count);
}
}
}
- 上文代码的另外一种写法
public class T{
private int count = 10;
public synchronized void m(){//同步方法
count--;
System.out.println(Thtread.currentThread().getName() + "count = " + count);
}
}
- 静态同步方法:没有this对象的静态方法如何获得锁对象
public class T{
private int count = 10;
public synchronized void m(){
count--;
System.out.println(Thtread.currentThread().getName() + "count = " + count);
}
public static void mm(){
synchronized(T.class){//静态的方法没有this对象,所以用反射来获取它的锁对象
count--;
}
}
}
public class T implements Runnable{
private int count = 10;
public /*synchronized*/ void run(){//synchronized所标注的代码块具备原子性,执行期间不可再分
count--;
System.out.println(Thtread.currentThread().getName() + "count = " + count);
}
public void main(){
T t = new T();//只有一个对象
for(int i = 0; i < 5; i++){
new Thread(t, "THREAD" + i).start();//开启五个线程,共同访问t对象,共同访问count
}
}
}
同步方法和非同步方法是否可以同时被调用
在一个同步方法执行的过程中,非同步的方法可以运行,因为非同步方法的执行不需要锁
public class demo6 {
public synchronized void m1() {
System.out.println(Thread.currentThread().getName() + "m1 start.... ");
try {
Thread.sleep(10000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "m1 end.... ");
}
public void m2() {
try {
Thread.sleep(5000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "m2 .... ");
}
public static void main(String[] args) {
demo6 t = new demo6();
new Thread(()->t.m1(), "t1").start();
new Thread(()->t.m2(), "t2").start();
}
}
脏读
写时需要加锁,读时其实也需要加锁,否则容易出现脏读的现象
/*写的时候加锁
* 读的时候不加锁
* 脏读的现象
*/
package concurrent1;
import java.util.concurrent.TimeUnit;
public class demo8 {
String name;
double balance;
public synchronized void set(String name, double balance) {
this.name = name;
try {
Thread.sleep(2000);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
this.balance = balance;
}
public double getBalance(String name) {
return this.balance;
}
public static void main(String[] args) {
demo8 aDemo8 = new demo8();
new Thread(()->aDemo8.set("zhangsan", 100.0)).start();
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println(aDemo8.getBalance("zhangsan"));
try {
TimeUnit.SECONDS.sleep(2);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println(aDemo8.getBalance("zhangsan"));
}
}
可重入
import java.util.concurrent.TimeUnit;
/*
* 一个同步方法是否可以调用另外一个同步的方法
* 一个已经拥有某个对象锁的线程,再次申请的时候仍然会得到该对象的锁
* 即:synchronized获得的锁是可以重入的
*/
public class demo9 {
synchronized void m1() {
System.out.println("m1 start...");
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
m2();
}
synchronized void m2() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println("m2");
}
}
- 子类中调用父类的同步方法
//子类中调用父类的同步方法
import java.util.concurrent.TimeUnit;
public class demo10 {
synchronized void m() {
System.out.println("m start ..");
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println("m end");
}
public static void main(String[] args) {
new TT().m();
}
}
class TT extends demo10{
@Override
synchronized void m() {
System.out.println("child m start");
super.m();
System.out.println("child m end");
}
}
程序在执行过程中出现异常,锁会被释放
import java.sql.Time;
import java.util.concurrent.TimeUnit;
/*
* 程序在执行时,如果出现异常,默认情况下锁会被释放,这时其他的线程访问的数据可能会出现不一致的情况
*/
public class T {
int count = 0;
synchronized void m() {
System.out.println(Thread.currentThread().getName() + " start ");
while(true) {
count++;
System.out.println(Thread.currentThread().getName() + "count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
if(count == 5) {
int i = 1/0;
}
}
}
public static void main(String[] args) {
T t =new T();
Runnable r = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
t.m();
}
};
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}
volatile 关键字:内存可见性
使一个变量在多个线程间可见。
Java编程语言允许线程访问共享变量,为了确保共享变量能够准确一致地更新,线程确保通过排他锁单独获得这个变量。
Java中的volatile关键字就是这个定义的体现。如果一个变量被声明为volatile,那么确保这个变量是“可见的”。可见性的意思是当线程修改一个共享变量的时候,另外一个线程能够读到这个修改的值。
具体原理参见:这个
例子:
public class T {
volatile boolean running = true;
void m() {
System.out.println(" m start ");
while(running) {
}
System.out.println("m end ...");
}
public static void main(String[] args) {
T t =new T();
new Thread(t::m, "t1").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
t.running = false;
}
}
volatile 与synchronized的区别:
volatile只能保证可见性
synchronized既保证可见性又保证了原子性
public class T {
volatile int count = 0;//只保证可见性
void m() {
for(int i = 0; i < 10000; i++) count++;
}
public static void main(String[] args) {
T t =new T();
List<Thread> threads = new ArrayList<Thread>();
for(int i = 0; i < 10; i++)
threads.add(new Thread(t::m, "thread-" + i));
threads.forEach((o)->o.start());
threads.forEach((o)->{
try {
o.join();
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
使用Atomic***类来保证原子性
public class T {
AtomicInteger count = new AtomicInteger(0);
void m() {
for(int i = 0; i < 10000; i++){
// if(count.get() < 1000) //使用Atomicxxx的多个方法并不具备原子性
count.incrementAndGet();//用于替代count++的
}
}
public static void main(String[] args) {
T t = new T();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(t::m, "thread-" + i));
}
threads.forEach((o) -> o.start());
threads.forEach((o) -> {
try {
o.join();
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
synchronized的优化
public class T {
int count = 0;
synchronized void m1() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
count++;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
void m2() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
//业务逻辑中只有下面的这句话需要同步,这时不应该在给整个方法加上锁
//采用更加细粒度的方法,可以使线程争用的时间变短,提高效率
synchronized (this) {
count++;
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
锁住的对象属性发生改变不会影响锁的使用,但是如果引用的对象改变情况就大为不同了
public class T {
Object o = new Object();
void m() {
synchronized (o) {
while(true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
T t = new T();
new Thread(t::m, "t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
Thread t2 = new Thread(t::m, "t2");
t.o = new Object();
t2.start();
}
}
不应该用字符串作为锁定的对象
public class T {
String s1 = "Hello";
String s2 = "Hello";
void m1() {
synchronized (s1) {
}
}
void m2() {
synchronized (s2) {
}
}
}