java多线程:锁
java的多线程中的锁是干嘛的呢?在网上找了很多博客,大都是很专业的语言,让我一时间摸不着头脑。下面分三个部分来总结多线程中的锁的概念。
一,基础概念:
多线程在运行的时候可能会遇到这样的问题,多个线程要用到同一个资源,那么可能会出现错乱,比如线程要改动资源里的数据,那么多个线程同时改就乱了套了。就像公共厕所,必须要一个一个接着上,不能两个人或者多个人同时上。那么锁这个东西就是像厕所里的门,一个人在上厕所,锁上了们,那下一个人就不能进去了。同样的,如果我们想让某一个程序或者某一个变量只能同时被一个线程运行,就得给程序上锁。所以上了锁,就能保证线程有秩序的去运行了。
这里补充一个面试常问的问题:进程和线程的区别:进程是某一个具有独立功能的程序的运行活动,它可以申请系统资源,是一个活动的实体。二线程的范围要比进程小,一个进程可以拥有多个线程。我们把进程作为分配资源的基本单位,而把线程作为独立运行和独立调用的基本单位。
二,实现方式:
具体来说呢。首先Object对象,都有自己的一把锁,也就是说,你随便定义一个变量,这个变量就有一把锁,保证自己只能同时被一个线程使用。这是对象锁。如果我们想给一个函数上锁怎么办?函数定义加上关键字synchronized就可以了,这个函数就被上锁了。如果我们想让一段代码块上锁呢?
synchronized(object){
#####要加锁的代码块
}
这样代码块就被上锁了,而synchronized()里面的参数的作用就是提供锁,因为odject对象自己有把锁,被synchronized(object)标记的代码块,自然就被object的锁,锁上了。
那么我们如何给一个类上锁呢?我需要在类的静态成员中添加synchronized,因为类的静态成员,是所有实例共享的,所以给静态成员加锁,就相当于给类加锁。其实类锁的作用并不是给类加锁:给类的普通成员函数加锁,同一个实例对象,的确不可以用多个线程访问加锁的成员函数。但是处于两个实例对象中的不同线程访问加锁的成员函数就不受影响了。所以类锁的概念就是让不同的实例对象中线程,访问静态成员函数也受到限制。
所以总结一下,锁的类型有:对象锁,类锁(实际上也是方法所),方法锁,代码块锁。
看一下代码例子:
测试主类:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package javathreadlock;
import java.lang.Thread;
/**
*
* @author chenyongkang
*/
public class JavaThreadlock {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
//两个测试类的实例对象
ThreadLock lock = new ThreadLock();
ThreadLock lock2 = new ThreadLock();
//同一个对象的两个线程
Test1 t1 = new Test1(lock,1);
Test2 t2 = new Test2(lock,2);
t1.start();
t2.start();
//通过测试结果可以看出,t1,t2线程不能同时执行被synchronized标记的代码块或者方法。
//不同对象的不同线程
Test3 t3 = new Test3(lock2,3);
Test4 t4 = new Test4(lock,4);
//t3.start();
//t4.start();
//通过测试结果可以看出,不同对象的不同线程,执行被synchronized标记的代码块或者方法不受影响,
//而执行被synchronized标记的静态函数,则受到限制
}
}
参与测试的线程类:
class Test1 extends Thread{
public ThreadLock lock;
public int i;
public Test1(ThreadLock lock,int i){
this.lock=lock;
this.i=i;
}
public void run(){
lock.nolock(i);
lock.lock5(i);
//ThreadLock.lock4(i);
}
}
class Test2 extends Thread{
public ThreadLock lock;
public int i;
public Test2(ThreadLock lock,int i){
this.lock=lock;
this.i=i;
}
public void run(){
lock.nolock(i);
//lock.lock5(i);
ThreadLock.lock4(i);
}
}
class Test3 extends Thread{
public ThreadLock lock;
public int i;
public Test3(ThreadLock lock,int i){
this.lock=lock;
this.i=i;
}
public void run(){
lock.nolock(i);
//lock.lock1(i);
//lock.lock2(i);
lock.lock5(i);
ThreadLock.lock4(i);
}
}
class Test4 extends Thread{
public ThreadLock lock;
public int i;
public Test4(ThreadLock lock,int i){
this.lock=lock;
this.i=i;
}
public void run(){
lock.nolock(i);
//lock.lock1(i);
//lock.lock2(i);
lock.lock5(i);
ThreadLock.lock4(i);
}
}
加各种锁的资源类:
class ThreadLock{
//统计类锁加锁次数
public static int i;
//类对象
public Object obj = new Object();
//不加锁的代码块
public void nolock(int thread){
try{
System.out.println("线程"+thread+"正在运行");
Thread.sleep(2000);
}
catch(Exception e){
e.printStackTrace();
}
}
//方法锁
public synchronized void lock1(int thread){
try{
System.out.println("方法锁一正在被线程"+thread+"运行");
Thread.sleep(2000);
System.out.println("方法所一被线程"+thread+"执行完了");
}
catch(Exception e){
e.printStackTrace();
}
}
//代码块锁1
public void lock2(int thread){
synchronized(this){
try{
System.out.println("代码块锁方法一正在被线程"+thread+"运行");
Thread.sleep(2000);
System.out.println("代码块锁方法一正在被线程"+thread+"执行完了");
}
catch(Exception e){
e.printStackTrace();
}
}
}
//代码块锁方法2
public void lock3(int thread){
synchronized(obj){
try{
System.out.println("代码块锁方法二正在被线程"+thread+"运行");
Thread.sleep(2000);
System.out.println("代码块锁方法二正在被线程"+thread+"执行完了");
}
catch(Exception e){
e.printStackTrace();
}
}
}
//类锁方法
public synchronized static void lock4(int thread){
try{
System.out.println("类锁方法正在被线程"+thread+"运行");
Thread.sleep(2000);
System.out.println("类锁方法正在被线程"+thread+"执行完了");
}
catch(Exception e){
e.printStackTrace();
}
}
//代码块锁3
public void lock5(int thread){
synchronized(this){
try{
System.out.println("代码块锁方法三正在被线程"+thread+"运行");
Thread.sleep(2000);
System.out.println("代码块锁方法三正在被线程"+thread+"执行完了");
}
catch(Exception e){
e.printStackTrace();
}
}
}
}
我们把Test1,和Test2的run里面改一下。t1和t2分别执行lock1和lock5,互相不影响,因为类锁和锁函数里面的锁不冲突。而分别执行lock2和lock6,是不可以的,因为lock2和lock6中的代码块参数都是this,这两个代码块共用一个this的锁。分别执行lock1和lock6,也不可以,因为this是指当前类对象的锁,普通函数上的锁也是当前类对象的锁。如果分别执行lock1所以被synchronized标记的代码块,关键看锁是哪一个。同一个参数与的不同代码块,相当于被绑在一起。
三,wait 和sleep的区别。
wait函数是Object的类函数,表示该对象的锁暂时挂起,任何线程都不能使用这个对象,正在使用的线程,也必须交出锁,然后和别的要使用该对象的线程等着。如果要恢复状态,就使用notify函数,然后再等待池里,随便选一个等待的线程来继续运行。
而sleep函数是Thread线程的函数,表示当前线程睡眠多少多少时间。
四,死锁的概念
先简单举个例子,介绍一下死锁,比如有两个线程A,B,和两个对象a,b。现在A正在调用a,调用a之后A想调用b。B正在使用b,调用完b,之后想调动a。于是A,B 两个线程分别抱着a,b的锁不放开,互相等对方放开锁,然后自己就可以执行下一步。于是程序就发生了死锁。我举一个栗子:
线程t1使用了fun1之后想使用fun2,t2使用了fun2之后想使用fun1
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package javadeadlock;
/**
*
* @author chenyongkang
*/
public class Javadeadlock {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
ThreadLock lock=new ThreadLock();
Test1 t1=new Test1(lock,1);
Test2 t2=new Test2(lock,2);
t1.start();
t2.start();
}
}
class ThreadLock extends Thread{
private Object obj=new Object();
public synchronized void fun1(int i){
try{
System.out.println("我在使用函数1,我是线程"+i);
Thread.sleep(2000);
System.out.println("我使用完了,我要是使用函数2我是线程"+i);
fun2(i);
}
catch(Exception e){
e.printStackTrace();
}
}
public void fun2(int i){
synchronized(obj){
try{
System.out.println("我在使用函数2,我是线程"+i);
Thread.sleep(2000);
System.out.println("我使用完了,我要使用函数1我是线程"+i);
fun1(i);
}
catch(Exception e){
e.printStackTrace();
}
}
}
}
class Test1 extends Thread{
ThreadLock lock;
int i;
public Test1(ThreadLock lock,int i){
this.lock=lock;
this.i=i;
}
public void run(){
try{
lock.fun1(i);
}catch(Exception e){
e.printStackTrace();
}
}
}
class Test2 extends Thread{
ThreadLock lock;
int i;
public Test2(ThreadLock lock,int i){
this.lock=lock;
this.i=i;
}
public void run(){
try{
lock.fun2(i);
}catch(Exception e){
e.printStackTrace();
}
}
}
结果:卡在那里。