1.什么是多线程并发运行安全问题?
当多个线程并发操作一个数据时,由于线程操作的时间不可控的原因,可能会导致操作该数据时的过程没有按照程序设计的执行顺序运行,导致操作后数据出现混乱,严重时可导致系统瘫痪。
2.用 synchronized 修饰的方法
当一个方法用 synchronized 修饰,那么该方法变为“同步方法“多个线程不能同时进入方法内容运行的,必须时有顺序的一个一个运行,这样就能避免并发安全问题。
案例:抢豆豆事件(使豆豆的剩余量不能为负数)
public class SyncDemo {
public static void main(String[] args) {
Table table=new Table();
Thread t1=new Thread(){
public void run(){
while(true){
int n=table.getBean();
Thread.yield();
System.out.println(Thread.currentThread().getName()+",豆豆还剩"+n);
}
}
};
Thread t2=new Thread(){
public void run(){
while(true){
int n=table.getBean();
Thread.yield();
System.out.println(Thread.currentThread().getName()+",豆豆还剩"+n);
}
}
};
t1.start();
t2.start();
}
}
class Table{
private int bean=10;
public synchronized int getBean(){
if(bean==0){
throw new RuntimeException("没有豆豆了");
}
/*
* yield()将导致线程从运行状态转到就绪状态,但是可能没有效果
* 作用时暂停当前正在执行的线程对象(放弃当前拥有的cpu资源),
* 并执行其他线程。
*/
Thread .yield();//模拟切换线程
return bean--;
}
}
复制代码
运行结果:
当豆豆剩余量为 0 时,程序抛出异常。如果不加锁的话,程序有一定的几率会跳过豆豆为 0,这个条件,而一直运行下去。
3.同步块
1.有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高效率。
2.同步块
synchronized(同步监视器){
//需要同步运行的代码片段
}
复制代码
3.同步块可以更灵活准确的锁定需要同步运行的代码片段,这样可以有效缩小同步范围提高并发效率,但是需要注意,必须保证多个线程看到同步监视器对象是同一个对象才可以。
案例:模拟商场买衣服
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(){
try {
String name=Thread.currentThread().getName();
System.out.println(name+"选衣服");
Thread.sleep(2000);
synchronized (this) {
System.out.println(name+"试衣服");
Thread.sleep(2000);
}
System.out.println(name+"结账走人");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
复制代码
运行结果:
Thread-1选衣服
Thread-0选衣服
Thread-1试衣服
Thread-1结账走人
Thread-0试衣服
Thread-0结账走人
复制代码
分析:
两个线程不会同时在试衣服,因为在试衣服环节设置了同步块,这使线程在运行试衣服环节时变得有序。
4.使用 Synchronized 修饰静态方法
1.静态方法若使用了 Synchronized 修饰后,那么方法一定具有同步效果
2.静态方法的对象是当前类的对象
3.class 类的每一个实例用于表达 jvm 加载一个类,当 jvm 加载一个类时后就会实例化一个 class 的实例用于表示它,每一个类在 jvm 都有且只有一个 class 的实例,所以静态方法锁的就是当前类对应的 class 的实例。
public class Syncdemo3 {
public static void main(String[] args) {
Thread t1=new Thread(){
public void run(){
Foo.dosome();
}
};
Thread t2=new Thread(){
public void run(){
Foo.dosome();
}
};
t1.start();
t2.start();
}
}
class Foo{
public synchronized static void dosome(){
try {
String name=Thread.currentThread().getName();
System.out.println(name+"正在运行dosome方法");
Thread.sleep(3000);
System.out.println(name+"运行结束》》");
} catch (Exception e) {
e.printStackTrace();
}
}
}
复制代码
5.互斥锁
使用 synchronized
锁定多段代码,而锁的对象相同时,这些代码片段之间就是互斥锁,多个线程不能同时执行这些方法。
public class Syncdemo4 {
public static void main(String[] args){
Eoo eoo=new Eoo();
Thread t1=new Thread(){
public void run(){
eoo.test01();
}
};
Thread t2=new Thread(){
public void run(){
eoo.test02();
}
};
t1.start();
t2.start();
}
}
class Eoo{
public synchronized void test01(){
String name=Thread.currentThread().getName();
System.out.println(name+"正在运行1方法");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"1运行完毕");
}
public synchronized void test02(){
String name=Thread.currentThread().getName();
System.out.println(name+"正在运行2方法");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"2运行完毕");
}
}
复制代码
运行结果:
Thread-0正在运行1方法
Thread-01运行完毕
Thread-1正在运行2方法
Thread-12运行完毕
复制代码
结果分析:
因为两个线程锁的对象相同,因此,当一个线程运行它的方法时,另一个线程不会一起运行运行它的方法,直到,一个线程运行结束后,他才可以进入这个类,来运行他的方法。
6.死锁现象
线程都是保持着自己的锁,但是都是等待对方来释放锁,就出现互相”僵持“的情况,导致程序不会继续向后运行。
public class Syncdemo5 {
public static void main(String[] args) {
Poo p=new Poo();
Thread t1=new Thread(){
public void run(){
p.method1();
}
};
Thread t2=new Thread(){
public void run(){
p.method2();
}
};
t1.start();
t2.start();
}
}
class Poo{
Object A=new Object();
Object B=new Object();
public void method1(){
String name=Thread.currentThread().getName();
synchronized (A) {
try {
System.out.println(name+"A正在运行。。。");
Thread.sleep(3000);
System.out.println(name+"A运行完毕");
method2();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void method2(){
String name=Thread.currentThread().getName();
synchronized (B) {
try {
System.out.println(name+"B正在运行。。。");
Thread.sleep(3000);
System.out.println(name+"B运行完毕");
method1();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
复制代码
运行结果:
Thread-0A正在运行。。。
Thread-1B正在运行。。。
Thread-0A运行完毕
Thread-1B运行完毕
复制代码
结果分析:
虽然有输出结果,但是,程序并没有运行结束,两个线程都等待着对方来释放锁,而僵持不下。
7.wait()和 sleep()的区别
1.wait 是 object 类中的方法,sleep 是 Thread 中的方法
2.最主要的是 sleep 方法调用后,并没有释放锁,使得线程仍然可以同步控制,sleep 不会让出出系统资源,sleep 方法可以在任何地方使用,而 wait 必须在 synchronized
方法或者 synchronized
块中使用,否则会抛出异常 (java.lang.IllegalMonitorStateException)
,wait 方法不仅让出 cpu,还会释放已占有的同步资源;
3. sleep
必须捕获异常,而 wait
, nofify
和 nofifyAll
不需要捕获异常。
4. sleep
是让某个线程暂时运行一段时间,其控制范围是由当前线程决定的,主动权在自己手里,而 wait
是由某个确定的对象来调用,主动权在某个对象手里。
public class WaitDemo {
public static void main(String[] args) {
Thread t1=new ThreadMy01();
Thread t2=new ThreadMy02();
t1.start();
t2.start();
}
}
class ThreadMy01 extends Thread{
public static StringBuilder str=new StringBuilder();
public void run(){
String name=Thread.currentThread().getName();
synchronized (str) {
for(int i=0;i<5;i++){
try {
str.wait(300);//完全释放锁
//Thread.sleep(300);//不释放锁
str.append('a');
System.out.println(name+str);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//唤醒wait状态
class ThreadMy02 extends Thread{
public void run(){
synchronized (ThreadMy01.str) {
for(int i=0;i<2;i++){
try {
Thread.sleep(2000);
System.out.println("888");
} catch (Exception e) {
e.printStackTrace();
}
}
//唤醒wait()状态
ThreadMy01.str.notify();
}
}
}
复制代码
运行结果:
888
888
Thread-0a
Thread-0aa
Thread-0aaa
Thread-0aaaa
Thread-0aaaaa
复制代码
结果分析:
在运行以上代码时 sleep 虽然睡眠 2 秒,但是 wait 并没有 执行,说明 sleep 不会让出系统资源。
线程的生命周期:
来源:https://blog.csdn.net/weixin_47600732
复制代码
评论