文章目录
1、synchronized简介
2、synchronized使用范围
2.1 修饰同步代码块
多个线程访问同一个对象的同步代码块
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
public class Solution {
public static void main(String[] args) {
SyncThread syncThread=new SyncThread();
Thread thread1=new Thread(syncThread,"syncThread1");
Thread thread2= new Thread(syncThread,"syncThread2");
thread1.start();
thread2.start();
}
}
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count=0;
}
@Override
public void run() {
synchronized (this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName()+":"+(count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public int getCount(){
return count;
}
}
上面代码中,thread1和thread2分别访问了同一个对象的同一个代码块,因此这两个会产生互斥,因此必须等到thread1执行完毕才能执行thread2。因此结果如下:
如果稍微修改下,将thread1和thread2分别访问不同的对象,则这两个线程不会产生互斥现象,修改代码如下:
SyncThread syncThread1=new SyncThread();
SyncThread syncThread2=new SyncThread();
Thread thread1=new Thread(syncThread1,"syncThread1");
Thread thread2= new Thread(syncThread2,"syncThread2");
thread1.start();
thread2.start();
结果如下:
多个线程访问synchronized和非synchronized代码块
当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。
多个线程访问synchronized和非synchronized代码块
public class Solution2 {
public static void main(String[] args) {
Counter counter = new Counter();
//
Thread thread1 = new Thread(counter,"A");
Thread thread2=new Thread(counter,"B");
thread1.start();
thread2.start();
}
}
class Counter implements Runnable{
private int count;
public Counter() {
count = 0;
}
public void countAdd(){
synchronized (this){
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName()+":"+(count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//非synchronized代码块,未对count进行读写操作,所以可以不用synchronized
public void printCount() {
for (int i = 0; i < 5; ++i) {
try {
System.out.println(Thread.currentThread().getName()+"count"+count);
Thread.sleep(100);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
String threadName=Thread.currentThread().getName();
if (threadName.equals("A")){
countAdd();
}else if (threadName.equals("B")){
printCount();
}
}
}
结果如下
上面代码中countAdd是一个synchronized的,printCount是非synchronized的。从上面的结果中可以看出一个线程访问一个对象的synchronized代码块时,别的线程可以访问该对象的非synchronized代码块而不受阻塞。
指定要给某个对象加锁
public class Solution3 {
public static void main(String[] args) {
Account account = new Account("zhang san",10000.0f);
AccountOperation accountOperation=new AccountOperation(account);
final int THREAD_NUM=5;
Thread[] threads=new Thread[THREAD_NUM];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(accountOperation,"Thread"+i);
threads[i].start();
}
}
}
/**
* 银行账户类
*/
class Account {
String name;
float amount;
public Account(String name, float amount) {
this.name = name;
this.amount = amount;
}
//存钱
public void deposit(float amt){
amount+=amt;
try {
Thread.sleep(100);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
//取钱
public void withdraw(float amt){
amount-=amt;
try {
Thread.sleep(100);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
public float getBalance(){
return amount;
}
}
/**
* 账户操作类
*/
class AccountOperation implements Runnable{
private Account account;
public AccountOperation(Account account){
this.account = account;
}
@Override
public void run() {
synchronized (account) {
account.deposit(500);
account.withdraw(500);
System.out.println(Thread.currentThread().getName()+":"+account.getBalance());
}
}
}
在调用代码中,创建了5个线程调用同一个对象,但是由于对象加了锁,所以必须等一个线程线程运行完,其它线程才能获取锁,也就是说谁拿到那个锁谁就可以运行它所控制的那段代码。结果如下:
当有一个明确的对象作为锁时,就可以用类似下面这样的方式写程序。
public void method3(SomeObject obj)
{
//obj 锁定的对象
synchronized(obj)
{
// todo
}
}
2.2 修饰一个方法
Synchronized修饰一个方法很简单,就是在方法的前面加synchronized
public synchronized void method(){//todo};
synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。如将【Demo1】中的run方法改成如下的方式,实现的效果一样。
public class Solution4 {
public static void main(String[] args) {
//同一个对象
// SyncThread syncThread=new SyncThread();
// Thread thread1=new Thread(syncThread,"syncThread1");
// Thread thread2= new Thread(syncThread,"syncThread2");
// thread1.start();
// thread2.start();
//
//不同对象
SyncThread4 syncThread1=new SyncThread4();
SyncThread4 syncThread2=new SyncThread4();
Thread thread1=new Thread(syncThread1,"syncThread1");
Thread thread2= new Thread(syncThread2,"syncThread2");
thread1.start();
thread2.start();
}
}
class SyncThread4 implements Runnable {
private static int count;
public SyncThread4() {
count=0;
}
//之间是synchronized是修饰的同步代码块,现在synchronized修饰的是一个方法
@Override
public synchronized void run() {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName()+":"+(count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public int getCount(){
return count;
}
}
2.3 修饰一个静态方法
public class Solution5 {
public static void main(String[] args) {
//同一个对象
// SyncThread syncThread=new SyncThread();
// Thread thread1=new Thread(syncThread,"syncThread1");
// Thread thread2= new Thread(syncThread,"syncThread2");
// thread1.start();
// thread2.start();
//
//不同对象
SyncThread5 syncThread1=new SyncThread5();
SyncThread5 syncThread2=new SyncThread5();
Thread thread1=new Thread(syncThread1,"syncThread1");
Thread thread2= new Thread(syncThread2,"syncThread2");
thread1.start();
thread2.start();
}
}
class SyncThread5 implements Runnable {
private static int count;
public SyncThread5() {
count=0;
}
public synchronized static void method() {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName()+":"+(count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public synchronized void run(){
method();
}
}
syncThread1和syncThread2是SyncThread5的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁。这与Demo1是不同的。
2.4 修饰一个类
Synchronized还可作用于一个类,用法如下:
class ClassName {
public void method() {
synchronized(ClassName.class) {
// todo
}
}
}
修饰一个类
public class Solution6 {
public static void main(String[] args) {
//同一个对象
// SyncThread syncThread=new SyncThread();
// Thread thread1=new Thread(syncThread,"syncThread1");
// Thread thread2= new Thread(syncThread,"syncThread2");
// thread1.start();
// thread2.start();
//
//不同对象
SyncThread6 syncThread1=new SyncThread6();
SyncThread6 syncThread2=new SyncThread6();
Thread thread1=new Thread(syncThread1,"syncThread1");
Thread thread2= new Thread(syncThread2,"syncThread2");
thread1.start();
thread2.start();
}
}
class SyncThread6 implements Runnable {
private static int count;
public SyncThread6() {
count=0;
}
public static void method() {
synchronized (SyncThread6.class){
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName()+":"+(count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Override
public synchronized void run(){
method();
}
}
3、总结
- 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
- 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
- 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。