java 线程同步以及数据安全 synchronized 的使用
Day20线程安全问题
一、线程同步以及数据安全
1、概念: 多个线程同时执行,在任意时刻 都可能被其他的线程抢占cpu,经过互相抢占,最终的结果可能有重复或者丢失 2、线程同步 线程的同步有两种方式一种是通过synchronized (o) {}同步代码块还有一种是同步方法直接在方法中加上synchronized对方法进行修饰,在同一时刻,只能有一个线程来执行特定的代码 3、同步代码块: synchronized(锁){ 任意锁 互斥锁 需要被同步的代码 } 锁的概念: 以上代码中的锁可以是任意对象,但必须是唯一的,又叫互斥锁,由于是多个线程共用一个,且每次只能给一个线程用,一个线程用完之后在由线程跟线程之间进行竞争,固又叫互斥锁。 4、同步方法: 使用同步方法格式如下: public synchronized void method(){ 写需要被同步的代码 } 在非静态方法中同步方法的锁默认是this,同步方法要想被执行,就需要被调用 同步静态方法:与同步非静态方法不同点就是锁不一样锁是资源类.class的Class对象 |
二、共享资源同步代码的操作
1、相同操作,共享资源: 使用同步代码块: 在run方法中就可以直接使用锁this 使用同步方式: 在资源类中创建同步方法在run方法中调用锁默认是 this 2、不同操作 共享资源 使用同步代码块: 在不同的操作类的run方法中使用,锁是资源类对象 使用同步方法: 在资源类中创建同步方法默认的锁是this也就是资源类对象 3、懒汉式的单例 public static LazyInstance getInstance(){ if(instance==null){// 第一次检查 对象是否为空 ,为空 则去判断 锁,如果不为空 直接返回 (提高效率降低锁的对比此数) synchronized(LazyInstatnce.class){ if(instance==null){//第二次检查 确保不会创建多个实例,如果为空就创建 不为空则 返回 instance = new LazyInstatnce(); } } } }
|
三、死锁和守护进程
1、概念: 线程双方都持有者自己的锁 不放,还需要对象的锁,僵持 产生死锁 2、形成条件: 1)同步嵌套 2)锁不同 代码如下:(关键代码) public class Test {
public static void main(String[] args) { GirlFriend friend = new GirlFriend(); BoyFriend friend2 = new BoyFriend();
friend.start(); friend2.start(); } }
class GirlFriend extends Thread{ @Override public void run() { synchronized (Lock.a) { System.out.println("女朋友拿到了筷子a"); synchronized (Lock.b) { System.out.println("女朋友拿着筷子b"); } } }
}
class BoyFriend extends Thread{ @Override public void run() { try { Thread.sleep(300); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } synchronized (Lock.b) { System.out.println("男朋友拿到了筷子b"); synchronized (Lock.a) { System.out.println("男朋友拿到了筷子a"); } } } }
class Lock{ static Object a = new Object(); static Object b = new Object(); }
3、守护线程: 子线程设置成为守护线程, 当主线程 执行完了,子线程就结束执行 线程对象.setDaemon(true)默认为false 设置守护线程的方法要写在 start前面 代码如下(关键代码): public class Test2 {
public static void main(String[] args) { MyThread2 thread2 = new MyThread2(); thread2.setDaemon(true);// true 此线程是守护线程 thread2.start(); for (int i = 0; i < 10; i++) { System.out.println("主线程执行"+i); } } }
class MyThread2 extends Thread{ @Override public void run() { for (int i = 0; i < 10000; i++) { System.out.println("子线程执行了"+i); } } } |
四、线程间通信:
1、线程实现有序执行 各个方法介绍: wait()挂起当前线程, 让当前线程进入到阻塞状态 notify()随机唤醒一个因为调用wait进入阻塞状态的线程 notifyAll()将所有的因为wait进入到阻塞状态的线程全部唤醒 都是来资源Object 类 ,在任何类中都可以调用 必须在同步方法或者同步代码块中调用 否则 IllegalMonitorStateException 2、wait()和sleep()的区别 相同点: 都可以使线程进入到阻塞状态 不同点: 1 sleep() 设定时间到了,可以自己进入到可执行状态 wait() 必须要通过notify唤醒 才能进入到 可执行状态 2 sleep 可以释放 cpu资源,不能释放锁资源 wait() 释放了cpu 也释放了锁 代码实现如下: package com.LiLinXiong.work2;
public class TestPrintABC123 { public static void main(String[] args) {
//第二种方法 Object o = new Object(); Printabc printabc = new Printabc(o); Print123 print123 = new Print123(o);
Thread thread = new Thread(print123); Thread thread2 = new Thread(printabc); thread.start(); thread2.start();
} }
class Printabc implements Runnable{ Object o; public Printabc(Object o) { this.o = o; } @Override public void run(){ synchronized (o) { for (int i = 65; i < 91; i++) { System.out.print((char)i); if(i == 90){ o.notify(); break; } try { o.notify(); o.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
class Print123 implements Runnable{ Object o;
public Print123(Object o) { this.o = o; } @Override public void run() { synchronized (o) { for (int i = 1; i <= 52; i++) { System.out.print(i); if(i == 52){ o.notify(); break; } if(i%2==0){ o.notify(); try { o.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } } |