一、多线程并发安全问题
1. 多线程并发安全问题
当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作临界资源的顺序出现混乱严重时可能导致系统瘫痪。
临界资源:操作该资源的全过程同时只能被单个线程完成。
package thread;
/**
* 多线程并发安全问题
* 当多个线程并发操作同一临界资源,由于线程切换的时机不确定,导致操作顺序出现
* 混乱,严重时可能导致系统瘫痪。
* 临界资源:同时只能被单一线程访问操作过程的资源。
*/
public class SyncDemo {
public static void main(String[] args) {
Table table = new Table();
Thread t1 = new Thread(){
public void run(){
while(true){
int bean = table.getBean();
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
Thread t2 = new Thread(){
public void run(){
while(true){
int bean = table.getBean();
/*
static void yield()
线程提供的这个静态方法作用是让执行该方法的线程
主动放弃本次时间片。
这里使用它的目的是模拟执行到这里CPU没有时间了,发生
线程切换,来看并发安全问题的产生。
*/
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
t1.start();
t2.start();
}
}
class Table{
private int beans = 20;//桌子上有20个豆子
public int getBean(){
if(beans==0){
throw new RuntimeException("没有豆子了!");
}
Thread.yield();
return beans--;
}
}
2. synchronized关键字
synchronized有两种使用方式
在方法上修饰,此时该方法变为一个同步方法
同步块,可以更准确的锁定需要排队的代码片段
同步方法
当一个方法使用synchronized修饰后,这个方法称为"同步方法",即:多个线程不能同时 在方法内部执行。只能有先后顺序的一个一个进行。 将并发操作同一临界资源的过程改为同步执行就可以有效的解决并发安全问题。
package thread;
/**
* 多线程并发安全问题
* 当多个线程并发操作同一临界资源,由于线程切换的时机不确定,导致操作顺序出现
* 混乱,严重时可能导致系统瘫痪。
* 临界资源:同时只能被单一线程访问操作过程的资源。
*/
public class SyncDemo {
public static void main(String[] args) {
Table table = new Table();
Thread t1 = new Thread(){
public void run(){
while(true){
int bean = table.getBean();
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
Thread t2 = new Thread(){
public void run(){
while(true){
int bean = table.getBean();
/*
static void yield()
线程提供的这个静态方法作用是让执行该方法的线程
主动放弃本次时间片。
这里使用它的目的是模拟执行到这里CPU没有时间了,发生
线程切换,来看并发安全问题的产生。
*/
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
t1.start();
t2.start();
}
}
class Table{
private int beans = 20;//桌子上有20个豆子
/**
* 当一个方法使用synchronized修饰后,这个方法称为同步方法,多个线程不能
* 同时执行该方法。
* 将多个线程并发操作临界资源的过程改为同步操作就可以有效的解决多线程并发
* 安全问题。
* 相当于让多个线程从原来的抢着操作改为排队操作。
*/
public synchronized int getBean(){
if(beans==0){
throw new RuntimeException("没有豆子了!");
}
Thread.yield();
return beans--;
}
}
同步块
有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发效率。同步块可以更准确的控制需要多个线程排队执行的代码片段。
语法:
synchronized(同步监视器对象){
需要多线程同步执行的代码片段
}
同步监视器对象即上锁的对象,要想保证同步块中的代码被多个线程同步运行,则要求多个线程看到的同步监视器对象是同一个。
package thread;
/**
* 有效的缩小同步范围可以在保证并发安全的前提下尽可能提高并发效率。
*
* 同步块
* 语法:
* synchronized(同步监视器对象){
* 需要多个线程同步执行的代码片段
* }
* 同步块可以更准确的锁定需要多个线程同步执行的代码片段来有效缩小排队范围。
*/
public class SyncDemo2 {
public static void main(String[] args) {
Shop shop = new Shop();
Thread t1 = new Thread(){
public void run(){
shop.buy();
}
};
Thread t2 = new Thread(){
public void run(){
shop.buy();
}
};
t1.start();
t2.start();
}
}
class Shop{
public void buy(){
/*
在方法上使用synchronized,那么同步监视器对象就是this。
*/
// public synchronized void buy(){
Thread t = Thread.currentThread();//获取运行该方法的线程
try {
System.out.println(t.getName()+":正在挑衣服...");
Thread.sleep(5000);
/*
使用同步块需要指定同步监视器对象,即:上锁的对象
这个对象可以是java中任何引用类型的实例,只要保证多个需要排队
执行该同步块中代码的线程看到的该对象是"同一个"即可
*/
synchronized (this) {
// synchronized (new Object()) {//没有效果!
System.out.println(t.getName() + ":正在试衣服...");
Thread.sleep(5000);
}
System.out.println(t.getName()+":结账离开");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在静态方法上使用synchronized
当在静态方法上使用synchronized后,该方法是一个同步方法。由于静态方法所属类,所以一定具有同步效果。
静态方法使用的同步监视器对象为当前类的类对象(Class的实例)。
注:类对象会在后期反射知识点介绍。
package thread;
/**
* 静态方法上如果使用synchronized,则该方法一定具有同步效果。
*/
public class SyncDemo3 {
public static void main(String[] args) {
Thread t1 = new Thread(){
public void run(){
Boo.dosome();
}
};
Thread t2 = new Thread(){
public void run(){
Boo.dosome();
}
};
t1.start();
t2.start();
}
}
class Boo{
/**
* synchronized在静态方法上使用是,指定的同步监视器对象为当前类的类对象。
* 即:Class实例。
* 在JVM中,每个被加载的类都有且只有一个Class的实例与之对应,后面讲反射
* 知识点的时候会介绍类对象。
*/
public synchronized static void dosome(){
Thread t = Thread.currentThread();
try {
System.out.println(t.getName() + ":正在执行dosome方法...");
Thread.sleep(5000);
System.out.println(t.getName() + ":执行dosome方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
静态方法中使用同步块时,指定的锁对象通常也是当前类的类对象
class Boo{
public static void dosome(){
/*
静态方法中使用同步块时,指定同步监视器对象通常还是用当前类的类对象
获取方式为:类名.class
*/
synchronized (Boo.class) {
Thread t = Thread.currentThread();
try {
System.out.println(t.getName() + ":正在执行dosome方法...");
Thread.sleep(5000);
System.out.println(t.getName() + ":执行dosome方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
互斥锁
当多个线程执行不同的代码片段,但是这些代码片段之间不能同时运行时就要设置为互斥的。
使用synchronized锁定多个代码片段,并且指定的同步监视器是同一个时,这些代码片段之间就是互斥的。
package thread;
/**
* 互斥锁
* 当使用synchronized锁定多个不同的代码片段,并且指定的同步监视器对象相同时,
* 这些代码片段之间就是互斥的,即:多个线程不能同时访问这些方法。
*/
public class SyncDemo4 {
public static void main(String[] args) {
Foo foo = new Foo();
Thread t1 = new Thread(){
public void run(){
foo.methodA();
}
};
Thread t2 = new Thread(){
public void run(){
foo.methodB();
}
};
t1.start();
t2.start();
}
}
class Foo{
public synchronized void methodA(){
Thread t = Thread.currentThread();
try {
System.out.println(t.getName()+":正在执行A方法...");
Thread.sleep(5000);
System.out.println(t.getName()+":执行A方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void methodB(){
Thread t = Thread.currentThread();
try {
System.out.println(t.getName()+":正在执行B方法...");
Thread.sleep(5000);
System.out.println(t.getName()+":执行B方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
二、聊天室(续)
1. 实现服务端发送消息给客户端
在服务端通过Socket获取输出流,客户端