同步线程
-
同步方法
关键字:synchronized
public synchronized void method(int args){}
被synchronized修饰的方法会给每个访问的对象配备一把锁,每个对象在访问该方法的时候需要排队访问,且当前正在访问的对象会把锁挂上去,其他对象就得等该对象访问完之后才能继续访问,避免了线程冲突的问题
缺点:若将一个大的方法申明为synchronized 将会影响效率
-
同步块
synchronized (object){ 代码块 }
object成为同步监视器,可以是任何对象
同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
//正确的锁定做法 public class UnsafeList { public static void main(String[] args) throws InterruptedException { List<String> list = new ArrayList<String>(); for (int i = 0; i < 10000; i++) { //通过lambda表达式重写Thread里面的run方法 new Thread(()->{ //锁住list块 synchronized (list){ list.add(Thread.currentThread().getName()); } }).start(); } Thread.sleep(5000); System.out.println(list.size()); } } //错误的锁定做法(容易出错!!!) public class UnsafeList { public static void main(String[] args) throws InterruptedException { List<String> list = new ArrayList<String>(); synchronized (list){ for (int i = 0; i < 10000; i++) { //通过lambda表达式重写Thread里面的run方法 new Thread(()->{ //锁住list块 list.add(Thread.currentThread().getName()); }).start(); } } Thread.sleep(5000); System.out.println(list.size()); } }
锁定是针对单个执行个体而言的,所以锁定要锁定到具体的变化个体,不可以锁定全体,比如上面这个代码里面,不能把for循环整个放在synchronized里面(如上图错误做法所示),如果这么做,程序会将整个for循环当作第一个线程,这个线程执行完之后才会执行其他(你不加synchronized本来也是如此执行的,这么做一点意义也没有)
但是如果将synchronized定位到单条、给数组元素赋值的语句上去(如上图正确做法所示)程序就把i = 0当作第一个线程访问,会锁定这里的list,执行其中的代码,防止其他线程同时给这个元素赋值造成覆盖
补充
-
JUC
在Java 5.0 提供了 java.util.concurrent包(简称JUC包)在此包中增加了在并发编程中很常用的工具类
还是上面那个代码,我们稍作修改,不用加synchronized关键字就可以避免并发出错
import java.util.concurrent.CopyOnWriteArrayList; //测试JUC安全性的集合 public class TestJUC { public static void main(String[] args) { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
ArrayList集合是不安全的,CopyOnWriteArrayList集合是安全的
死锁现象
当两个对象A和B,一开始A拥有进程C的锁,B拥有进程D的锁,然后A想要拥有D的锁,B想要拥有C的锁,这时候A和B两人各自抱着手中的锁不放开,又“眼馋”对方手里的锁,就出现了僵持状态,这种现象就称为死锁
public class DeadLock {
public static void main(String[] args) {
MakeUp girl1 = new MakeUp("小红",0);
MakeUp girl2 = new MakeUp("小蓝",1);
girl1.start();
girl2.start();
}
}
//口红
class LipStick{
}
//镜子
class Mirror{
}
class MakeUp extends Thread {
//需要的资源只有一份,用static关键字来保证资源只有一份
static LipStick lipStick = new LipStick();
static Mirror mirror = new Mirror();
int choice;//选择
String name;//要做出选择的女孩姓名
MakeUp(String name, int choice){
this.name = name;
this.choice = choice;
}
public void run(){
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void makeup() throws InterruptedException {
if(choice==0){
synchronized (lipStick){
System.out.println(this.name+"想要获得口红的锁");
Thread.sleep(1000);
synchronized (mirror){//获得口红的锁同时又想获得镜子的锁
System.out.println(this.name+"想要获得镜子的锁");
}
}
}else{
synchronized (mirror){
System.out.println(this.name+"想要获得镜子的锁");
Thread.sleep(2000);
synchronized (lipStick){//获得镜子的锁同时又想获得口红的锁
System.out.println(this.name+"想要获得口红的锁");
}
}
}
}
}
上面代码会卡顿在
小红想要获得口红的锁
小蓝想要获得镜子的锁
更改之后,代码变成
public class DeadLock {
public static void main(String[] args) {
MakeUp girl1 = new MakeUp("小红",0);
MakeUp girl2 = new MakeUp("小蓝",1);
girl1.start();
girl2.start();
}
}
//口红
class LipStick{
}
//镜子
class Mirror{
}
class MakeUp extends Thread {
//需要的资源只有一份,用static关键字来保证资源只有一份
static LipStick lipStick = new LipStick();
static Mirror mirror = new Mirror();
int choice;//选择
String name;//要做出选择的女孩姓名
MakeUp(String name, int choice){
this.name = name;
this.choice = choice;
}
public void run(){
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void makeup() throws InterruptedException {
if(choice==0){
synchronized (lipStick){
System.out.println(this.name+"想要获得口红的锁");
Thread.sleep(1000);
}
synchronized (mirror){
System.out.println(this.name+"想要获得镜子的锁");
}
}else{
synchronized (mirror){
System.out.println(this.name+"想要获得镜子的锁");
Thread.sleep(2000);
}
synchronized (lipStick){
System.out.println(this.name+"想要获得口红的锁");
}
}
}
}
更改之后的程序运行结果为
小蓝想要获得镜子的锁
小红想要获得口红的锁
小红想要获得镜子的锁
小蓝想要获得口红的锁
进程已结束,退出代码 0
锁
JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步
ReentrantLock类实现了Lock,在实现线程安全的控制中,用这个类,可以显式加锁、释放锁
基本的模式:
class A{
private final ReentrantLock lock = new ReentrantLock();
public void m(){
lock.lock();
try{
}finally{
lock.unlock();
//如果同步代码有异常,要将unock()写入finally语句块
}
}
}
举例(买票问题)
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
}
class Ticket implements Runnable{
int ticketNum = 10;
private final ReentrantLock lock = new ReentrantLock();//创建一个锁
@Override
public void run() {
while(true){
try{
lock.lock();//加锁
if(ticketNum>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNum--);
}else{
break;
}
}finally {
lock.unlock();//释放锁
}
}
}
}