1.synchronized关键字说明
对于操作系统进程来说,资源分配是很重要的一个问题,如果操作不当会造成多个进程抢占同一个资源,进而造成死锁,无限等待。线程同样无法避免这个问题,对于资源的控制与分配,Java可以使用Synchronzied同步
(注:避免进程死锁的四个必要条件(任何一个不成立死锁就会发生):
- 互斥条件:每个资源同时只能有一个进程访问。
- 请求与保持:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
)
synchronized用法
- synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。
- 无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁。
- 每个对象只有一个锁(lock)与之相关联。
2.测试
(1)没有加锁的情况
有一个打印Print类,PrintNumber方法打印数字,PrintChar打印字母。
两个线程,都有一个Print类变量。一个打印字母,一个打印数字
class PrintTest {
public void PrintNumber() {
for (int i = 0; i < 20; i++) {
System.out.println(i);
}
}
public void PrintChar() {
for (int i = 0; i < 20; i++) {
System.out.println((char) (i+65));
}
}
}
class mthread12 extends Thread {
public PrintTest test;
public mthread12(PrintTest test) {
// TODO Auto-generated constructor stub
this.test=test;
}
@Override
public void run() {
// TODO Auto-generated method stub
test.PrintNumber();
}
}
class mthread13 extends Thread {
public PrintTest test;
public mthread13(PrintTest test) {
// TODO Auto-generated constructor stub
this.test=test;
}
@Override
public void run() {
// TODO Auto-generated method stub
test.PrintChar();
}
}
在主方法中new 一个Print类对象,两个线程都引用这一个对象。
public class thread2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
PrintTest test=new PrintTest();
mthread12 mthread12=new mthread12(test);
mthread13 mthread13=new mthread13(test);
mthread12.start();
mthread13.start();
}
}
输出结果:
打印数字线程还没有执行完,打印字母线程就使用了打印对象。对象资源没有加锁。
(2)一个方法加锁的情况
现在在Print类的打印数字方法加锁
public synchronized void PrintNumber() {
for (int i = 0; i < 20; i++) {
System.out.println(i);
}
}
public void PrintChar() {
for (int i = 0; i < 20; i++) {
System.out.println((char) (i+65));
}
}
执行结果
两个线程同时执行。虽然调用了同一个对象,并且对象的某个方法加了锁,但是其他线程依然可以访问该对的没有加锁的代码块而不受阻塞。
(3)两个方法同时加锁
给两个方法同时加锁
public synchronized void PrintNumber() {
for (int i = 0; i < 20; i++) {
System.out.println(i);
}
}
public synchronized void PrintChar() {
for (int i = 0; i < 20; i++) {
System.out.println((char) (i+65));
}
}
输出结果
先打印数字后再打印字母,在打印数字时打印字母线程不能访问对象而受阻塞。当线程访问一个对象的synchronized方法,其他的线程不能访问该对象加了synchronized的其他方法。
(4)使用同类不同对象
现在构造线程时使用不同的Print对象
public static void main(String[] args) {
// TODO Auto-generated method stub
mthread12 mthread12=new mthread12(new PrintTest());
mthread13 mthread13=new mthread13(new PrintTest());
mthread12.start();
mthread13.start();
}
打印结果
同步执行。此时synchronized针对的是同一个对象,而不是同一个类。
5、给对象加锁
Print对象方法都不加锁
public void PrintNumber() {
for (int i = 0; i < 20; i++) {
System.out.print(i);
}
}
public void PrintChar() {
for (int i = 0; i < 20; i++) {
System.out.print((char) (i + 65));
}
}
在两个线程的run方法里加入synchronized
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (test) {
test.PrintNumber();
}
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (test) {
test.PrintChar();
}
}
运行结果
运行结果要么是先打印完数字,要么先打印完字母。后面的线程会被阻塞。拿到test对象锁的线程可以执行被synchronized包围的代码块。
(6)给类加锁
把Print类的两个方法都加上锁并且改为静态方法
class PrintTest {
public synchronized static void PrintNumber() {
for (int i = 0; i < 20; i++) {
System.out.print(i);
}
}
public synchronized static void PrintChar() {
for (int i = 0; i < 20; i++) {
System.out.print((char) (i + 65));
}
}
}
两个线程run方法的synchronized去掉
@Override
public void run() {
// TODO Auto-generated method stub
test.PrintNumber();
}
@Override
public void run() {
// TODO Auto-generated method stub
test.PrintChar();
}
主方法new 两个不同的对象
public static void main(String[] args) {
// TODO Auto-generated method stub
//PrintTest test=new PrintTest();
mthread12 mthread12 = new mthread12(new PrintTest());
mthread13 mthread13 = new mthread13(new PrintTest());
mthread12.start();
mthread13.start();
}
输出结果
同时访问该类的不对对象也会被阻塞。在static修饰的方法加锁,作用域是整个类,而不是这个类的某一个对象。
总结
1.synchronized可以修饰方法和一段代码块。
void synchronized method(){
}
等价于
void method(){
synchronized(this){
}
}
修饰一个代码块时,只有拿到()里的对象的锁,才能执行{}里的代码。
2.在静态方法里加锁,是锁定了这个类,而不是该类的对象
void synchronized static method(){
}
等价于
void method(){
synchronized(this.getClass){
}
}
注:在线程拿到锁之后,执行sleep()休眠时,不管休眠时间多久,都不会释放该线程拿到的锁。
3.修饰代码块时,可以更加精细的控制同步,拿到o1的锁时执行一段,拿到o2的锁时执行一段。一个对象只有一把钥匙一把锁,一个线程拿到后,其他线程都必须等待释放后抢占。当线程访问一个对象的synchronized方法,其他的线程不能访问该对象加了synchronized的其他方法,但是可以随意访问没有加synchronized的方法。
void method(){
synchronized(Object o1){
}
synchronized(Object o2){
}
}